[ LiB ]

Previously, I explained the idea of commands, which are scriptable objects that can be given to players to enable them to communicate with other players and with the game.

Perhaps the best aspect of commands is that they can be given to people on a per-person basis, and this is probably a good place to have a rewards system. Imagine this scenario: A character spends a lot of time in woods and forests, so having the ability to find herbs and turn them into elixirs would be useful. But the character doesn't know how to do that; the BetterMUD has no way of saying "convert this object into that."

Perhaps, as a reward for completing a quest or by paying for schooling, the character can be given the ability to issue a makeelixer command. Then, whenever the character invoked that command on herb-items in his inventory, the command script would destroy the herbs and generate a new elixir in his inventory.

That's how commands work; they're simple scripts that can be executed by characters .

The Command class is very simple:

 class Command : public Script { public:     virtual void Execute( const std::string& p_parameters ) = 0;     virtual std::string Usage() = 0;     virtual std::string Description() = 0; };  // end class Command 

The command class has three functions: a player can execute the command, obtain its "usage," and can get its description.

In the game, whenever a player types /blah blarg , the game interprets that as a command. The game tries to find a command named blah , and executes it with a parameter's value of blarg .

The usage and the description of a command are just strings that describe the syntax of the command and describe what it accomplishes. Usage and description help the player figure out what to do.


It is important to note that all com mands must be given the ID of the character that they belong to, so that when the commands are executed, they know who executed them. You will see how this is done when new command objects are created.

The Commands class can be found in the directory /BetterMUD/scripts in the files Command.h and .cpp. Like logic scripts, Commands is an abstract class that can be implemented in either Python or C++.

C++ Commands

Before I had the Python engine up and running, I had a few C++ commands running as a test case, which means I implemented the Command module class in C++. You can find this class in the same directory as the Command class, in the file CPPCommand.h.

Here is the implementation of the C++ version of the class:

 class CPPCommand : public Command { public:     CPPCommand( entityid p_character, std::string p_name,         const char* p_usage, const char* p_description );     std::string Name()          { return m_name; }     std::string Usage()         { return m_usage; }     std::string Description()   { return m_description; } protected:     std::string m_name;     const char* m_usage;     const char* m_description;     character m_character; };  // end class CPPCommand 

The class is a simple one; it contains one string and two const char* pointers. Since many instances of the individual command classes have the same nonchanging description and usage strings, it doesn't make sense to store the actual string data on a per-instance basis, so I'd rather have pointers to the strings instead.

This class doesn't implement the Execute command, but lets classes that inherit from this class implement it instead.

C++ Commands Implemented

I've implemented a few commands in C++. These commands are located in the file CPPCommands.h (note the "s" at the end of the file name and that the base CPPCommand class is in a different file, CPPCommand.h). For example, the first command I implemented was the Quit command:

 class CPPCommandQuit : public CPPCommand { public:     CPPCommandQuit( entityid p_character ) :         CPPCommand( p_character, "quit", "\"quit\"",         "This removes your character from the game and takes you"         " back to the Game Menu." ) {}     void Execute( const std::string& p_parameters ) {         m_character.DoAction( Action( "leave", m_character.ID() ) );     } };  // end class CPPCommandQuit 

This defines the Quit command. When you give this command to a player, he can type /quit and the game logs him out. As you can see from the Execute function, this command simply tells the game that a player is leaving the realm. That's all there is to it!

Here's another example:

 class CPPCommandChat : public CPPCommand { public:     CPPCommandChat( entityid p_character ) :         CPPCommand( p_character, "chat", "\"chat <message>\"",         "This sends a message to every player who is currently "         "logged into the game." ) {}     void Execute( const std::string& p_parameters ) {         if( p_parameters.size() == 0 ) {             m_character.DoAction( "error", 0, 0, 0, 0, "Usage: " + Usage() );             return;         }         g_game.AddActionAbsolute(             0, "chat", m_character.ID(), 0, 0, 0, p_parameters );     } };  // end class CPPCommandChat 

This one is slightly more complex. It checks to see if the user supplied the appropriate parameters (that is, any text after "chat"), and if there is no parameter, the game prints an error to the character who tried to chat.

