Lua for scriptable stuff?

Topics: Code
Coordinator
Feb 3, 2008 at 8:09 PM
I was researching for a way to do MUD commands like we did in the VB version. I just ran across these:

http://www.gamedev.net/reference/articles/article2275.asp
http://luaforge.net/projects/luainterface/

It looks a lot less complicated than what we were trying to do earlier. I wanted to put this info somewhere where we can access it later.
Feb 4, 2008 at 8:51 AM
Using lua was brought up before by someone, I can't remember who.

I dont think its a bad idea at all, if it adds value. ie allows easy recompilation of commands, but also fits in with our framework ie reactions in the framework while not part of the command system still place items of work onto our command processor for execution. If lua can do the same then sure, why not.

The alternative is of course to sort out some form of recompilation system for our current approach to allow commands to be added and recompiled at run time. We know this is doable of course, but I don't want to lose the debugging support we currently enjoy.
Coordinator
Feb 5, 2008 at 2:51 AM
I looked at CS-Script today. It does a lot of the same stuff we were doing in the VB version of WM. This system does have a better integration with debuggers, so that part is covered. It seems that the author just added support for actually embedding his system into another application. I think I want to explore this further, instead of trying a whole different language.
  • Debugger support
  • Optional loading assemblies into different AppDomain (need to inherit from MarshallByObjectRef though)
  • Multiple .NET language support
  • Caches compiled scripts
  • Open sourced
I think this is one of those things where we don't have to re-invent the wheel (pun intended).

http://www.members.optusnet.com.au/~olegshilo/index.html

I want also explore the ability to add a custom attribute that will contain name, aliases, and description. I want this incorporated right into the script, so that we don't need the database to persist this info. This would have the side effect of being able to drop a file in directory and it could be auto-processed by whatever we setup.
Feb 5, 2008 at 9:33 PM
Edited Feb 5, 2008 at 9:35 PM
Yeah, lets investigate this further, I do like the sound of it.

I was a little bit nervous about the attributes thing with our current system as there would be no way to change an alias for a command until recompile, but with a scripting system, that problem goes away which is nice :)

If it is a succesful integration, what do you see been written in the scripts? Do we stop at commands or is the mob ai moved there, perhaps reactions, and rooms even?
Coordinator
Feb 5, 2008 at 9:51 PM

foxedup wrote:
Yeah, lets investigate this further, I do like the sound of it.

Avast, forward hoooooooooo!


foxedup wrote:
If it is a succesful integration, what do you see been written in the scripts? Do we stop at commands or is the mob ai moved there, perhaps reactions, and rooms even?

I say just throw anything at the scripting engine, within reason that is. We could have one directory for all the scripts, then have different attributes to tells us where the script belongs, ie MobBrainScriptAttribute, CommandScriptAttribute, ReactionScriptAttribute, RoomScriptAttribute, etc. I really like the idea of rooms being scripts. Honestly, I'm still undecided as to what is best: rooms in db or in file/script.
Feb 18, 2008 at 4:16 AM
I would actually recommend IronPython instead. That LUA interface that you're referencing is known to have some issues, and may not run on Mono very well. The only reason I mention it is because that was the first thing I headed towards too, on my own project, and switched to IronPython - without looking back. It's a cinch to integrate, too.
Feb 18, 2008 at 4:16 AM
Edited Feb 18, 2008 at 4:43 AM
I guess i'll use the double-posted post for an actual example:

Here is the setup of the scripting engine:
static ScriptCore()
        {
            engine = new PythonEngine();
            compiler = new Microsoft.CSharp.CSharpCodeProvider();            
            parms = new CompilerParameters();
            // Configure parameters
            parms.GenerateExecutable = false;
            parms.GenerateInMemory = true;
            parms.IncludeDebugInformation = false;
            parms.ReferencedAssemblies.Add("System.dll");
            parms.ReferencedAssemblies.Add("" + System.Windows.Forms.Application.StartupPath + @"\Heimdall.dll");
            
 
            clr = (ClrModule)engine.Import("clr");
            clr.AddReferenceByPartialName("Heimdall");            
            engine.Import("Heimdall.Core");
            engine.Import("Heimdall.Entities");                            
        }

You can see me import two namespaces, containing most of my "Utility" functions. Theres some stuff in the beginning where I setup a C# compiler, but that a separate branch of the script-core used to hot-fix skills and spells by recompiling a file and replacing the existing handler. The python stuff takes four lines, the ones at the very end and the first one where I declare engine.

The following is called when a script is triggered from the item, via a trigger. There are other triggers in the code, such as if a weapon has a Combat trigger the script is loaded as part of the attack roll etc. Anyway, you can see me passing the script parser the Player, Item object and the trigger (in other words, what they typed).

