01may11 CmdrZin

Commands: Inner and Outer

How the Command system works in ZMUohMy

There are several pieces to the ZMUohMy Command system. All of them were inherited from the Lab 7400 DarkMUD game concept presented at Java One in 2008.

1. HELP: Any User typeable Command should have help text associated with it. Classes that add Command register them to link the Help string.
Only new Commands need to be registered as there should only be one HELP text for each User Command.
The MudObject.registerCommands() provides the initial LOOK commands HELP text response.
    MudMain.registerHelpCommand("look", LOOK_DESC);
If a User types "help look", then they get the LOOK_DESC text displayed to them.

Here are some of the Command already in use:
    MudUser class registers HELP, INV, LOGOUT, and YELL commands.
    MudCharacter class registers the SAY and EMOTE commands.

The Help Commands are all stored in the static HelpCommands classin MudMain as a HashMap object. There are simple assesor methods to add and get the help list or the help item description.

Other Commands, such as ARRIVAL, do not need help text since the User nevers sees them. For example, the Door object registers the GO and WALK commands with help text, but not the ARRIVE command.

2: RESPONSE: Each MudObject extended Class has a list of Commands that it can respond to. These are normally listed in a CommandType enum.
The Class is also responsible for parsing out the command from the text sent by the User and adjusting the Command object to tell everyone else what type of message it is.

The Basic Flow: LOOK
Since the MudUser is the only class that implements the ClientSessionListener interface, ALL Client messages are received by its receivedMessage() method and sent through the MessageFactory() to the handleMessage() method. From there, the message text is sent to the MudObject.parse( String ) method, turned into a Command object, and sent off to the parse( Command ) method. Now the fun begins. Let's assume that the User typed in a LOOK command.

First we record who started this mess by recording the invoker (the User) who had to be some extension of a MudObject.
Then the Command is passed to the Overridden parseInnerCommand() method of MudUser.
The MudUser first sends the command up to the MudCharacter class parseInnerCommand() method to check the more common commands. The MudCharacter does the same by sending to the MudObject first for initial processing. This last step doesn't make much sence since the MudObject.parseInnerCommand() method is empty.
In this case, we're back in MudCharcter.parseInnerCommand() to check for "look" text. If it had found "say" or "emote" at the begining of the text, then it would have modified the Command object before returning. Since not, the method returns without doing anything of interest.
So we're back in MudUser. It looks for "help", "inv", and other commands that it supports. Finding none of them, the processing section registers null interest in this command and returns the unmodfied command back to MudObject.

Now a check is made to see if this invoker is in a Container. Since we're a User, we have to be in a Room (a container) otherwise the User would not be in the game.
First we allow the container to try to process the text by calling its parseInnerCommand() method. Even though it is cast as a MudObject, the method has been overridden by the Room class.
The Room.parseInnerCommand() method first checks to see if anyone has already parsed this commands text. Then it checks for a single "look" text command. Note that "Look" and "LOOK" will fail. Maybe a toLower() should be used here??
Since it will find a "look", the Command type is set to LOOK and the text is set to "" (null) so that others will know what this command is. It also tells the command the the Room is the object that handled the command.
The Room then processes the command and generates text output, listing all of thie items in the Room for the User (invoker) that sent the message.

Once the container's inner command process is done, it passes the command to all of its inventory items (except the invoker) outer process methods to see if they need to respond to the command.
Since their processOuterCommand() methods first check to see if the command was handled, none of them preform any processing.

Now we're back to the User and we check the User's inventory to see if any of it response to the command. Again, since the command was handled, none of them preform and processing.

The last thing to do is add the invoker to the notifyInterest list. This method also calls any other object that are on the list (none in this example) to allow them to void the command. The notify() and commitCommand() methods do the final processing if any more is needed.

Man, this whole process may have started out as a good idea, but as implimented, it's a convoluted mess.

Post Mordem
Ok, some good things.
1. The Help stuff seems simple enough, so it can be kept almost as is.
2. The concept of Inner and Outer commands is never fully disclosed. In fact, the comments in MudObject for the methods are the same. So why would you need Inner and Outer commands???
The Command paradiem is used so that an object does not have to keep track of its environment. Instead of testing an action against every item nearby, it anounces that it's going to do something and sees if any item nearby wants to oppose the action. This is actually faster than trying to figure out all the interactions and dependentcies of objects nearby that could result in the action being voided.
So why both Inner and Outer???..hmm..Ok, commands start out as text and are parsed into a type. So, Inner command processing will be used until the command is parsed into a type and Outer command processing will be used once the command type is know.
Since the command are of the type <action> and <action> + <object>, a command with only <action> will have a type and null text while one with an <action> + <object> will have a type and the rest of the text in the command with whitespace removed (trimmed).
These concepts are only applicable to Commands. Other text interface like merchants and quest NPCs will have their own parsing.

New Process Flow
Commands can come from the User (message) or from the Server (event).
The User sill always be text to be parsed and starts it's process by calling it's own processInnerCommand() method.
The Server will already have the command type established and can start by calling the User's processOuterCommand() method.

The User's Command
Continue until command.handled == true
Setup new command: invoker, handled, text, type, etc.
    User.inventory.processInnerCommand (NOTE: inventory is what is carried or worn, NOT what is in a bag or backpack)
User.container.processInnerCommand (This would be the Room)
    User.container.inventory.processInnerCommand (This would be object in the Room like Doors and items or MOBs)
if still false, then "unrecognized commend" error message.
else (the outerCommandProcessing usually has objects registering their interest in the command.)
Now call the notify list for to see if any want or can abort the command.
Then call the commit list for those that can respond to the command.