Jeesdev

We fulfill power fantasies

RSS Feed Back

MUTA devlog 3: Lua scripting, game data files and packetwriter improvements

12.5.2018 11:18:45

My most recent workings on MUTA have been about adding ways to implement game content. I have now added the first version of the server side scripting language, Lua. In relation to this work, I've had to implement multiple data file formats so that we know thing slike which scripts to load and which game object uses which script.

File formats for defining game objects

The first thing I wanted to try scripting with were creatures. But before I could script creatures, we had to be able to define them. For that a file format was required. I had used ini-type files so far for quick debug purposes, but I decided it was time to implement a better format.

I've written earlier about the binary mdb file format I wrote for use in the game - we use it for art assets. How ever, while debugging and trying out new stuff, binary files with special editors (mdb has a command line editor in which you give commands like "set column") are a little cumbersome. Text files on the other hand are easy to modify on the go. So a text-based format seemed logical for something like creature definitions that - entries that would change frequently.

Definitions like these aren't complex to represent. An ini file would not do it, but something very close to it would. So here's how our creature definition files look like now:

creature: matlock_creature_1
   name            = Matlock
   description     = Tiesin heti et tollanen haluun ison olla
   sex             = 0.5
   attackable      = 0
   texture_name    =
   script          = test

creature: human_male_1
   name            = Man
   description     = A man
   sex             = 0
   attackable      = 0
   script          = test
   #ae_set is an entity animation set (this line is a comment)
   ae_set          = human_male_1

A similar format is handy for a lot of things: creature, dynamic object, static object and player race definitions to name some. So there's a generalized parser function:

int
parse_def_file(const char *fp,
   int (*on_def)(void *ctx, const char *def, const char *val),
   int (*on_opt)(void *ctx, const char *opt, const char *val),
   void *ctx);

As the function parses the file given in parameter fp, the on_def callback is called when a new definition beginning is found, for example "creature: human_male_1". The callback on_opt is called when an ini-like option is encountered. Ctx is an optional pointer to a caller-defined context. The option name and the value are passed as strings to the callbacks. A missing feature for now are multi-line values, which we might need at some point for longer pieces of text like object descriptions. It's not fancy (actually it's almost the same as .desktop files on Linux or many other similar formats), but these files are both, easy to change on the go and human readable.

Lua scripting creatures

The addition of a scripting language is something I've been meaning to get to for a longer while, but everytime I've meant to get to it, I've found some other feature missing and wandered off. A couple of weeks back I finally got my hands dirty with Lua.

I had not used Lua earlier so it took a little bit of reading the reference manual and the Programming in Lua text found online to figure out how to use the thing. Lua is of course one of, if not
the
most popular embedded scripting language, so the process was fairly simple - if it wasn't, I doubt the language would have such a following.

The first thing I decided to apply scripting for were creatures. Here's how a creature script might look like at the moment:

local v = 0

function Init(creature)
   v = 0
end

function Tick(creature, delta)
   --creature_walk walks a creature in the given direction (0-7)
   creature_walk(creature, v);
   v = (v + 1) % 8
end

The Init() function is called when the script it attached to an entity. The Tick function is called every frame if it exists. As more use cases come up, more functions will probably be added. All of the functions will probably be optional, too, so if an entity doesn't require a script function call every frame, the Tick function for example could be omitted.

The MUTA Lua-side API will probably consist of mostly C functions bound to Lua. In the example above for example, creature_walk is a C function binding. Functions like these operate directly on raw entity pointers (Lua's 'user data' type). I don't know yet if that's a good idea, but it's certainly easy. One thing that must be kept in mind is that since our world will be divided into multiple 'worldd' processes, each simulating a part of the map, the scripts must be movable from one node to another and back again.

Defining scripts

All scripts to be loaded at server start-up must be defined in an ini file. Each line of the file defines the name and the file path of a script, for instance, "test = muta-data/server/scripts/test.lua". This associates an easy-to-remember string id with the script. The given name can then be used in game object definition files to refer to the script.

Script hot reloading

The nice thing about scripts is that you can modify them as the program is running. The way this functionality works in MUTA is that a character with the game master status calls a chat command with the script's name.

When a GM types in, '.reload_script test', a command is sent to every worldd process connected to the master server to reload the script 'test'.  Once the worldd's receive the command, they will look up the script, reload it's Lua code and re-register it's functions in Lua's own registry, then find every entity in the world using the script and reload it for them. That's quite nice for fast iteration!

Packetwriter improvements

Elsewhere, Lommi has been working on improving MUTA Packetwriter, the program we use to define and generate code for network packets. The program works so that every packet in the protocol is defined in a .mp file, which is passed as a parameter to the command-line packetwriter program. The program reads the file and forms the correct structs and inline functions for handling the defined packets, writing them into a C header file.  While the changes aren't upstream yet, there's a bunch of nice stuff incoming.

The improvements include packet definition formatting that isn't as strict as it used to be. It used to be that a white space or linebreak in the wrong place could break the generation but the program would not tell you want went wrong, instead just generating broken code.

You're also now allowed to do arithmetic when defining packet specific size limits.  This is a handy feature for packets that contain variable size elements such as strings.

Support for arrays of packed structs is also on it's way. This is a feature I've been wanting a lot because it's damn handy. For example when sending an account's character list, instead of being able to send an array of structs I've had to create an individual array for every parameter of every character. This has resulted in a lot of friction because populating multiple arrays is obviously more complex than populating a single array, and since our way of using packets is fairly low level, there have been bugs with uninitialized variables and such that took a while to debug.

Up next

I'm currently working on improving creatures. I'm not quite sure what exactly I'll be doing next, but it will be about making gameplay implementation easier, be it more Lua API functionality or actual game object interaction between the client and the server. Better get coding right now!