|[ LiB ]|
Now you can join the game, but you can't perform any actions; there are no command scripts! Well, that is remedied easily enough.
All commands in the BetterMUD inherit from a base Command class, which provides a few functions to make your life a little easier. Here's the class, which can be found in /data/ commands/PythonCommand.py:
class Command( data.bettermudscript.bettermudscript ): # Usage def Usage( self ): return self.usage # description def Description( self ): return self.description # the standard call method. def Execute( self, args ): try: self.Run( args ) except UsageError: me = BetterMUD.character( self.me ) me.DoAction( "error", 0, 0, 0, 0, "Usage: " + self.Usage() ) except TargetError, e: me = BetterMUD.character( self.me ) me.DoAction( "error", 0, 0, 0, 0, "Cannot find: " + e.value )
In addition to names , commands have usage and description strings (as you saw in Chapter 14). So when asked, the Command class returns usage and description strings (they don't exist in this class, but if you inherit from Command class and define strings on your own, the functions still work).
The Execute function is called from C++ whenever you want to execute a command in the game. To make things easier, I've inserted a try/catch block into the code, and it looks for UsageError and TargetError exceptions.
The Execute function tries calling Run with the arguments given, and if that throws a UsageError exception, the command gets an accessor to your character, and prints out an error, telling you how to use the command. This is for cases in which you type go without specifying where you would like to go, or something similar.
Since so many commands depend on finding an item to act on ( get <item> , attack <charac- ter> , and so on), it's fairly common that the designated targets cannot be found. Rather than hardcode You cannot find: <blah> into each and every command, I've enabled commands to throw a TargetError exception if you try to operate on a target that doesn't exist.
To make things even easier, I've made a special FindTarget function that attempts to find a target contained by an entity and returns the ID if it's found, or throws a TargetError if it can't be found. Here's the code:
def FindTarget( seekf, validf, getf, name ): seekf( name ) if not validf(): raise TargetError( name ) return getf()
The first three parameters are functions; seekf is a function that searches for an entity, validf checks if the result of the seek was valid, and getf returns the ID of the item that seekf found. This might seem confusing at first, so let me show you how to use it:
me = BetterMUD.character( 10 ) item = FindTarget( me.SeekItem, me.IsValidItem, me.CurrentItem, "sword" )
This code first gets a character accessor, pointing to character 10 (whoever that may be), and then gets the ID of an item with the name sword . If there is no item named sword , an exception is thrown, and this code doesn't bother catching it. Essentially, when passed into FindTarget, the code is transformed into the following snippet:
me.SeekItem( "sword" ) if not me.IsValidItem(): raise TargetError( "sword" ) item = me.CurrentItem()
You can easily perform the same trick on a room, when searching for either items or characters within that room, or a region, or whatever else you may need!
# assume r is a room, reg is a region i = FindTarget( r.SeekItem, r.IsValidItem, r.CurrentItem, "sword" ) c = FindTarget( r.SeekCharacter, r.IsValidCharacter, r.CurrentCharacter, "mithrandir" ) j = FindTarget( reg.SeekItem, reg.IsValidItem, reg.CurrentItem, "pie" )
After those lines are successfully executed, i has the ID of the first item that matched sword inside the room, c has the ID of the first person with the name mithrandir , and j has the ID of the first item in the region named pie . If any of those fail, an exception is thrown, and it's up to someone else to handle it.
Once you enter the game, you really can't do anything but use the built-in C++ commands that I've given you, so it's time to add some Python commands.
At this point, the only movement command available to you is the go command. You type go north or go south if you want to go anywhere , and that can be annoying, so as a simple test of commands, I've made additional directional commands. Here's one of them:
class north( PythonCommand.Command ): name = "north" usage = "\"north\"" description = "Attempts to move north" def Run( self, args ): c = BetterMUD.character( self.me ) self.mud.DoAction( "command", c.ID(), 0, 0, 0, "/go north" )
This is the north command, which acts as an alias to go north . You can see that the name, usage, and description are all defined first, and then the Run function grabs an accessor to your character, and commands him to /go north .
Pretty cool, huh? I've included commands for all the common directions: north, south, east, west, up, down, northeast, northwest, southeast , and southwest. I've even included aliases of aliases , which are nw , ne , se , and sw . Here's an example:
class ne( northeast ): name = "ne" usage = "\"ne\""
This simply inherits from class northeast , and redefines its name and usage. This class was created so that you can simply type ne instead of northeast to move northeast in the game.
In this particular case, you can't rely on partial matching to match ne with northeast , because the partial string ne doesn't exist in northeast . If you typed no , the game would think you're going north, and not northeast; the smallest string you could type to make the code think you want to go northeast is northe , which isn't exactly a shortcut. So, to fix this, I just created a brand new command named ne .
Now that you can freely move around, it's a good idea to create commands that modify the physical world. For this, I've implemented the get , drop and give command objects. Here's the get command:
class get( PythonCommand.Command ): name = "get" usage = "\"get <quantity> <item>\"" description = "This makes your character attempt to pick up an item" def Run( self, args ): if not args: raise PythonCommand.UsageError me = BetterMUD.character( self.me ) r = BetterMUD.room( me.Room() )
The code if not args checks to see if any arguments were passed into the function; if not, the function raises a UsageError , which causes the command to print out Usage: get <quan- tity> <item> to the player. The game doesn't know how to get items if you don't give them a name. The game retrieves accessors to your character ( me ), and to the room ( r ).
The usage string for this command is get <quantity> <item> . The bar in front of quantity means that it's an optional argument; it applies only to quantity items. If there's a sword on the ground, a player could type get sword , or if there is a pile of coins , he could type get 10 coins . If a player wants to get the entire pile (he's greedy!) he would type get coins . The next code segment tries to figure out if a player is trying to get a quantity of items or not:
quantity = 0 item = args if string.digits.find( args ) != -1:
The string.digits string is a special built-in string in Python which contains the characters 0123456789. I search that string to see if the first character is a digit.
If a player is getting a quantity, the game extracts that quantity from the arguments using the split function. I've designed the split function so that it splits the string into a list of two strings, one containing the first word, and the other containing the rest of the string:
# first letter is a digit, so get quantity split = args.split( None, 1 )
So args was 10 gold coins , split will be 10 , and split will be gold coins . The next part converts the quantity into an integer:
try: quantity = int( split ) item = split except: # do nothing pass
This could fail, however. If you try converting something like 1blah into an integer, an exception is thrown. If that happens, the function catches the exception, and just nixes the idea of getting a quantity; quantity is left at 0, and item is left as it was.
If the conversion was successful, the function tries to find the item:
i = BetterMUD.item( FindTarget( r.SeekItem, r.IsValidItem, r.CurrentItem, item ) )
If the item is valid, an accessor to the item is retrieved; I need to do a little work on it however. If the quantity value is 0, yet the item in question is an actual quantity object, you want the function to get the entire quantity. So that's what it does:
if i.IsQuantity() and quantity == 0: quantity = i.GetQuantity() self.mud.DoAction( "attemptgetitem", me.ID(), r.CurrentItem(), quantity, 0, "" )
Finally, the item is retrieved, or an error is printed if the item wasn't found.
Dropping an item is almost identical; you need only search the character's inventory for the item to drop (instead of searching the room for an item), and tell the game that you dropped an item.
Giving an item away is slightly more complex, but that's because it must find a player to deliver an item, and then find the item to give to that player. Overall, the code isn't that much different from either getting or dropping an item, so I'm not going to show it here.
All three of these command modules can be found in the /data/commands/ usercommands.py script file.
|[ LiB ]|