Otherwise, the game is told about the chat action, using the AddActionAbsolute command, which you'll learn about in the next chapter. For now, all you need to know is that the chat action adds a command to the game, and that the command is executed as soon as the game can get to it. (The first parameter is the time to execute the command, and zero means right away.)

There are other commands in the filecommands that make players say things, reload scripts, move around the map, look at a room, kick other players, and set quiet-mode, which is a feature I will get to in the next chapter when I show you how to execute commands.

I'm leaving these commands in the game as sort of a "legacy," so you can get an idea of how you can implement logic modules in C++. In Chapter 18, however, I'll show you how to create commands in Python, and I hope you'll agree that Python is preferable to C++. The primary reason, of course, is the fact that Python commands are reloadable at run-time, which means that you can add stuff to existing commands if you need to.

Command Database

I took the liberty of creating a class that will manage all commands for you. Since the database is closely linked with Python, you won't be able to understand everything that it does until Chapter 17, but it's easy enough to follow. Here is the class skeleton and the definition of the global instance of the database:

 class CommandDatabase : public PythonDatabase { public:     CommandDatabase() : PythonDatabase( "data/commands/" ) {}     Command* generate( const std::string& p_str, entityid p_character );     void GiveCommands( entityid p_character, accesslevel p_level ); }; extern CommandDatabase CommandDB; 

The database inherits from a PythonDatabase class, which you'll see in Chapter 17. All you need to know at this point is that the PythonDatabase class generates Python scripts, and that you can wrap those into a PythonCommand object (also in Chapter 17), which is the Python version of the Command class. Figure 14.2 shows this relationship.

Figure 14.2. Commands have a partial inheritance hierarchy.


The CommandDatabase returns new instances of any kind of command you give it.

Generating Commands

Whenever the database is asked for a command, this is the function it calls:

 Command* CommandDatabase::generate(     const std::string& p_str, entityid p_character ) {     if( p_str == "quit" )         return new CPPCommandQuit( p_character );     else if( p_str == "go" )         return new CPPCommandGo( p_character );     else if( p_str == "chat" )         return new CPPCommandChat( p_character );     else if( p_str == "say" )         return new CPPCommandSay( p_character );     else if( p_str == "kick" )         return new CPPCommandKick( p_character );     else if( p_str == "quiet" )         return new CPPCommandQuiet( p_character );     else if( p_str == "shutdown" )         return new CPPCommandShutdown( p_character );     else if( p_str == "look" )         return new CPPCommandLook( p_character );     else if( p_str == "commands" )         return new CPPCommandCommands( p_character );     else if( p_str == "reloadscript" )         return new CPPCommandReloadScript( p_character ); 

I'm going to split up the code right here, and I would like to note that this kind of code is generally considered ugly. Whenever you add a new command class to the BetterMUD in C++, you also need to come back to this class and make sure it knows how to create and return the new kind of class. That can get extremely annoying if you end up adding lots of C++ classes.

Here's the rest of the function, which executes when it can't find a C++ command with the name you requested :

 else {         try {             // try to load a Python script             PythonInstance* command = SpawnNew( p_str );             return new PythonCommand( p_character, command );         }         catch( ... ) {             PyErr_Print();             // no script found         }     }     throw Exception( "Unknown Command Script" ); } 

This code uses classes you won't see until Chapter 17, but I hope makes some sense to you right now. The code calls the PythonDatabase::SpawnNew function to get an instance of a new Python class, and once it has that, it creates a new PythonCommand object that wraps around the Python class instance.

If no class is found, an exception is thrown, and no command object is returned.


If you really insist on adding lots of C++ commands to the game, you should note that the chained if-else method is inefficient once you get a large number of commands. You would be better off creating a map of some sort, to map names to classes. Or even better, just use Python for your commands.

[ LiB ]

MUD Game Programming
MUD Game Programming (Premier Press Game Development)
ISBN: 1592000908
EAN: 2147483647
Year: 2003
Pages: 147
Authors: Ron Penton

Similar book on Amazon © 2008-2017.
If you may any questions please contact us: