diff --git a/game/gamesrc/commands/examples/__init__.py b/game/gamesrc/commands/examples/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/game/gamesrc/commands/example.py b/game/gamesrc/commands/examples/example.py similarity index 92% rename from game/gamesrc/commands/example.py rename to game/gamesrc/commands/examples/example.py index 30e9a90583..098a9fe04a 100644 --- a/game/gamesrc/commands/example.py +++ b/game/gamesrc/commands/examples/example.py @@ -4,16 +4,16 @@ in action. You'll need to make sure that this or any new modules you create are added to game/settings.py under CUSTOM_COMMAND_MODULES or CUSTOM_UNLOGGED_COMMAND_MODULES, -which are tuples of module import path strings. -See src/config_defaults.py for more details. +which are tuples of module import path strings. See src/config_defaults.py for more details. E.g. to add this example command for testing, your entry in game/settings.py would look like this: -CUSTOM_COMMAND_MODULES = ('game.gamesrc.commands.example',) +CUSTOM_COMMAND_MODULES = ('game.gamesrc.commands.examples.example',) (note the extra comma at the end to make this into a Python tuple. It's only -needed if you have only one entry.) +needed if you have only one entry.) You need to restart the Evennia server before new +files are recognized. """ @@ -70,8 +70,6 @@ def cmd_example(command): # automatically be created for us. GLOBAL_CMD_TABLE.add_command("example", cmd_example, auto_help=True), - - #another simple example def cmd_emote_smile(command): @@ -92,3 +90,7 @@ def cmd_emote_smile(command): #add to global command table (no auto_help activated) GLOBAL_CMD_TABLE.add_command('smile', cmd_emote_smile) + + + + diff --git a/game/gamesrc/commands/examples/state_example.py b/game/gamesrc/commands/examples/state_example.py new file mode 100644 index 0000000000..a574b202e3 --- /dev/null +++ b/game/gamesrc/commands/examples/state_example.py @@ -0,0 +1,112 @@ +""" +Example of using the state system. The Event system allows a player object to be +'trapped' in a special environment where different commands are available than normal. +This is very useful in order to implement anything from menus to npc-conversational +choices and inline text-editors. + +This example uses the State system to create a simple menu. + +To test out this example, add this module to the CUSTOM_COMMAND_MODULES tuple in +your game/settings.py as 'game.gamesrc.commands.examples.state_example' (see ./example.py +for another example). You need to restart the Evennia server before new files are +recognized. + +Next enter the mud and give the command +> entermenu + +Note that the help entries added to the state system with the auto_help flag are NOT +part of the normal help database, they are stored with the state and only accessible +from inside it (unless you also set the 'global_help' flag in the add_command(), in +which case it is also added to the global help system). If you want to describe the +state itself in more detail you should add that to the main help index manually. +""" + +#This is the normal command table, accessible by default +from src.cmdtable import GLOBAL_CMD_TABLE + +#The statetable contains sets of cmdtables that is made available +#only when we are in a particular state, overriding the GLOBAL_CMD_TABLE +from src.statetable import GLOBAL_STATE_TABLE + +# +# Implementing a simple 'menu' state +# + +#the name of our state, to make sure it's the same everywhere +STATENAME = 'menu' + + +# +# 'entry' command +# +def cmd_entermenu(command): + """ + This is the 'entry' command that takes the player from the normal + gameplay mode into the 'menu' state. In order to do this, it + must be added to the GLOBAL_CMD_TABLE like any command. + """ + #get the player object calling the command + source_object = command.source_object + #this is important: we use the set_state() command + #to shift the player into a state named 'menu'. + source_object.set_state(STATENAME) + #display the menu. + print_menu(source_object) + +# +# Commands only available while in the 'menu' state. Note that +# these have auto_help, so the __doc__ strings of the functions +# can be read as help entries when in the menu. +# +def menu_cmd_option1(command): + """option1 + This selects the first option. + """ + source_object = command.source_object + print_menu(source_object, 1) +def menu_cmd_option2(command): + """option2 + This selects the second option. Duh. + + <> + This is an extra topic to test the auto_help functionality. + """ + source_object = command.source_object + print_menu(source_object, 2) +def menu_cmd_clear(command): + """clear + Clears the options. + """ + source_object = command.source_object + print_menu(source_object) + +# +# helper function +# +def print_menu(source_obj,choice=None): + """ + Utility function to print the menu. More interesting things + would happen here in a real menu. + """ + if choice==1: + ch = "> option1\n option2" + elif choice==2: + ch = " option1\n> option2" + else: + ch = " option1\n option2" + + s ="Menu---------\n%s\n help - get help" % ch + source_obj.emit_to(s) + + + +#Add the 'entry' command to the normal command table +GLOBAL_CMD_TABLE.add_command("entermenu", cmd_entermenu) + +#Add the menu commands to the state table by tying them to the 'menu' state. It is +#important that the name of the state matches what we set the player-object to in +#the 'entry' command. Since auto_help is on, we will have help entries for all commands +#while in the menu. +GLOBAL_STATE_TABLE.add_command(STATENAME, "option1", menu_cmd_option1,auto_help=True) +GLOBAL_STATE_TABLE.add_command(STATENAME, "option2", menu_cmd_option2,auto_help=True) +GLOBAL_STATE_TABLE.add_command(STATENAME, "clear", menu_cmd_clear,auto_help=True) diff --git a/src/cmdhandler.py b/src/cmdhandler.py index f7f0cee619..6b99ee93ba 100755 --- a/src/cmdhandler.py +++ b/src/cmdhandler.py @@ -8,6 +8,7 @@ from traceback import format_exc from django.contrib.contenttypes.models import ContentType import defines_global import cmdtable +import statetable import logger import comsys import alias_mgr @@ -318,19 +319,40 @@ def handle(command): # Not logged in, look through the unlogged-in command table. command_table_lookup(command, cmdtable.GLOBAL_UNCON_CMD_TABLE, eval_perms=False) - else: - # Match against the 'idle' command. - match_idle(command) - # See if this is an aliased command. - match_alias(command) - # Check if the user is using a channel command. - match_channel(command) - # See if the user is trying to traverse an exit. - match_exits(command) - neighbor_match_found = match_neighbor_ctables(command) - if not neighbor_match_found: - # Retrieve the appropriate (if any) command function. - command_table_lookup(command, cmdtable.GLOBAL_CMD_TABLE) + + else: + + state_name = command.source_object.get_state() + state_cmd_table = statetable.GLOBAL_STATE_TABLE.get_cmd_table(state_name) + + if state_name and state_cmd_table: + # we are in a special state. + + # check idle command. + match_idle(command) + # check for channel commands + prev_command = command.command_string + match_channel(command) + if prev_command != command.command_string: + # a channel command is handled normally also in the state + command_table_lookup(command, cmdtable.GLOBAL_CMD_TABLE) + else: + command_table_lookup(command, state_cmd_table) + else: + #normal operation + + # Match against the 'idle' command. + match_idle(command) + # See if this is an aliased command. + match_alias(command) + # Check if the user is using a channel command. + match_channel(command) + # See if the user is trying to traverse an exit. + match_exits(command) + neighbor_match_found = match_neighbor_ctables(command) + if not neighbor_match_found: + # Retrieve the appropriate (if any) command function. + command_table_lookup(command, cmdtable.GLOBAL_CMD_TABLE) """ By this point, we assume that the user has entered a command and not diff --git a/src/cmdtable.py b/src/cmdtable.py index d8f574d05e..ae0916a405 100644 --- a/src/cmdtable.py +++ b/src/cmdtable.py @@ -1,6 +1,6 @@ """ Command Table Module ---------------------- + Each command entry consists of a key and a tuple containing a reference to the command's function, and a tuple of the permissions to match against. The user only need have one of the permissions in the permissions tuple to gain @@ -19,7 +19,7 @@ from src.helpsys.management.commands.edit_helpfiles import add_help class CommandTable(object): """ - Stores command tables and performs lookups. + Stores commands and performs lookups. """ ctable = None @@ -28,7 +28,7 @@ class CommandTable(object): self.ctable = {} def add_command(self, command_string, function, priv_tuple=None, - extra_vals=None, auto_help=False, staff_only=False): + extra_vals=None, auto_help=False, staff_help=False, state=None): """ Adds a command to the command table. @@ -41,7 +41,7 @@ class CommandTable(object): auto_help (bool): If true, automatically creates/replaces a help topic with the same name as the command_string, using the functions's __doc__ property for help text. - staff_help (bool): Only relevant if help_auto is activated; It True, makes the + staff_help (bool): Only relevant if auto_help is activated; If True, makes the help topic (and all eventual subtopics) only visible to staff. Note: the auto_help system also supports limited markup. If you divide your __doc__ @@ -57,7 +57,7 @@ class CommandTable(object): #add automatic help text from the command's doc string topicstr = command_string entrytext = function.__doc__ - add_help(topicstr, entrytext, staff_only=staff_only, + add_help(topicstr, entrytext, staff_only=staff_help, force_create=True, auto_help=True) def get_command_tuple(self, func_name): @@ -67,6 +67,7 @@ class CommandTable(object): """ return self.ctable.get(func_name, False) + """ Command tables """ diff --git a/src/commands/objmanip.py b/src/commands/objmanip.py index e1bd35f23f..2fa465709d 100644 --- a/src/commands/objmanip.py +++ b/src/commands/objmanip.py @@ -867,7 +867,7 @@ def cmd_recover(command): GLOBAL_CMD_TABLE.add_command("@recover", cmd_recover, - priv_tuple=("genperms.builder"),auto_help=True,staff_only=True) + priv_tuple=("genperms.builder"),auto_help=True,staff_help=True) def cmd_destroy(command): """ @@ -943,4 +943,4 @@ def cmd_destroy(command): source_object.emit_to("You schedule %s for destruction." % target_obj.get_name()) GLOBAL_CMD_TABLE.add_command("@destroy", cmd_destroy, - priv_tuple=("genperms.builder"),auto_help=True,staff_only=True) + priv_tuple=("genperms.builder"),auto_help=True,staff_help=True) diff --git a/src/helpsys/management/commands/edit_helpfiles.py b/src/helpsys/management/commands/edit_helpfiles.py index 435c4fe1c5..80411a47e3 100644 --- a/src/helpsys/management/commands/edit_helpfiles.py +++ b/src/helpsys/management/commands/edit_helpfiles.py @@ -67,7 +67,7 @@ def _create_help(topicstr, entrytext, staff_only=False, force_create=False, new_entry.save() return [new_entry] -def _handle_help_markup(topicstr, entrytext, staff_only, identifier="<> and @@ -103,7 +103,7 @@ def _handle_help_markup(topicstr, entrytext, staff_only, identifier="<> markup. + staff_help: (bool) Help entry is only available for staff players. + help_global: (bool) Also auto_add the help entry to the main help database. Be + careful with overwriting same-named help topics if you define special + versions of commands inside your state. + + exit_command: (bool) Sets if the default @exit command is added to the state. Only + one command needs to include this statement in order to add @exit. This is + usually a good idea to make sure the player is not stuck, but you might want + to turn this off if you know what you're doing and want to avoid players + 'escaping' the state (like in a combat state or similar), or when + you need to do some special final cleanup or save operations before + exiting (like in a text editor). + """ + + if not state_name: + log_errmsg("Command %s was not given a state. Not added." % command_string) + return + + state_name = state_name.strip() + if not self.state_table.has_key(state_name): + + #create the state + self.state_table[state_name] = CommandTable() + #always add a help index even though it might not be used. + self.help_index.add_state(state_name) + + if exit_command: + #add the @exit command + self.state_table[state_name].add_command("@exit", + cmd_state_exit, + auto_help=True) + if auto_help: + #add help for @exit command + self.help_index.add_state_help(state_name, "@exit", + cmd_state_exit.__doc__) + + #handle auto-help for the state + if auto_help: + + #make sure the state's help command is in place + self.state_table[state_name].add_command('help',cmd_state_help) + + #add the help text + helptext = function.__doc__ + self.help_index.add_state_help(state_name,command_string, + helptext,staff_only=staff_help) + if not help_global: + #if we don't want global help access, we need to + #turn off auto_help before adding the command. + auto_help = False + + #finally add the new command to the state + self.state_table[state_name].add_command(command_string, + function, priv_tuple, + extra_vals,auto_help, + staff_help) + + def get_cmd_table(self, state_name): + """ + Return the command table for a particular state. + """ + if self.state_table.has_key(state_name): + return self.state_table[state_name] + else: + return None + + +class StateHelpIndex(object): + """ + Handles the dynamic state help system. + """ + help_index = None + def __init__(self): + self.help_index = {} + self.identifier = '<> and <> markup.""" + if self.help_index.has_key(state): + + if self.identifier in text: + topic_dict, staff_dict = edit_help.handle_help_markup(command, text, staff_only, + identifier=self.identifier) + for topic, text in topic_dict.items(): + entry = edit_help.format_footer(topic,text,topic_dict,staff_dict) + if entry: + self.help_index[state][topic] = (staff_only, entry) + else: + self.help_index[state][command] = (staff_only, text) + + def get_state_help(self,caller, state, command): + "get help for a particular command within a state" + if self.help_index.has_key(state) and self.help_index[state].has_key(command): + help_tuple = self.help_index[state][command] + if caller.is_staff() or not help_tuple[0]: + return help_tuple[1] + return None + else: + return None + + def get_state_index(self,caller, state): + "list all help topics for a state" + if self.help_index.has_key(state): + if caller.is_staff(): + index = self.help_index[state].keys() + else: + index = [] + for key, tup in self.help_index[state].items(): + if not tup[0]: + index.append(key) + return sorted(index) + else: + return None + +#default commands available for all special states + +def cmd_state_exit(command): + """@exit (when in a state) + + This command only works when inside a special game 'state' (like a menu or + editor or similar place where you have fewer commands available than normal). + + It aborts what you were doing and force-exits back to the normal mode of + gameplay. Some states might deactivate the @exit command for various reasons. + In those cases, read the help when in the state to learn more.""" + + source_object = command.source_object + source_object.clear_state() + source_object.emit_to("... Exited.") + source_object.execute_cmd('look') + +def cmd_state_help(command): + """ + help (while in a state) + + In-state help system. This is NOT tied to the normal help + system and is not stored in the database. It is intended as a quick + reference for users when in the state; if you want a detailed description + of the state itself, you should probably add it to the main help system + so the user can read it at any time. + If you don't want to use the auto-system, turn off auto_help + for all commands in the state. You could then for example make a custom help command + that displays just a short help summary page instead. + """ + + source_object = command.source_object + state = source_object.get_state() + args = command.command_argument + help_index = GLOBAL_STATE_TABLE.help_index + + if not args: + index = help_index.get_state_index(source_object, state) + if not index: + source_object.emit_to("There is no help available here.") + return + s = "Help topics for %s:\n" % state + for i in index: + s += " %s\n" % i + s = s[:-1] + source_object.emit_to(s) + return + else: + args = args.strip() + help = help_index.get_state_help(source_object, state, args) + if help: + source_object.emit_to("%s" % help) + else: + source_object.emit_to("No help available on %s." % args) + + +GLOBAL_STATE_TABLE = StateTable()