luad20 - a Lua Schema for d20 gaming systems - design
I'm not going to start the planning and design from scratch, having the
body of xmld20 to work from. Instead, we're going to dive right in and
start converting xmld20 to what we need.
A few things attracted me to using Lua for this project. One was the
need for a scripting language, which was at first (un)satisfied by
XSLT; then by a short attempt at Xscr,
a scripting language in XML; and last, an aborted attempt to move to PHP.
Lua provides the simple syntax that made PHP appealing, and also provides
inherent embeddability into other applications, allowing Luad20 to be a
library of d20 manipulation routines that can be used by any external
application, regardless of their own language of choice.
Additionally, Lua has a few pieces of "syntactic sugar", which make for
nicer-read code. One of them in particular, however, was the deciding
factor in using Lua for the d20 project. Lua allows you to pass a table into a function in the following way:
Feat{name="Acrobatic",type="General",benefit="jump=jump+2; tumble=tumble+2"}
This is really shorthand for calling the Feat() function with the table, but if we lay it
out a little nicer, we get something that I think a non-programmer
might feel comfortable with:
Feat{
name="Acrobatic",
type="General",
benefit="jump=jump+2; tumble=tumble+2"
}
There is a lot of glossing-over of what happens with this simple
command, which we'll take a look at.
Lua tricks
The above looks very much like a data file that might be describing
the feat. This is what attracted me to Lua. Given the parameter
names required (name, type, benefit) and optional (prerequisities,
special), anyone should be able to add a feat to their d20 system
with this format without knowing any programming.
The Feat() function can be thought of a DefineFeat() function, in that it loads a feat into
the game system for use by character sheets, items, etc. Because this
is the visible side of the system, I figure we'll keep out the
techno-babble of "DefineFeat" and just let it look like you're
providing a list.
The curly-braces, instead of the parenthesis, are the syntactic sugar
for passing in a table. In this case, we're defining the name of our
feat, its type (for sorting, searching, etc.), and what to do when
a creature has this feat.
Another nice feature of the definition being code, and not just code,
but a function call, is that the feat is automatically being passed
through some error-checking when it's being loaded: the Feat() function can check if the name is already
used, and if so, ask the user what to do (or just inform the user that
this new definition is being used/ignored); if the name or type is
missing, a message can pop up saying so, asking for one, or the entry
gets ignored; if the type isn't known, a warning might pop up (but it
continues loading anyway); if the benefit is missing,
well... something's wrong.
The type could also be multiple things, perhaps, with [Fighter] and [Metamagic] (no, it doesn't really make sense, but as an example), in which case, we can't have
...
type="General",
type="Metamagic",
...
but we can have this instead:
...
type={"General","Metamagic"},
...
We could require that types are always in their own table, but Lua is
nice and flexible in that we can allow either just a string, or the
table with strings, and our Feat() function
can check for and handle both. In fact, we might decide to handle it
without the quotes, to make it even easier for non-programmers to use:
...
type={General,Metamagic},
...
This can be done if we have variables named General and Metamagic as
constants in our environment.
The benefit entry is interesting. It's just a string, but this is no
problem for us... Lua has a loadstring()
function which takes code in exactly this form and returns a function,
which is what we'd probably want. But this way, we don't require the
user to know how to write benefit=function()
... end. Even better, we can get rid of the need for quotation
marks by using an alternate notation in Lua:
Feat{
name="Acrobatic",
type="General",
benefit=
[[
jump=jump+2
tumble=tumble+2
]]
}
This even got rid of the need for semi-colons, which we don't need to
impose on the user (no, they weren't needed before either, but made
for more readable code.) It's unfortunate that the body of the benefit has
to be so code-like, but we have to draw the line somewhere, unless we
want to start parsing it as data. As of now, the above is legitimate
Lua code, and automatically does what we want it to (well, once we
write the Feat() function of course).
Probably the best part of this system (and supporting scripting over
data in general), is that we don't have to pre-plan the logic that is
required to support future additions. I went through this a bit in my
design sections for xmld20, but I'll re-iterate here: the fact that
the benefit field can be any arbitrary Lua code means that if someone
knows enough programming, they could have a feat that does something
that me, as the developer, didn't plan for, or couldn't imagine
needing. I don't need to support a DoublesCharacterHeight=yes feature, just in case
it's needed, but it can still be done. All that's required is a
schema of the values that are available for
modification.
©2002-2010 Wayne Pearson