caller.ACS is a numeric entry in my objects that is set to point to a script, so essentially i'm getting the path to the Python script and then calling it.
public static void FromItem(Player originator, Item caller, string trigger)
        {            
            string script = Path + caller.Acs.ToString() + ".py";
            if (File.Exists(script))
            {
                Dictionary<string, object> locals = new Dictionary<string, object>();
                locals.Add("Caller", originator);
                locals.Add("Artifact", caller);
                locals.Add("Trigger", trigger);
                try
                {
                    engine.ExecuteFile(script, engine.DefaultModule, locals);
                }
                catch (PythonSyntaxErrorException e)
                {
                    string error = "" +
                        "Syntax Error:\n" +
                        "Line#" + e.Line.ToString() + ": " + e.LineText + "\n" +
                        "Message: " + e.Message.ToString();
                    originator.SendSys(error);
                }
            }
            else
            {
                originator.SendSys("You feel an uncomfortable pressure and you know something's gone wrong.");
            }
        }

Here are two example scripts, the first one does a healing potion. I added this as a test, since I had no magic system at the time. I'm kind of embarrassed to show this, but anything for an example :P You can see me constantly multiplying by negative one because I'm actually using the "damage" function so I have to cause negative damage. CHA is charges, so we check it for charges, and delete it if it was the last charge.
if Artifact.Cha > 0:    
    healFor = Core.GCore.Dice.RandomRange(20, 35) * -1
    Artifact.Cha = Artifact.Cha - 1
    if Caller.Hit == 0: Caller.Hit += 1
    actualHeal = Caller.Damage(healFor, 0)
    Caller.SendLine("You recover " + (actualHeal[1] * -1).ToString() + " health!")
    Caller.Update("h")
    if Artifact.Cha == 0:
        Caller.Inventory.Remove(Artifact)
        Artifact.Delete()
        Caller.SendLine("" + Artifact.PName + " is all used up.")
    else:
        Caller.SendLine("You estimate you can drink from this " + Artifact.Cha.ToString() + " more times.")        
else:
    Caller.SendLine("" + Artifact.PName + " doesn't have any charges left")   

The next one I wrote as an example to my staff. I should also mentioned that I picked Python because the syntax is not too difficult, and theres less "curly bracket stuff" which makes it less intimidating to the non-technical. It is an exercise in tag usage to imbue a staff with an extra, cooldown, effect that doesn't rely on the player's abilities or timers.
#ID: Dovim
#Description: AOE Staff.
#You should probably first check the cooldown timer tag here before you do any lengthy checking for charges
#Tag lines are commented out til the feature is fixed.
if Artifact.HasTag("#dovim_staff"):
    Caller.SendLine("The staff isn't ready yet!")
else:
if Artifact.Cha > 0:    
	# You need monsters to damage first. result = monster.Damage(30-50)
	Artifact.Cha = Artifact.Cha - 1
	msgToCaller = "The runes on the staff glow bright blue as lightning strikes out at your opponents!"
	#To fill in the %s you need to put %s (substitute) at the end of the line like I did.
	msgToRoom = "The runes on %s's staff glow bright blue as lightning strikes out at %s opponents!" % (Caller.Nam, Caller.ProPoss)
	#sending messages
	Caller.SendLine(msgToCaller)
	Caller.cRoom.Say(msgToRoom, Caller)
	#Do your FOR loop here, calling Caller.cRoom.Mob
	for monster in Caller.cRoom.Mobs.ToArray():
		result = monster.Damage(Core.GCore.Dice.RandomRange(30, 50))
		#Add the hate.
		monster.Hate.Add(Caller, result[1]) 
		#Check for death.
		if result[0]: monster.Die(Caller)		
	if Artifact.Cha == 0:
		Caller.SendLine("" + Artifact.PName + " is all used up.")
	else:
		Caller.SendLine("You estimate this staff can be used " + Artifact.Cha.ToString() + " more times.") 
		Caller.SendLine("You estimate that the staff will require a minute before it can be used again.")
		Artifact.AddTag("#dovim_staff", 0, "", 60)
else:
	Caller.SendLine("" + Artifact.PName + " doesn't have any charges left")    

Anyway, just about everything except for the IF\Else\FOR constructs is API from my own engine, so thats why it may not make sense. If you combine the scripting engine with some kind of Tagging for your objects, blocks, theres no telling what all you can make your scripts do in terms of Quests and AI, etc.
Coordinator
Feb 20, 2008 at 3:51 AM
Hi Vassi!

Sorry I didn't answer sooner. Been sick with a flu-like virus.

Thanks for sharing! We had a working scripting system back when our codebase was VB.NET. It used the CodeProviders as in your first code segment. It was pretty neat in that it let us unload the in-memory assembly without having to unload the whole AppDomain. We only got as far as using it for MUD commands. I'm trying to be a bit more ambitious this time around. I want to have more MUD systems that are scriptable. Foxy and I are taking a more methodological approach this time around.

I want to stick with just one language for everything for now. Having a multitude of languages is exactly one of pet peeves with web programming. :p