Monday, August 31, 2009

Patterns in programming

Just managed to sneak in ONE post for August...

It's not that I haven't been playing with Metaplace lately (though not nearly as often as I'd like), but a lot of the stuff I've been doing hasn't been groundbreaking or, really, that interesting. Not in its current stage, anyway.

Ever since I added sounds to my Ultima Online world, I've been drawn back to getting that large project going again, happily ignoring the fact that custom avatars are the bane of my Metaplace existence.

While the footstep sounds were interesting, the most memorable sounds in UO were the spells, so I decided to focus on that subsystem next. As is typical of my programming history, I'm much better off at the behind-the-scenes coding than the user interface portion, and since UO spells (more specifically, the spellbooks) can require a bit of UI that I shudder at creating (and I won't even bring up UiXml()... well, except there), I went to work on the back-end portion of spellcasting.


One of the most common things I do in Metaplace, whether it's designing a new module, a new system, or just testing out new functionality, is to write a command-line interface to the code I'm developing. This is useful for testing things that would otherwise require buttons and sliders and textboxes, but don't have them yet, and for quickly trying different values in different situations. This is something I do so often, in fact, that I've got a routine when I first create a script, that sets me up for development. For instance, when I decided I was going to work on spellcasting in UO (specifically, Magery, which is actually a skill...) I created a script called "magery", and right off the bat, wrote the following:


Define Properties()
magery={}
end

Define Commands()
MakeCommand("magery", "magery interface", "cmd:sentence")
end

Command magery(cmd)
local params=string.gmatch(cmd,"%S+")
local subcommand=params()

end


This sets me up for a few things: it gives me some local storage for anything I create during testing, such as window IDs, sound IDs, state variables, etc. all within the self.magery table. It also gives me a quick way to pop up the command-line and start sending commands to my code, taking advantage of the handy "sentence" type in Metaplace. In case you've not seen it, it lets you type, say


magery cast gate travel


And it'll take everything after the command name and pass it as a single string, spaces and all. This allows for parameters with spaces (such as "gate travel" above), and for sub-commands that have varying or variable numbers of parameters.
The little bit of code at the start of the magery Command helps me tokenize the subcommand and the parameters.


After throwing together a little bit of magic in my UO world, I decided that I should really add in skill support (because, as I mentioned, Magery is technically a skill that you use), so I made a script called "skills" and started it with


Define Properties()
skill={}
end

Define Commands()
MakeCommand("skill", "skill interface", "cmd:sentence")
end

Command skill(cmd)
local params=string.gmatch(cmd,"%S+")
local subcommand=params()
local skillname=params()

end


Almost the same as before. Then I could quickly add


if subcommand=="use" then
SendTo(self,"use_"..skillname,0,params(),params(),params(),params(),params())
elseif subcommand=="set" then
self.skill[skillname]=params()
else
AlertToUser(self,2000,"Unknown skill command: "..subcommand)
end


Now, admittedly, I kind of lose some of the nicety of the sentence type by coding my skillnames without spaces (AnimalTaming instead of Animal Taming). And the ugly bit with the repeated calls to params(), to pull off each of the extra parameters (if they exist), works because the function returned by string.gmatch() will just continue to return nils once it's done, and sending extra nils through to the Trigger is harmless (provided the Trigger didn't expect anything there, of course). I could spend the time and write code that says, "if the skillname is Animal Taming, then there's just one extra parameter, the target, so I'll only pass one extra params(); but if it's Provocation, then there are two...", but this lets me change the rule in their own individual handler functions, making for very rapid prototyping.


The other day I got distracted from UO development by a conversation with LunarRaid, which made me want to try out implementing drag-and-drop functionality using the UiEvent() system. Replace the "magery" with "dndui" and you have my starting block of code, ready to change settings, pop up windows, or whatever else I might want to change while testing. The nice thing about this setup is that many UI elements call Commands when pressed or used, and thus the same single interface can be used by them. Also, I tend to have all of the conditional code in the Command just fire Triggers to self so other code I write can easily duplicate, with a SendTo(), the functionality that I've been testing from the command-line.

For just testing concepts, or to avoid fiddling with buttons, this is a great way to just get coding. If I didn't have a nice way of quickly prototyping the code I come up with, I'd probably still be fiddling with a spellbook interface and have nothing but a few sprites to show for it.