From a711e07b8023e0a168452ae0964254935e1d4e73 Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 18 Oct 2009 19:29:18 +0000 Subject: [PATCH] Added the capability of evennia commands to consist of more than one word. So the examples.red_button parent can now be pressed with the command 'push button' instead of 'pushbutton' as before. The variable COMMAND_MAXLEN in the config defines how many words your commands may maximum contain (don't set it higher than needed for efficiency reasons). The main drawback of multi-word commands are that they can not have any switches; they are intended for in-game use (in states and on objects). Use normal one-word commands for administration and more complex commands with many options. /Griatch --- game/gamesrc/parents/examples/red_button.py | 4 +- src/cmdhandler.py | 142 ++++++++++++++------ src/commands/privileged.py | 14 +- src/config_defaults.py | 8 +- 4 files changed, 115 insertions(+), 53 deletions(-) diff --git a/game/gamesrc/parents/examples/red_button.py b/game/gamesrc/parents/examples/red_button.py index cc9757a4fd..92f3fc9c3f 100644 --- a/game/gamesrc/parents/examples/red_button.py +++ b/game/gamesrc/parents/examples/red_button.py @@ -104,6 +104,6 @@ def class_factory(source_obj): """ button = RedButton(source_obj) # add the object-based commands to the button - button.command_table.add_command("pushbutton", cmd_push_button) - button.command_table.add_command("pullbutton", cmd_pull_button) + button.command_table.add_command("push button", cmd_push_button) + button.command_table.add_command("pull button", cmd_pull_button) return button diff --git a/src/cmdhandler.py b/src/cmdhandler.py index 618f9bec9a..172592becf 100755 --- a/src/cmdhandler.py +++ b/src/cmdhandler.py @@ -3,8 +3,9 @@ This is the command processing module. It is instanced once in the main server module and the handle() function is hit every time a player sends something. """ -import time +#import time from traceback import format_exc +from django.conf import settings from django.contrib.contenttypes.models import ContentType import defines_global import cmdtable @@ -13,6 +14,8 @@ import logger import comsys import alias_mgr +COMMAND_MAXLEN = settings.COMMAND_MAXLEN + class UnknownCommand(Exception): """ Throw this when a user enters an an invalid command. @@ -47,6 +50,8 @@ class Command(object): command_switches = [] # The un-parsed argument provided. IE: if input is "look dog", this is "dog". command_argument = None + # list of tuples for possible multi-space commands and their arguments + command_alternatives = None # A reference to the command function looked up in a command table. command_function = None # An optional dictionary that is passed through the command table as extra_vars. @@ -66,45 +71,63 @@ class Command(object): Breaks the command up into the main command string, a list of switches, and a string containing the argument provided with the command. More specific processing is left up to the individual command functions. - """ - try: - """ - Break the command in half into command and argument. If the - command string can't be parsed, it has no argument and is - handled by the except ValueError block below. - """ - # Lop off the return at the end. - self.raw_input = self.raw_input.strip('\r') - # Break the command up into the root command and its arguments. - (self.command_string, self.command_argument) = self.raw_input.split(' ', 1) - # Yank off trailing and leading spaces. - self.command_argument = self.command_argument.strip() - self.command_string = self.command_string.strip() - """ - This is a really important behavior to note. If the user enters - anything other than a string with some character in it, the value - of the argument is None, not an empty string. - """ - if self.command_string == '': - self.command_string = None - if self.command_argument == '': - self.command_argument = None - - if self.command_string == None: - """ - This prevents any bad stuff from happening as a result of - trying to further parse a None object. - """ - return - except ValueError: - """ - No arguments. IE: look, who. - """ - self.command_string = self.raw_input - # Parse command_string for switches, regardless of what happens. - self.parse_command_switches() - + The command can come in two forms: + command/switches arg + command_with_spaces arg + + The first form is the normal one, used for administration and other commands + that benefit from the use of switches and options. The drawback is that it + can only consist of one single word (no spaces). + The second form, which does not accept switches, allows for longer command + names (e.g. 'press button' instead of pressbutton) and is mainly useful for + object-based commands for roleplay, puzzles etc. + """ + if not self.raw_input: + return + + # add a space after the raw input; this cause split() to always + # create a list with at least two entries. + raw = "%s " % self.raw_input + cmd_words = raw.split() + try: + if '/' in cmd_words[0]: + # if we have switches we directly go for the first command form. + command_string, command_argument = \ + (inp.strip() for inp in raw.split(' ', 1)) + if command_argument: + self.command_argument = command_argument + if command_string: + # we have a valid command, store and parse switches. + self.command_string = command_string + self.parse_command_switches() + else: + # no switches - we need to save a list of all possible command + # names up to the max-length allowed. + command_maxlen = min(COMMAND_MAXLEN, len(cmd_words)) + command_alternatives = [] + for spacecount in reversed(range(command_maxlen)): + # store all space-separated possible command names + # as tuples (commandname, args). They are stored with + # the longest possible name first. + try: + command_alternatives.append( (" ".join(cmd_words[:spacecount+1]), + " ".join(cmd_words[spacecount+1:])) ) + except IndexError: + continue + if command_alternatives: + # store alternatives. Store the one-word command + # as the default command name. + one_word_command = command_alternatives.pop() + self.command_string = one_word_command[0] + self.command_argument = one_word_command[1] + self.command_alternatives = command_alternatives + except IndexError: + # this SHOULD only happen if raw_input is malformed + # (like containing only control characters). + pass + + def __init__(self, source_object, raw_input, session=None): """ Instantiates the Command object and does some preliminary parsing. @@ -179,6 +202,16 @@ def match_alias(command): command.command_string = alias_mgr.CMD_ALIAS_LIST.get( command.command_string, command.command_string) + # Run aliasing on alternative command names (for commands with + # spaces in them) + if command.command_alternatives: + command_alternatives = [] + for command_alternative in command.command_alternatives: + command_alternatives.append( (alias_mgr.CMD_ALIAS_LIST.get( + command_alternative[0], + command_alternative[0]), + command_alternative[1]) ) + command.command_alternatives = command_alternatives def get_aliased_message(): """ @@ -201,7 +234,6 @@ def match_alias(command): elif first_char == ':': command.command_argument = get_aliased_message() command.command_string = "pose" -# command.command_string = "emote" # Pose without space alias. elif first_char == ';': command.command_argument = get_aliased_message() @@ -298,13 +330,35 @@ def command_table_lookup(command, command_table, eval_perms=True, evaluates the permissions tuple. The test flag only checks without manipulating the command neighbor (object) If this is supplied, we are looking at a object table and - must check for locks. + must check for locks. + + In the case of one-word commands with switches, this is a + quick look-up. For non-switch commands the command might + however consist of several words separated by spaces up to + a certain max number of words. We don't know beforehand if one + of these match an entry in this particular command table. We search + them in order longest to shortest before deferring to the normal, + one-word assumption. """ - # Get the command's function reference (Or False) - cmdtuple = command_table.get_command_tuple(command.command_string) + cmdtuple = None + if command.command_alternatives: + # we have command alternatives (due to spaces in command definition) + for cmd_alternative in command.command_alternatives: + # the alternatives are ordered longest -> shortest. + cmdtuple = command_table.get_command_tuple(cmd_alternative[0]) + if cmdtuple: + # we have a match, so this is the 'right' command to use + # with this particular command table. + command.command_string = cmd_alternative[0] + command.command_argument = cmd_alternative[1] + if not cmdtuple: + # None of the alternatives match, go with the default one-word name + cmdtuple = command_table.get_command_tuple(command.command_string) + if cmdtuple: - # Check if this is just a test. + # if we get here we have found a command match in the table if test: + # Check if this is just a test. return True # Check locks if neighbor and not neighbor.scriptlink.use_lock(command.source_object): diff --git a/src/commands/privileged.py b/src/commands/privileged.py index 8984cb71e4..6cc522d3f3 100644 --- a/src/commands/privileged.py +++ b/src/commands/privileged.py @@ -696,10 +696,10 @@ def cmd_setcmdalias(command): @setcmdalias - define shortcuts for commands Usage: - @setcmdalias[/switch] [command = ] alias + @setcmdalias[/switch] alias [= command] Switches: - list - view all command aliases (default) + list - view all command aliases add - add alias del - remove and existing alias @@ -713,22 +713,26 @@ def cmd_setcmdalias(command): args = command.command_argument switches = command.command_switches - if not args or 'list' in switches: + if "list" in switches: # show all aliases string = "Command aliases defined:" aliases = CommandAlias.objects.all() if not aliases: string = "No command aliases defined." for alias in aliases: - string += "\n %s = %s" % (alias.equiv_command, alias.user_input) + string += "\n %s -> %s" % (alias.user_input, alias.equiv_command) source_object.emit_to(string) return + if not args: + source_object.emit_to("Usage: @setcmdalias[/list/add/del] alias [= command]") + return + equiv_command = "" user_input = "" # analyze args if '=' in args: - equiv_command, user_input = [arg.strip() for arg in args.split("=",1)] + user_input, equiv_command = [arg.strip() for arg in args.split("=",1)] else: user_input = args.strip() diff --git a/src/config_defaults.py b/src/config_defaults.py index 5b98ca4ca8..f90a45de17 100644 --- a/src/config_defaults.py +++ b/src/config_defaults.py @@ -66,10 +66,14 @@ DATABASE_HOST = '' # Empty string defaults to localhost. Not used with sqlite3. DATABASE_PORT = '' +# How many words a single command name may have (e.g. 'push button' instead of 'pushbutton') +# (commands with switches always accept only one word in the name, e.g. @sethelp/add) +COMMAND_MAXLEN = 3 + ## Command aliases # These are convenient aliases set up when the game is started # for the very first time. You can add/delete aliases in-game using -# the @cmdalias command. +# the @setcmdalias command. COMMAND_ALIASES = {"@desc":"@describe", "@dest":"@destroy", "@nuke":"@destroy", "@tel":"@teleport", @@ -78,7 +82,7 @@ COMMAND_ALIASES = {"@desc":"@describe", "ex":"examine", "sa":"say", "emote":"pose", - "p":"page" } + "p":"page"} ## Permissions ## The variables in this section are used by each evennia subsystem to tell which permissions to define.