I'm pretty happy to announce a preview release of Lunatic, a Lua-based scripting system for EDuke32 and more. Lunatic has been in development since September 2011, being conceived in an IRC chat about how certain internal aspects of CON may be improved. But given the starting point of embedding LuaJIT into EDuke32 made these ideas outgrow in scope, so that pretty much every time I said something along the lines of "feature X is for the future", this was the reason. In fact, I alluded to Lunatic as far back as August 2011!
I'll return to a bit of history, but for now, onwards to the matter!
What the heck is Lunatic?
From a modder's perscpective, Lunatic is an interface to EDuke32 that allows pretty much what currently CON is for -- customizing actor behavior or changing the behavior at certain events -- but in a fashion that flows naturally with the Lua way of doing things. So, the emphasis is on redesign of these interfaces, not merely one-to-one duplication, which would be rather fruitless. For example, functions dealing with positions of game objects usually accept it as a single argument, expecting it to be indexable with "x", "y" and "z". This makes the code look somewhat more readable than passing the components separately.
From our (the EDuke32 developers') perspective, Lunatic significantly eases development of exposing EDuke32 features to scripting, but also adds new ways of extending EDuke32 itself. As an example, the Lunatic build comes with a new map format, or rather, two of them. As represented in memory, it has been extended with separate fields for the TROR members, so the collisions with the members these data were previously shoehorned into are gone. On disk, any map containing TROR is now saved in a completely new, text-based format ("map-text"; loading V9 maps is still supported). The genius of this is that external and internal representations are completely decoupled. We may add features to the map format and trivially keep backward compatibility, just by providing sensible default values for new members. Also, in maps of the new format, proper drawing of walls with non-power-of-two ysize tiles is enabled unconditionally. It was previously not possible to enable it because existing maps, among others the original Duke3D ones, relied on them being drawn as they are.
Modularity
Lunatic is conceived with modularity in mind from the start. A Lua file that is loaded into EDuke32 is effectively an encapsulated entity: game variables are defined per-module (and may even be invisible to the outside), so that there is no potential for name clashes among them. It may "require" other modules using a mechanism that is similar to Lua 5.1's system, but also allows arguments being passed to modules. So for instance, you could create badass actor code, but give the user of your module various customization options, or allow to set its tile number to a user-defined value. The Lunatic build of EDuke32 allows to load several root Lua files, enabling to write "mutators" that change only certain aspects of the game. (This is not unlike CON mutators loadable with -mx, but in effect, those are still merely textually appended to the main CON code.)
The focus on writing separate Lua files for independent functionality means that modders can adopt a workflow typically found in free software / open source projects. Instead of taking and adapting existing code for one's needs, a module always has an "upstream" maintainer, to which users can contribute features and bugfixes. Thus, improvements to that module propagate back to every user, and modders have more time caring about the artistic side of their mod. In the long run, it is desirable to have a repository of projects that are useful on their own, preferably backed by a revision control system.
About Lua
Lua is an extremely likeable dynamically typed, general purpose language and doesn't really need an introduction, having been used in various games such as The Witcher and a well-known MMORPG. It lends primarily to an imperative programming style, but borrows concepts from other paradigms, such as object-oriented or functional programming. One of its virtues is being light-weight: rather than prividing a full-blown array of features or data structures, it gives a few constructions with which many concepts may be realized if desired. An outstanding feature of Lua is that it comes with only one composite data type -- the so-called _table_, which is really an associative container type. In other words, tables can be indexed and can contain pretty much everything one wishes, and can be used to implement common data structures such as stacks or queues.
Lua uses a single numeric data type throughout, double-precision floating point. Thus, you have both the ability of writing clear code involving geometrical calculations, as well as the full range of a 32-bit signed integer (and more).
While Lua is the language visible to a modification author using Lunatic, under the hood EDuke32 uses LuaJIT, which is nothing short of Lua on a wild mix of steroids. LuaJIT is on one hand a tracing just-in-time compiler for the Lua language. This means that code (which is first present as bytecode getting interpreted) which is run frequently and contributes to a significant portion of the run time ("hot code") is compiled to _native machine code_ on-the-fly. But beyond that, LuaJIT gives us (the devs) a foreign function interface (FFI) to access data and call functions on the C side from Lua, which is the primary reason for the aforementioned easing of the scripting system development.
LunaCON -- a reworked CON implementation
Lunatic completely reimplements the CON run-time as well as translation facilities. It parses CON with a Lua module written completely from scratch that uses the invaluable LPeg library. All CON code is translated to Lua code that could in principle be run like a stand-alone Lunatic module, with the exception that it uses private interfaces, and for this and other reasons must not be used as a base for new Lunatic code.
The main aim of LunaCON (the name given collectively to the CON to Lua translator as well as the underlying facilities) is correctness, in a broad sense. First, because the translator has a clear syntactic model of the CON language instead of being processed by mostly hand-written C code, it lets only well-formed CON code pass: glitches such as the parser wrongly accepting an unfinished directive at the end of a file (like "gamevar 0") are a thing of the past. It is delightfully strict in the semantics department, too; many constructs that would previously pass but have no sensible meaning (such as using getactorvar on a per-player gamevar) give errors. Occasionally, (unintentional) misuse of the CON system was found out to be too common when testing various released mods. For these cases, compatibility switches have been added that e.g. turn errors into warnings, and generating code that may or may not make sense.
On the run-time side, since the LunaCON implementation is completely unrelated to C-CON, one can expect many quirks[1] of the latter to be non-existent. In some places, the syntax or behavior may be augmented: for instance, LunaCON permits nested array expressions and allows redefinequote to be issued with a previously unallocated quote number (one for which no definequote was given).
[1] See for example: http://forums.duke4....post__p__143260 and http://forums.duke4....post__p__148019 . [The forums didn't render these correctly as hyperlink.]
Compatibility with existing mods
The LunaCON system would be pointless if it wasn't able to run a good portion of CON code found "in the wild". But while compatibility is one of the main aims, the emphasis on correctness means that LunaCON is also unfogiving with errors in the CON code -- whenever Lunatic encounters an erroneous condition (such as an attempt to access an array beyond its bounds), it will display an error message persistently on the screen; furthermore the code following the error will not be executed. This means that mods and TCs will need to undergo reviews and the respective bugs fixed -- making the code much more cleaner in the process, a most desired and undervalued thing!
Documentation
An essential part of every software project is accurate and useful documentation. One of its main purposes is to set a scope of features that users can rely on -- namely everything described in it. By implication, this means that everything the documentation does not mention must be considered nonexistent when assuming the role of a user. This is really the only way to achieve robust, future-proof software.
Lunatic comes with documentation of Application User Interface (API) as well as an overview of LunaCON. The goal of the former is an accurate description of everything that the system provides. But as so often, it seriously lags behind the development. It is critical (and I probably sound like a broken CD player already) that only that which is written down in the documentation is assumed to be ready for use.[2] Accuracy means that the important implications of using an interface should be inferrable from reading its documentation, as opposed to resorting to trial and error. Many parts should be familiar -- after all, in a sense the same cake (EDuke32) wrapped in a different icing.
Currently, the documentation can be downloaded in HTML form at the end this post.
[2]For example, there's no documentation on player[].visibility yet, although you might find out that a member with such a name exists. But maybe I'll want to rename it to "visfactor" for easier grepping, and because it has different semantics (the see-to-infinity value is 0, not 240).
Undocumented features
Besides the lack of documentation for features that expose game functionality, some helper modules have not yet been described either. This is partially due to uncertainty whether they're very useful, although sometimes they have been inspired by forum discussion; I'd be glad to hear any feedback. They are the following:
- A module implementing objects that return pseudo-random numbers using a generator with better statistical properties than the engine's krand(). They can be initialized either to a fixed initial state (for reproducibility) or to a pseudo-random one by using the CRC32 of values returned from the high-precision timer.
- A module that effectively provides arrays of boolean values with a storage requirement of one bit per value (plus constant overhead).
- A module implementing objects collecting simple running statistics, largely useful for profiling. "Running" means that the statistics (mean, variance) are updated each time a new value is added, without the need to store all values.
Preview version and compatibility considerations
This release is a preview release. As such, it is not targeted toward the general audience, but rather at modders wishing to get familiar with Lunatic or people interested in the development of EDuke32 itself. Currently, Lunatic does not have the breadth of interface features that could replace CON, although this is the planned direction. The decision to release a not fully functional version has various reasons.
First, everyone interested can start getting aquainted with Lua and the interfaces provided by Lunatic right now. Some features like setting actor behavior properties can be considered finalized. Others are implemented but lack documentation (forbidding any careful programmer to use them). Many features or interface exposures have not even been started, but that can be seen from the positive side: everyone is welcome to contribute ideas. The goal is nothing less than to have the best possible interface to EDuke32 features, be it engine, sound, video or game ones. But good interfaces have to be justified with actual use cases, which is where your feedback is valued.
But for the time being, I suggest the primary focus be the maintenance of existing and work-in-progress CON modifications. You'd be surprised at how many things can go wrong if you take them under close scrutiny. I have tested various CON mods of mainly modern times while developing LunaCON, and many have accumulated patches that fix translation with LunaCON. Sometimes, but not to the same extent, I also played these mods to find out what kind of errors they contain (but also to fix bugs in LunaCON and drive its development forward, of course). If you're a mod author, feel free to request these patches -- I'll give them away without asking. But if you want to get the most information gain, you might consider trying fixing the issues on your own (and discussing it on the board, which is highly encouraged).
A good point to start would be to play around with the stand-alone LunaCON translator. I have compiled a Windows version of LuaJIT and LPeg, linked from my signature, that can be used with the "lunacon.lua" script obtainable from the EDuke32 SVN repository. Further directions are given in the LunaCON documentation.
As yet, there is no synthesis build of Lunatic, but brief instructions for compiling one can be found in source/lunatic/doc/how_to_build_lunatic.txt in the EDuke32 SVN repository.
A bit of history and acknowlegdements
The idea to use LuaJIT was put forward by Plagman in an IRC session. I'm sure he'll chime in to tell about his point of view. One of his visions was that development using the scripting system should be more like programming a Build game from scratch, rather than bending into shape hard-coded behavior to achieve one's goals. It's an ideal I agree with, but realistically, it's a huge undertaking to "exorcise" the hard-wired code to the Lua side. In that IRC session, Plagman figuratively described adding more features to CON as "putting lipstick on a dead horse", and this basically was the igniting point for Lunatic -- I simply couldn't agree more.
The idea to have a textual format for map representation is due to TerminX. He got through my thick skull that this does not exclude having an in-memory representation. I was initially pretty confused about that point.
A person I'm unsure wants to be called by name contacted me in the course of Lunatic development and was eager to send patches. However, I had to reject those because I had a different direction in mind. So, contribution is welcome, but please coordinate it with me.
Finally, the Lunatic project would not have been possible without the work of various people on the systems that it builds on.
- The Lua maintainers at PUC-Rio deserve credit for creating, and importantly, keeping alive and evolving, a very well-thought out language.
- Without LuaJIT, Lunatic could not have been implemented in such a convenient way. Mike Pall, the author and maintainer of LuaJIT, has engineered an extraordinary piece of software. He was also very responsive with bug reports.
- The LPeg module for Lua, by Roberto Ierusalimschy, made it possible to write a CON parser in a natural way. While it's thinkable to cope with such a task using Lua's string functionality, the result would have been rather ad hoc.
Closing remarks
This brief introduction gave an overview of what Lunatic will have to offer. In a fairy world, the limit would be imagination. Realistically, it is time. I can't say for sure when it will reach a state that could be called "release", but I hope that your feedback will guide its development to the right direction. So, take a look and discuss!
UPDATE 2012-08-23: The documentation is now available at http://lunatic.eduke32.com/ .