diff --git a/game/gamesrc/commands/examples/state_example.py b/game/gamesrc/commands/examples/state_example.py index 914e26ffac..4a40b37fd7 100644 --- a/game/gamesrc/commands/examples/state_example.py +++ b/game/gamesrc/commands/examples/state_example.py @@ -14,11 +14,10 @@ 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. +Note that the help entries related to this little menu are not part of the normal +help database, they are stored with the state and only accessible +from inside it. If you want to describe the state itself in more detail you +should add a help entry about it to the main help index manually. To further test the state system, try the command > enterstate @@ -58,10 +57,13 @@ def cmd_entermenu(command): #show the menu. s = """ Welcome to the Demo menu! - In this demo all you can do is select one of the two options so it changes colour. - This is just intended to show off the possibility of the state system. More - interesting things should of course happen in a real menu. Use @exit to - leave the menu.""" + In this small demo all you can do is select one of + the two options so it changes colour. + This is just intended to show off the + possibilities of the state system. More + interesting things should of course happen + in a real menu. + Use @exit to leave the menu.""" source_object.emit_to(s) source_object.execute_cmd('menu') @@ -88,7 +90,7 @@ def menu_cmd_menu(command): """ menu Clears the options and redraws the menu. - <> + [[autohelp]] This is an extra topic to test the auto-help functionality. The state-help system supports nested ('related') topics just like the normal help index does. """ @@ -117,19 +119,15 @@ def print_menu(source_obj,choice=None): #Add the 'entry' command to the normal command table GLOBAL_CMD_TABLE.add_command("entermenu", cmd_entermenu) -#create the state. +#create the state. GLOBAL_STATE_TABLE.add_state(STATENAME,exit_command=True) #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, "menu", menu_cmd_menu,auto_help=True) - - - +#the 'entry' command. +GLOBAL_STATE_TABLE.add_command(STATENAME, "option1", menu_cmd_option1) +GLOBAL_STATE_TABLE.add_command(STATENAME, "option2", menu_cmd_option2) +GLOBAL_STATE_TABLE.add_command(STATENAME, "menu", menu_cmd_menu) #-----------------------testing the depth of the state system # This is a test suite that shows off all the features of the state system. @@ -234,7 +232,7 @@ GLOBAL_STATE_TABLE.add_state(TSTATE6,exit_command=True, allow_exits=True,allow_obj_cmds=True) #append the "test" function to all states -GLOBAL_STATE_TABLE.add_command(TSTATE1,'test',cmd_instate_cmd,auto_help=True) +GLOBAL_STATE_TABLE.add_command(TSTATE1,'test',cmd_instate_cmd) GLOBAL_STATE_TABLE.add_command(TSTATE2,'test',cmd_instate_cmd) GLOBAL_STATE_TABLE.add_command(TSTATE3,'test',cmd_instate_cmd) GLOBAL_STATE_TABLE.add_command(TSTATE4,'test',cmd_instate_cmd) diff --git a/src/cmdhandler.py b/src/cmdhandler.py index d461b082b3..637e24be28 100755 --- a/src/cmdhandler.py +++ b/src/cmdhandler.py @@ -291,7 +291,8 @@ def match_exits(command,test=False): raise ExitCommandHandler -def command_table_lookup(command, command_table, eval_perms=True,test=False,neighbor=None): +def command_table_lookup(command, command_table, eval_perms=True, + test=False, neighbor=None): """ Performs a command table lookup on the specified command table. Also evaluates the permissions tuple. @@ -329,7 +330,7 @@ def match_neighbor_ctables(command,test=False): Looks through the command tables of neighboring objects for command matches. test mode just checks if the command is a match, without manipulating - any commands. + any commands. """ source_object = command.source_object if source_object.location != None: @@ -349,7 +350,7 @@ def match_neighbor_ctables(command,test=False): #no matches return False -def handle(command): +def handle(command, ignore_state=False): """ Use the spliced (list) uinput variable to retrieve the correct command, or return an invalid command error. @@ -357,6 +358,8 @@ def handle(command): We're basically grabbing the player's command by tacking their input on to 'cmd_' and looking it up in the GenCommands class. + + ignore_state : ignore eventual statetable lookups completely. """ try: # TODO: Protect against non-standard characters. @@ -379,11 +382,11 @@ def handle(command): state = command.source_object.get_state() state_cmd_table = statetable.GLOBAL_STATE_TABLE.get_cmd_table(state) - if state and state_cmd_table: + if state and state_cmd_table and not ignore_state: # Caller is in a special state. state_allow_exits, state_allow_obj_cmds = \ - statetable.GLOBAL_STATE_TABLE.get_state_flags(state) + statetable.GLOBAL_STATE_TABLE.get_exec_rights(state) state_lookup = True if match_channel(command): diff --git a/src/cmdtable.py b/src/cmdtable.py index c276326920..d6461a7947 100644 --- a/src/cmdtable.py +++ b/src/cmdtable.py @@ -14,8 +14,8 @@ based on the value of settings.COMMAND_MODULES and settings.CUSTOM_COMMAND_MODULES. Each module imports cmdtable.py and runs add_command on the command table each command belongs to. """ - -from src.helpsys.management.commands.edit_helpfiles import add_help +from django.conf import settings +from src.helpsys import helpsystem class CommandTable(object): """ @@ -28,7 +28,8 @@ class CommandTable(object): self.ctable = {} def add_command(self, command_string, function, priv_tuple=None, - extra_vals=None, auto_help=False, staff_help=False): + extra_vals=None, help_category="", priv_help_tuple=None, + auto_help_override=None): """ Adds a command to the command table. @@ -36,29 +37,45 @@ class CommandTable(object): function: (reference) The command's function. priv_tuple: (tuple) String tuple of permissions required for command. extra_vals: (dict) Dictionary to add to the Command object. - - Auto-help system: - 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 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__ - with markers of the form <>, the system will automatically create - separate help topics for each topic. Your initial text (if you define no TOPIC) - will still default to the name of your command. - You can also custon-set the staff_only flag for individual subtopics by - using the markup <> and <>. + + Auto-help system: (this is only used if settings.HELP_AUTO_ENABLED is active) + help_category (str): An overall help category where auto-help will place + the help entry. If not given, 'General' is assumed. + priv_help_tuple (tuple) String tuple of permissions required to view this + help entry. If nothing is given, priv_tuple is used. + auto_help_override (bool): Override the value in settings.AUTO_HELP_ENABLED with the + value given. Use None to not override. + This can be useful when developing a new routine and + has made manual changes to help entries of other + commands in the database (and so do not want to use global + auto-help). It is also used by e.g. the state system + to selectively deactive auto-help. + + Note: the auto_help system also supports limited markup. You can divide your __doc__ + with markers of any combinations of the forms + [[Title]] + [[Title, category]] + [[Title, (priv_tuple)]] + [[Title, category, (priv_tuple)]], + If such markers are found, the system will automatically create + separate help topics for each topic. Your main help entry will + default to the name of your command. """ self.ctable[command_string] = (function, priv_tuple, extra_vals) - if auto_help: + if auto_help_override == None: + auto_help_override = settings.HELP_AUTO_ENABLED + + if auto_help_override: #add automatic help text from the command's doc string topicstr = command_string entrytext = function.__doc__ - add_help(topicstr, entrytext, staff_only=staff_help, - force_create=True, auto_help=True) + if not help_category: + help_category = "General" + if not priv_help_tuple: + priv_help_tuple = priv_tuple + helpsystem.edithelp.add_help_auto(topicstr, help_category, + entrytext, priv_help_tuple) def get_command_tuple(self, func_name): """ @@ -67,10 +84,6 @@ class CommandTable(object): """ return self.ctable.get(func_name, False) - -""" -Command tables -""" # Global command table, for authenticated users. GLOBAL_CMD_TABLE = CommandTable() # Global unconnected command table, for unauthenticated users. diff --git a/src/commands/batchprocess.py b/src/commands/batchprocess.py index ea5a139a18..25a05b3d6e 100644 --- a/src/commands/batchprocess.py +++ b/src/commands/batchprocess.py @@ -55,7 +55,7 @@ from src.statetable import GLOBAL_STATE_TABLE #global defines for storage -STATENAME="_interactive_batch_processor" +STATENAME="interactive batch processor" CMDSTACKS={} # user:cmdstack pairs (for interactive) STACKPTRS={} # user:stackpointer pairs (for interactive) FILENAMES={} # user:filename pairs (for interactive/reload) @@ -161,6 +161,8 @@ def batch_process(source_object, commands): def cmd_batchprocess(command): """ + @batchprocess - build from batch file + Usage: @batchprocess[/interactive] @@ -198,16 +200,16 @@ def cmd_batchprocess(command): CMDSTACKS[source_object] = commands STACKPTRS[source_object] = 0 FILENAMES[source_object] = filename - source_object.emit_to("Interactive mode (h for help).") + source_object.emit_to("\nBatch processor - Interactive mode ...") show_curr(source_object) else: + source_object.emit_to("Running Batch processor - Automatic mode ...") source_object.clear_state() batch_process(source_object, commands) source_object.emit_to("%s== Batchfile '%s' applied." % (cgreen,filename)) GLOBAL_CMD_TABLE.add_command("@batchprocess", cmd_batchprocess, - auto_help=True, staff_help=True, - priv_tuple=("genperms.process_control")) + priv_tuple=("genperms.process_control",), help_category="Building") #interactive state commands @@ -271,43 +273,71 @@ def reload_stack(source_object): source_object.emit_to("Commands in file could not be reloaded. Was it moved?") def move_in_stack(source_object, step=1): + "store data in stack" global CMDSTACKS, STACKPTRS N = len(CMDSTACKS[source_object]) currpos = STACKPTRS[source_object] STACKPTRS[source_object] = max(0,min(N-1,currpos+step)) def exit_state(source_object): + "Quit the state" global CMDSTACKS,STACKPTRS,FILENAMES - del CMDSTACKS[source_object] - del STACKPTRS[source_object] - del FILENAMES[source_object] + try: + del CMDSTACKS[source_object] + del STACKPTRS[source_object] + del FILENAMES[source_object] + except KeyError: + logger.log_errmsg("Batchprocessor quit error: all state vars could not be deleted.") source_object.clear_state() -def cmd_state_l(command): - "l-ook at current command definition" +def cmd_state_ll(command): + """ + ll + + Look at the full source for the current + command definition. + """ show_curr(command.source_object,showall=True) -def cmd_state_p(command): - "p-rocess current command definition" +def cmd_state_pp(command): + """ + pp + + Process the currently shown command definition. + """ process_commands(command.source_object) command.source_object.emit_to(printfooter()) -def cmd_state_r(command): - "r-eload file, keep current stack position" +def cmd_state_rr(command): + """ + rr + + Reload the batch file, keeping the current + position in it. + """ reload_stack(command.source_object) command.source_object.emit_to("\nFile reloaded. Staying on same command.\n") show_curr(command.source_object) -def cmd_state_rr(command): - "r-eload file, start over" +def cmd_state_rrr(command): + """ + rrr + + Reload the batch file, starting over + from the beginning. + """ global STACKPTRS reload_stack(command.source_object) STACKPTRS[command.source_object] = 0 command.source_object.emit_to("\nFile reloaded. Restarting from top.\n") show_curr(command.source_object) -def cmd_state_n(command): - "n-ext command (no exec)" +def cmd_state_nn(command): + """ + nn + + Go to next command. No commands are executed. + """ source_object = command.source_object arg = command.command_argument if arg and arg.isdigit(): @@ -317,8 +347,29 @@ def cmd_state_n(command): move_in_stack(source_object, step) show_curr(source_object) -def cmd_state_b(command): - "b-ackwards to previous command (no exec)" +def cmd_state_nl(command): + """ + nl + + Go to next command, viewing its full source. + No commands are executed. + """ + source_object = command.source_object + arg = command.command_argument + if arg and arg.isdigit(): + step = int(command.command_argument) + else: + step = 1 + move_in_stack(source_object, step) + show_curr(source_object, showall=True) + +def cmd_state_bb(command): + """ + bb + + Backwards to previous command. No commands + are executed. + """ source_object = command.source_object arg = command.command_argument if arg and arg.isdigit(): @@ -328,8 +379,30 @@ def cmd_state_b(command): move_in_stack(source_object, step) show_curr(source_object) -def cmd_state_s(command): - "s-tep to next command (exec)" +def cmd_state_bl(command): + """ + bl + + Backwards to previous command, viewing its full + source. No commands are executed. + """ + source_object = command.source_object + arg = command.command_argument + if arg and arg.isdigit(): + step = -int(command.command_argument) + else: + step = -1 + move_in_stack(source_object, step) + show_curr(source_object, showall=True) + +def cmd_state_ss(command): + """ + ss [steps] + + Process current command, then step to the next + one. If steps is given, + process this many commands. + """ source_object = command.source_object arg = command.command_argument if arg and arg.isdigit(): @@ -339,8 +412,30 @@ def cmd_state_s(command): process_commands(source_object,step) show_curr(source_object) -def cmd_state_c(command): - "c-ontinue to process remaining" +def cmd_state_sl(command): + """ + sl [steps] + + Process current command, then step to the next + one, viewing its full source. If steps is given, + process this many commands. + """ + source_object = command.source_object + arg = command.command_argument + if arg and arg.isdigit(): + step = int(command.command_argument) + else: + step = 1 + process_commands(source_object,step) + show_curr(source_object, showall=True) + +def cmd_state_cc(command): + """ + cc + + Continue to process all remaining + commands. + """ global CMDSTACKS,STACKPTRS source_object = command.source_object N = len(CMDSTACKS[source_object]) @@ -350,8 +445,12 @@ def cmd_state_c(command): exit_state(source_object) source_object.emit_to("Finished processing batch file.") -def cmd_state_j(command): - "j-ump to specific command index" +def cmd_state_jj(command): + """ + j + + Jump to specific command number + """ global STACKPTRS source_object = command.source_object arg = command.command_argument @@ -365,18 +464,44 @@ def cmd_state_j(command): move_in_stack(source_object, step) show_curr(source_object) -def cmd_state_q(command): - "q-uit state." +def cmd_state_jl(command): + """ + jl + + Jump to specific command number and view its full source. + """ + global STACKPTRS + source_object = command.source_object + arg = command.command_argument + if arg and arg.isdigit(): + no = int(command.command_argument)-1 + else: + source_object.emit_to("You must give a number index.") + return + ptr = STACKPTRS[source_object] + step = no - ptr + move_in_stack(source_object, step) + show_curr(source_object, showall=True) + +def cmd_state_qq(command): + """ + qq + + Quit the batchprocessor. + """ exit_state(command.source_object) command.source_object.emit_to("Aborted interactive batch mode.") -def cmd_state_h(command): +def cmd_state_hh(command): "Help command" s = """ Interactive batch processing commands: nn [steps] - next command (no processing) + nl [steps] - next & look bb [steps] - back to previous command (no processing) + bl [steps] - back & look jj - jump to command no N (no processing) + jl - jump & look pp - process currently shown command (no step) ss [steps] - process & step ll - look at full definition of current command @@ -384,8 +509,8 @@ def cmd_state_h(command): rrr - reload batch file (start from first) hh - this help list - cc - continue processing to end and quit. - qq - quit (abort all remaining) + cc - continue processing to end, then quit. + qq - quit (abort all remaining commands) """ command.source_object.emit_to(s) @@ -394,14 +519,18 @@ def cmd_state_h(command): GLOBAL_STATE_TABLE.add_state(STATENAME,global_cmds='all', allow_exits=True,allow_obj_cmds=True) #add state commands -GLOBAL_STATE_TABLE.add_command(STATENAME,"nn",cmd_state_n) -GLOBAL_STATE_TABLE.add_command(STATENAME,"bb",cmd_state_b) -GLOBAL_STATE_TABLE.add_command(STATENAME,"jj",cmd_state_j) -GLOBAL_STATE_TABLE.add_command(STATENAME,"pp",cmd_state_p) -GLOBAL_STATE_TABLE.add_command(STATENAME,"ss",cmd_state_s) -GLOBAL_STATE_TABLE.add_command(STATENAME,"cc",cmd_state_c) -GLOBAL_STATE_TABLE.add_command(STATENAME,"ll",cmd_state_l) -GLOBAL_STATE_TABLE.add_command(STATENAME,"rr",cmd_state_r) -GLOBAL_STATE_TABLE.add_command(STATENAME,"rrr",cmd_state_rr) -GLOBAL_STATE_TABLE.add_command(STATENAME,"hh",cmd_state_h) -GLOBAL_STATE_TABLE.add_command(STATENAME,"qq",cmd_state_q) +GLOBAL_STATE_TABLE.add_command(STATENAME,"nn",cmd_state_nn) +GLOBAL_STATE_TABLE.add_command(STATENAME,"nl",cmd_state_nl) +GLOBAL_STATE_TABLE.add_command(STATENAME,"bb",cmd_state_bb) +GLOBAL_STATE_TABLE.add_command(STATENAME,"bl",cmd_state_bl) +GLOBAL_STATE_TABLE.add_command(STATENAME,"jj",cmd_state_jj) +GLOBAL_STATE_TABLE.add_command(STATENAME,"jl",cmd_state_jl) +GLOBAL_STATE_TABLE.add_command(STATENAME,"pp",cmd_state_pp) +GLOBAL_STATE_TABLE.add_command(STATENAME,"ss",cmd_state_ss) +GLOBAL_STATE_TABLE.add_command(STATENAME,"sl",cmd_state_sl) +GLOBAL_STATE_TABLE.add_command(STATENAME,"cc",cmd_state_cc) +GLOBAL_STATE_TABLE.add_command(STATENAME,"ll",cmd_state_ll) +GLOBAL_STATE_TABLE.add_command(STATENAME,"rr",cmd_state_rr) +GLOBAL_STATE_TABLE.add_command(STATENAME,"rrr",cmd_state_rrr) +GLOBAL_STATE_TABLE.add_command(STATENAME,"hh",cmd_state_hh) +GLOBAL_STATE_TABLE.add_command(STATENAME,"qq",cmd_state_qq) diff --git a/src/commands/comsys.py b/src/commands/comsys.py index 31b51f49a1..c5c4c322b2 100644 --- a/src/commands/comsys.py +++ b/src/commands/comsys.py @@ -1,26 +1,22 @@ """ Comsys command module. """ -import time -from django.conf import settings from src import comsys from src.channels.models import CommChannelMembership, CommChannel from src import defines_global -from src import ansi -from src.util import functions_general from src.objects.models import Object from src.cmdtable import GLOBAL_CMD_TABLE def cmd_addcom(command): """ - addcom + addcom - join a channel with alias Usage: addcom [alias=] - Joins a channel. Allows adding an alias for it to make it - easier and faster to use. Subsequent calls of this command - can be used to add multiple aliases. + Allows adding an alias for a channel to make is easier and + faster to use. Subsequent calls of this command can + be used to add multiple aliases. """ source_object = command.source_object command_argument = command.command_argument @@ -81,11 +77,11 @@ def cmd_addcom(command): except CommChannel.DoesNotExist: # Failed to match iexact on channel's 'name' attribute. source_object.emit_to("Could not find channel %s." % chan_name) -GLOBAL_CMD_TABLE.add_command("addcom", cmd_addcom) +GLOBAL_CMD_TABLE.add_command("addcom", cmd_addcom, help_category="Comms") def cmd_delcom(command): """ - delcom + delcom - remove a channel alias Usage: delcom @@ -113,10 +109,15 @@ def cmd_delcom(command): leave_msg = "%s has left the channel." % \ (source_object.get_name(show_dbref=False),) comsys.send_cmessage(chan_name, leave_msg) -GLOBAL_CMD_TABLE.add_command("delcom", cmd_delcom), +GLOBAL_CMD_TABLE.add_command("delcom", cmd_delcom,help_category="Comms") def cmd_comlist(command): """ + comlist - list channel memberships + + Usage: + comlist + Lists the channels a user is subscribed to. """ source_object = command.source_object @@ -137,11 +138,14 @@ def cmd_comlist(command): chan.get_name(), chan_on) s = s[:-1] source_object.emit_to(s) -GLOBAL_CMD_TABLE.add_command("comlist", cmd_comlist) +GLOBAL_CMD_TABLE.add_command("comlist", cmd_comlist,help_category="Comms") def cmd_allcom(command): """ - allcom [on|off|who|clear] + allcom - operate on all channels + + Usage: + allcom [on | off | who | clear] Allows the user to universally turn off or on all channels they are on, as well as perform a 'who' for all channels they are on. Clear deletes @@ -197,14 +201,18 @@ def cmd_allcom(command): s = s[:-2] + "\n" s = s[:-1] source_object.emit_to(s) -GLOBAL_CMD_TABLE.add_command("allcom", cmd_allcom) +GLOBAL_CMD_TABLE.add_command("allcom", cmd_allcom, help_category="Comms") def cmd_clearcom(command): """ - clearcom + clearcom - removes all channels - Effectively runs delcom on all channels the user is on. It will remove their aliases, - remove them from the channel, and clear any titles they have set. + Usage: + clearcom + + Effectively runs delcom on all channels the user is on. It will remove + their aliases, remove them from the channel, and clear any titles they + have set. """ source_object = command.source_object #get aall subscribed channel memberships @@ -227,7 +235,10 @@ def cmd_clist(command): """ @clist - Lists all available channels on the game. + Usage: + @clist + + Lists all available channels in the game. """ session = command.session source_object = command.source_object @@ -248,13 +259,17 @@ def cmd_clist(command): s = s[:-1] #s += "** End of Channel List **" source_object.emit_to(s) -GLOBAL_CMD_TABLE.add_command("@clist", cmd_clist), +GLOBAL_CMD_TABLE.add_command("@clist", cmd_clist, help_category="Comms") + def cmd_cdestroy(command): """ @cdestroy - Destroys a channel. + Usage: + @cdestroy + + Destroys a channel that you control. """ source_object = command.source_object cname = command.command_argument @@ -275,7 +290,7 @@ def cmd_cdestroy(command): else: source_object.emit_to("Permission denied.") return -GLOBAL_CMD_TABLE.add_command("@cdestroy", cmd_cdestroy) +GLOBAL_CMD_TABLE.add_command("@cdestroy", cmd_cdestroy, help_category="Comms") def cmd_cset(command): """ @@ -297,9 +312,12 @@ def cmd_ccharge(command): def cmd_cboot(command): """ - @cboot[/quiet] = + @cboot - Kicks a player or object from the channel + Usage: + @cboot[/quiet] = + + Kicks a player or object from a channel you control. """ source_object = command.source_object args = command.command_argument @@ -352,14 +370,17 @@ def cmd_cboot(command): for mship in membership: comsys.plr_del_channel(bootobj, mship.user_alias) -GLOBAL_CMD_TABLE.add_command("@cboot", cmd_cboot) +GLOBAL_CMD_TABLE.add_command("@cboot", cmd_cboot, help_category="Comms") def cmd_cemit(command): """ - @cemit = - @cemit/noheader = - @cemit/sendername = + @cemit - send a message to channel + + Usage: + @cemit = + @cemit/noheader = + @cemit/sendername = Allows the user to send a message over a channel as long as they own or control it. It does not show the user's name unless they @@ -425,12 +446,12 @@ def cmd_cemit(command): #pipe to external channels (IRC, IMC) eventually mapped to this channel comsys.send_cexternal(cname_parsed, cmessage, caller=source_object) -GLOBAL_CMD_TABLE.add_command("@cemit", cmd_cemit,priv_tuple=("channels.emit_commchannel",)) +GLOBAL_CMD_TABLE.add_command("@cemit", cmd_cemit,priv_tuple=("channels.emit_commchannel",), + help_category="Comms") def cmd_cwho(command): """ @cwho - list Usage: @cwho channel[/all] @@ -468,15 +489,17 @@ def cmd_cwho(command): else: source_object.emit_to("No channel with that name was found.") return -GLOBAL_CMD_TABLE.add_command("@cwho", cmd_cwho), +GLOBAL_CMD_TABLE.add_command("@cwho", cmd_cwho, help_category="Comms") def cmd_ccreate(command): """ @ccreate - Creates a new channel with the invoker being the default owner. + Usage: + @ccreate + + Creates a new channel owned by you. """ - # TODO: Implement cmd_ccreate source_object = command.source_object cname = command.command_argument @@ -496,11 +519,14 @@ def cmd_ccreate(command): # Create and set the object up. new_chan = comsys.create_channel(cname, source_object) source_object.emit_to("Channel %s created." % (new_chan.get_name(),)) -GLOBAL_CMD_TABLE.add_command("@ccreate", cmd_ccreate) +GLOBAL_CMD_TABLE.add_command("@ccreate", cmd_ccreate, help_category="Comms") def cmd_cchown(command): """ - @cchown = + @cchown + + Usage: + @cchown = Changes the owner of a channel. """ @@ -535,4 +561,4 @@ def cmd_cchown(command): channel.set_owner(new_owner) source_object.emit_to("Owner of %s changed from %s to %s." % (cname, old_pname, pname)) new_owner.emit_to("%s transfered ownership of channel '%s' to you." % (old_pname, cname)) -GLOBAL_CMD_TABLE.add_command("@cchown", cmd_cchown) +GLOBAL_CMD_TABLE.add_command("@cchown", cmd_cchown, help_category="Comms") diff --git a/src/commands/general.py b/src/commands/general.py index d8f78557f8..42c182590d 100644 --- a/src/commands/general.py +++ b/src/commands/general.py @@ -7,17 +7,19 @@ from django.contrib.auth.models import User from src.config.models import ConfigValue from src.helpsys.models import HelpEntry from src.ansi import ANSITable -from src import defines_global from src import session_mgr from src.util import functions_general -import src.helpsys.management.commands.edit_helpfiles as edit_help +from src.helpsys import helpsystem from src.cmdtable import GLOBAL_CMD_TABLE def cmd_password(command): """ - Changes your own password. - - @password = + @password - set your password + + Usage: + @paassword = + + Changes your password. Make sure to pick a safe one. """ source_object = command.source_object @@ -54,18 +56,27 @@ def cmd_password(command): uaccount.set_password(newpass) uaccount.save() source_object.emit_to("Password changed.") -GLOBAL_CMD_TABLE.add_command("@password", cmd_password) +GLOBAL_CMD_TABLE.add_command("@password", cmd_password, help_category="System") def cmd_pemit(command): """ + @pemit + Emits something to a player. + + (Not yet implemented) """ # TODO: Implement cmd_pemit #GLOBAL_CMD_TABLE.add_command("@pemit", cmd_pemit) def cmd_emit(command): """ - Emits something to your location. + @emit + + Usage: + @emit + + Emits a message to your immediate surroundings. """ message = command.command_argument @@ -74,10 +85,15 @@ def cmd_emit(command): else: command.source_object.emit_to("Emit what?") GLOBAL_CMD_TABLE.add_command("@emit", cmd_emit, - priv_tuple=("genperms.announce")), + priv_tuple=("genperms.announce",),help_category="Comms"), def cmd_wall(command): """ + @wall + + Usage: + @wall + Announces a message to all connected players. """ wallstring = command.command_argument @@ -90,18 +106,29 @@ def cmd_wall(command): command.source_object.get_name(show_dbref=False), wallstring) session_mgr.announce_all(message) GLOBAL_CMD_TABLE.add_command("@wall", cmd_wall, - priv_tuple=("genperms.announce")) + priv_tuple=("genperms.announce",),help_category="Comms") def cmd_idle(command): """ - Returns nothing, this lets the player set an idle timer without spamming - his screen. + idle + + Usage: + idle + + Returns and does nothing. You can use this to send idle + messages to the game, in order to avoid getting timed out. """ pass -GLOBAL_CMD_TABLE.add_command("idle", cmd_idle) +GLOBAL_CMD_TABLE.add_command("idle", cmd_idle, help_category="System") def cmd_inventory(command): """ + inventory + + Usage: + inventory + inv + Shows a player's inventory. """ source_object = command.source_object @@ -121,7 +148,13 @@ GLOBAL_CMD_TABLE.add_command("inventory", cmd_inventory) def cmd_look(command): """ - Handle looking at objects. + look + + Usage: + look + look + + Observers your location or objects in your vicinity. """ source_object = command.source_object @@ -144,7 +177,13 @@ GLOBAL_CMD_TABLE.add_command("look", cmd_look) def cmd_get(command): """ - Get an object and put it in a player's inventory. + get + + Usage: + get + + Picks up an object from your location and puts it in + your inventory. """ source_object = command.source_object obj_is_staff = source_object.is_staff() @@ -193,11 +232,15 @@ GLOBAL_CMD_TABLE.add_command("get", cmd_get) def cmd_drop(command): """ - Drop an object from a player's inventory into their current location. + drop + + Usage: + drop + + Has you drop an object from your inventory into the + location you are currently in. """ source_object = command.source_object - obj_is_staff = source_object.is_staff() - if not command.command_argument: source_object.emit_to("Drop what?") return @@ -219,153 +262,40 @@ def cmd_drop(command): target_obj.get_name(show_dbref=False)), exclude=source_object) - # SCRIPT: Call the object's script's a_drop() method. + # SCRIPT: Call the object script's a_drop() method. target_obj.scriptlink.at_drop(source_object) GLOBAL_CMD_TABLE.add_command("drop", cmd_drop), - -def cmd_examine(command): - """ - Detailed object examine command - """ - source_object = command.source_object - attr_search = False - - if not command.command_argument: - # If no arguments are provided, examine the invoker's location. - target_obj = source_object.get_location() - else: - # Look for a slash in the input, indicating an attribute search. - attr_split = command.command_argument.split("/", 1) - - # If the splitting by the "/" character returns a list with more than 1 - # entry, it's an attribute match. - if len(attr_split) > 1: - attr_search = True - # Strip the object search string from the input with the - # object/attribute pair. - obj_searchstr = attr_split[0] - attr_searchstr = attr_split[1].strip() - - # Protect against stuff like: ex me/ - if attr_searchstr == '': - source_object.emit_to('No attribute name provided.') - return - else: - # No slash in argument, just examine an object. - obj_searchstr = command.command_argument - - # Resolve the target object. - target_obj = source_object.search_for_object(obj_searchstr) - # Use search_for_object to handle duplicate/nonexistant results. - if not target_obj: - return - - # If the user doesn't control the object, just look at it instead. - if not source_object.controls_other(target_obj, builder_override=True): - command.command_string = 'look' - cmd_look(command) - return - - if attr_search: - """ - Player did something like: examine me/* or examine me/TE*. Return - each matching attribute with its value. - """ - attr_matches = target_obj.attribute_namesearch(attr_searchstr) - if attr_matches: - for attribute in attr_matches: - source_object.emit_to(attribute.get_attrline()) - else: - source_object.emit_to("No matching attributes found.") - else: - """ - Player is examining an object. Return a full readout of attributes, - along with detailed information about said object. - """ - s = "" - newl = "\r\n" - # Format the examine header area with general flag/type info. - - s += str(target_obj.get_name(fullname=True)) + newl - s += str("Type: %s Flags: %s" % (target_obj.get_type(), - target_obj.get_flags())) + newl - s += str("Owner: %s " % target_obj.get_owner()) + newl - s += str("Zone: %s" % target_obj.get_zone()) + newl - s += str("Parent: %s " % target_obj.get_script_parent()) + newl - - locks = target_obj.get_attribute_value("LOCKS") - if locks and "%s" % locks: - s += str("Locks: %s" % locks) + newl - - # Contents container lists for sorting by type. - con_players = [] - con_things = [] - con_exits = [] - - # Break each object out into their own list. - for obj in target_obj.get_contents(): - if obj.is_player(): - con_players.append(obj) - elif obj.is_exit(): - con_exits.append(obj) - elif obj.is_thing(): - con_things.append(obj) - - # Render the object's home or destination (for exits). - if not target_obj.is_room(): - if target_obj.is_exit(): - # The Home attribute on an exit is really its destination. - s += str("Destination: %s" % target_obj.get_home()) + newl - else: - # For everything else, home is home. - s += str("Home: %s" % target_obj.get_home()) + newl - # This obviously isn't valid for rooms. - s += str("Location: %s" % target_obj.get_location()) + newl - - # Render other attributes - for attribute in target_obj.get_all_attributes(): - s += str(attribute.get_attrline()) + newl - - # Render Contents display. - if con_players or con_things: - s += str("%sContents:%s" % (ANSITable.ansi["hilite"], - ANSITable.ansi["normal"])) - for player in con_players: - s += str(' %s' % newl + player.get_name(fullname=True)) - for thing in con_things: - s += str(' %s' % newl + thing.get_name(fullname=True)) - # Render Exists display. - if con_exits: - s += str("%sExits:%s" % (newl + ANSITable.ansi["hilite"], - ANSITable.ansi["normal"])) - for exit in con_exits: - s += str(' %s' % newl + exit.get_name(fullname=True)) - - # Send it all - source_object.emit_to(s) - -GLOBAL_CMD_TABLE.add_command("examine", cmd_examine,priv_tuple=("objects.info",)) - def cmd_quit(command): """ - Gracefully disconnect the user as per his own request. + quit + + Usage: + quit + + Gracefully disconnect from the game. """ if command.session: session = command.session session.msg("Quitting. Hope to see you soon again.") session.handle_close() -GLOBAL_CMD_TABLE.add_command("quit", cmd_quit) +GLOBAL_CMD_TABLE.add_command("quit", cmd_quit, help_category="System") def cmd_who(command): """ - Generic WHO command. + who + + Usage: + who + + Shows who is currently online. """ session_list = session_mgr.get_session_list() source_object = command.source_object # In the case of the DOING command, don't show session data regardless. - if command.extra_vars and command.extra_vars.get("show_session_data", None) == False: + if command.extra_vars and \ + command.extra_vars.get("show_session_data", None) == False: show_session_data = False else: show_session_data = source_object.has_perm("genperms.see_session_data") @@ -412,12 +342,17 @@ def cmd_who(command): source_object.emit_to(retval) GLOBAL_CMD_TABLE.add_command("doing", cmd_who, - extra_vals={"show_session_data": False}) -GLOBAL_CMD_TABLE.add_command("who", cmd_who) + extra_vals={"show_session_data": False}, help_category="System") +GLOBAL_CMD_TABLE.add_command("who", cmd_who,help_category="System") def cmd_say(command): """ - Room-based speech command. + say + + Usage: + say + + Talk to those in your current location. """ source_object = command.source_object @@ -441,7 +376,18 @@ GLOBAL_CMD_TABLE.add_command("say", cmd_say) def cmd_pose(command): """ - Pose/emote command. + pose - strike a pose + + Usage: + pose + + Example: + pose is standing by the wall, smiling. + -> others will see: + Tom is standing by the wall, smiling. + + Describe an action being taken. The pose text will + automatically begin with your name. """ source_object = command.source_object @@ -464,189 +410,134 @@ def cmd_pose(command): GLOBAL_CMD_TABLE.add_command("pose", cmd_pose) def cmd_group(command): - """@group + """ + @group - show your groups + Usage: @group - This command shows you which user permission groups you are a member of, if any. + This command shows you which user permission groups + you are a member of, if any. """ source_object = command.source_object - user = User.objects.get(username=source_object.get_name(show_dbref=False,no_ansi=True)) - s = "" + user = User.objects.get(username=source_object.get_name(show_dbref=False, no_ansi=True)) + string = "" if source_object.is_superuser(): - s += "\n This is a SUPERUSER account! Group membership does not matter." + string += "\n This is a SUPERUSER account! Group membership does not matter." if not user.is_active: - s += "\n ACCOUNT NOT ACTIVE." - for g in user.groups.all(): - s += "\n -- %s" % g - for p in g.permissions.all(): - s += "\n --- %s" % p.name - if not s: - s = "You are not a member of any groups." % source_object.get_name(show_dbref=False) + string += "\n ACCOUNT NOT ACTIVE." + for group in user.groups.all(): + string += "\n -- %s" % group + for perm in group.permissions.all(): + string += "\n --- %s" % perm.name + if not string: + string = "You are not a member of any groups." % source_object.get_name(show_dbref=False) else: - s = "\nYour (%s's) group memberships: %s" % (source_object.get_name(show_dbref=False),s) - source_object.emit_to(s) -GLOBAL_CMD_TABLE.add_command("@group", cmd_group,auto_help=True) - + string = "\nYour (%s's) group memberships: %s" % (source_object.get_name(show_dbref=False), string) + source_object.emit_to(string) +GLOBAL_CMD_TABLE.add_command("@group", cmd_group) +GLOBAL_CMD_TABLE.add_command("@groups", cmd_group, help_category="System") def cmd_help(command): """ - Help command - Usage: help + help - view help database + + Usage: + help Examples: help index help topic - help 2 + help 345 - Shows the available help on . Use without to - get the help index. If more than one topic match your query, you will get a + Shows the available help on . Use without to get the help + index. If more than one topic match your query, you will get a list of topics to choose between. You can also supply a help entry number directly if you know it. - - <> - Help command extra functions for staff: - - help index - the normal index - help index_staff - show only help files unique to staff - help index_player - show only help files visible to all players - - The help command has a range of staff-only switches for manipulating the - help data base: - - help/add : - add/replace help topic with text (staff only) - help/append : - add text to the end of a topic (staff only) - (use the /newline switch to add a new paragraph - to your help entry.) - help/delete - delete help topic (staff only) - - Note: further switches are /force and /staff. /force is used together with /add to - always create a help entry, also when they partially match a previous entry. /staff - makes the help file visible to staff only. The /append switch can be used to change the - /staff setting of an existing help file if required. - - The entry supports markup to automatically divide the help text into - sub-entries. These are started by the markup < > (with no spaces - between the << >>), which will create a new subsectioned entry 'MyTopic' for all - text to follow it. All subsections to be added this way are automatically - referred to in the footer of each help entry. Normally the subsections inherit the - staff_only flag from the main entry (so if this is a staff-only help, all subentries - will also be staff-only and vice versa). You can override this behaviour using the - alternate forms < > and < >. - """ - + source_object = command.source_object - topicstr = command.command_argument - switches = command.command_switches + topicstr = command.command_argument if not command.command_argument: #display topic index if just help command is given - if not switches: - topicstr = "topic" - else: - #avoid applying things to "topic" by mistake - source_object.emit_to("You have to supply a topic.") - return - - elif len(topicstr) < 2 and not topicstr.isdigit(): + topicstr = "index" + + if len(topicstr) < 2 and not topicstr.isdigit(): #check valid query source_object.emit_to("Your search query must be at least two letters long.") return - #speciel help index names. These entries are dynamically - #created upon request. - if topicstr == 'index': - #the normal index, affected by permissions - edit_help.get_help_index(source_object) + # speciel help index names. These entries are dynamically + # created upon request. + if topicstr in ['topic','topics']: + # the full index, affected by permissions + text = helpsystem.viewhelp.index_full(source_object) + text = " \nHELP TOPICS (By Category):\n\r%s" % text + source_object.emit_to(text) return - elif topicstr == 'index_staff': - #allows staff to view only staff-specific help - edit_help.get_help_index(source_object,filter='staff') - return - elif topicstr == 'index_player': - #allows staff to view only the help files a player sees - edit_help.get_help_index(source_object,filter='player') - return - - #handle special switches - force_create = 'for' in switches or 'force' in switches - staff_only = 'sta' in switches or 'staff' in switches + elif 'index' in topicstr: + # view the category index + text = helpsystem.viewhelp.index_categories() + text = " \nHELP CATEGORIES (try 'help ' or 'help topics'):\n\r\n\r%s" % text + source_object.emit_to(text) + return - if 'add' in switches: - #try to add/replace help text for a command - if not source_object.has_perm("helpsys.add_help"): - source_object.emit_to(defines_global.NOPERMS_MSG) - return - spl = (topicstr.split(':',1)) - if len(spl) != 2: - source_object.emit_to("Format is help/add :") - return - topicstr = spl[0] - text = spl[1] - topics = edit_help.add_help(topicstr,text,staff_only,force_create,source_object) - if not topics: - source_object.emit_to("No topic(s) added due to errors. Check syntax and that you don't have duplicate subtopics with the same name defined.") + # not a special help index entry. Do a search for the help entry. + topics = HelpEntry.objects.find_topicmatch(source_object, topicstr) + + # display help entry or handle no/multiple matches + + string = "" + if not topics: + # no matches. + + # try to see if it is matching the name of a category. If so, + # show the topics for this category. + text = helpsystem.viewhelp.index_category(source_object, topicstr) + if text: + # We have category matches, display the index and exit. + string = "\n%s%s%s\n\r\n\r%s" % ("---", " Help topics in category %s: " % \ + topicstr.capitalize(), "-"* (30-len(topicstr)), text) + source_object.emit_to(string) return - elif len(topics)>1: - source_object.emit_to("Added or replaced multiple help entries.") + + # at this point we just give a not-found error and give suggestions. + topics = HelpEntry.objects.find_topicsuggestions(source_object, + topicstr) + if topics: + if len(topics) > 3: + topics = topics[:3] + string += "\n\rMatching similarly named topics (use name or number to refine search):" + for entry in topics: + string += "\n %i.%s" % (entry.id, entry.topicname) else: - source_object.emit_to("Added or replaced help entry for %s." % topicstr ) + string += "No matching topics found, please refine your search." - elif 'append' in switches or 'app' in switches: - #append text to a help entry - if not source_object.has_perm("helpsys.add_help"): - source_object.emit_to(defines_global.NOPERMS_MSG) - return - spl = (topicstr.split(':',1)) - if len(spl) != 2: - source_object.emit_to("""Format is help/append : - Use the /newline switch to make a new paragraph.""") - return - topicstr = spl[0] - text = spl[1] - topics = HelpEntry.objects.find_topicmatch(source_object, topicstr) - if len(topics) == 1: - newtext = topics[0].get_entrytext_ingame() - if 'newl' in switches or 'newline' in switches: - newtext += "\n\r\n\r%s" % text - else: - newtext += "\n\r%s" % text - topics = edit_help.add_help(topicstr,newtext,staff_only,force_create,source_object) - if topics: - source_object.emit_to("Appended text to help entry for %s." % topicstr) - - elif 'del' in switches or 'delete' in switches: - #delete a help entry - if not source_object.has_perm("helpsys.del_help"): - source_object.emit_to(defines_global.NOPERMS_MSG) - return - topics = edit_help.del_help(source_object,topicstr) - if type(topics) != type(list()): - source_object.emit_to("Help entry '%s' deleted." % topicstr) - return - - else: - #no switch; just try to get the help as normal - topics = HelpEntry.objects.find_topicmatch(source_object, topicstr) - - #display help entry or handle no/multiple matches - - if len(topics) == 0: - source_object.emit_to("No matching topics found, please refine your search.") - suggestions = HelpEntry.objects.find_topicsuggestions(source_object, - topicstr) - if len(suggestions) > 0: - source_object.emit_to("Matching similarly named topics:") - for result in suggestions: - source_object.emit_to(" %s" % (result,)) - source_object.emit_to("You may type 'help <#>' to see any of these topics.") + elif len(topics) > 1: - source_object.emit_to("More than one match found:") + # multiple matches found + string += "More than one match found:" for result in topics: - source_object.emit_to(" %3d. %s" % (result.id, result.get_topicname())) - source_object.emit_to("You may type 'help <#>' to see any of these topics.") + string += " %3d. %s" % (result.id, result.get_topicname()) + else: + # a single match found topic = topics[0] - source_object.emit_to("\n\r "+ topic.get_entrytext_ingame()) -GLOBAL_CMD_TABLE.add_command("help", cmd_help, auto_help=True) + header = "--- Help entry for '%s' (%s category) " % (topic.get_topicname(), + topic.get_category()) + header = "%s%s" % (header, "-" * (80-len(header))) + string += "\n\r%s\n\r\n\r%s" % (header, topic.get_entrytext_ingame()) + + # add the 'See also:' footer + topics = HelpEntry.objects.find_topicsuggestions(source_object, + topicstr) + if topics: + if len(topics) > 5: + topics = topics[:5] + topics = [str(topic.topicname) for topic in topics ] + string += "\n\r\n\r" + " " * helpsystem.viewhelp.indent + \ + "See also: " + ", ".join(topics) + + source_object.emit_to(string) +GLOBAL_CMD_TABLE.add_command("help", cmd_help) diff --git a/src/commands/imc2.py b/src/commands/imc2.py index 728d3267be..a673acbfae 100644 --- a/src/commands/imc2.py +++ b/src/commands/imc2.py @@ -1,14 +1,8 @@ """ IMC2 user and administrative commands. """ -from time import time from django.conf import settings -from src.config.models import ConfigValue -from src.objects.models import Object -from src import defines_global -from src import ansi from src import comsys -from src.util import functions_general from src.cmdtable import GLOBAL_CMD_TABLE from src.ansi import parse_ansi from src.imc2.imc_ansi import IMCANSIParser @@ -20,7 +14,12 @@ from src.channels.models import CommChannel def cmd_imcwhois(command): """ - Shows a player's inventory. + imcwhois + + Usage: + imcwhois + + IMC2 command. Shows a player's inventory. """ source_object = command.source_object if not command.command_argument: @@ -30,10 +29,15 @@ def cmd_imcwhois(command): source_object.emit_to("Sending IMC whois request. If you receive no response, no matches were found.") packet = IMC2PacketWhois(source_object, command.command_argument) imc2_conn.IMC2_PROTOCOL_INSTANCE.send_packet(packet) -GLOBAL_CMD_TABLE.add_command("imcwhois", cmd_imcwhois) +GLOBAL_CMD_TABLE.add_command("imcwhois", cmd_imcwhois, help_category="Comms") def cmd_imcansi(command): """ + imcansi + + Usage: + imcansi + Test IMC ANSI conversion. """ source_object = command.source_object @@ -43,20 +47,30 @@ def cmd_imcansi(command): else: retval = parse_ansi(command.command_argument, parser=IMCANSIParser()) source_object.emit_to(retval) -GLOBAL_CMD_TABLE.add_command("imcansi", cmd_imcansi) +GLOBAL_CMD_TABLE.add_command("imcansi", cmd_imcansi, help_category="Comms") def cmd_imcicerefresh(command): """ - Semds an ice-refresh packet. + imcicerefresh + + Usage: + imcicerefresh + + IMC2: Semds an ice-refresh packet. """ source_object = command.source_object packet = IMC2PacketIceRefresh() imc2_conn.IMC2_PROTOCOL_INSTANCE.send_packet(packet) source_object.emit_to("Sent") -GLOBAL_CMD_TABLE.add_command("imcicerefresh", cmd_imcicerefresh) +GLOBAL_CMD_TABLE.add_command("imcicerefresh", cmd_imcicerefresh, help_category="Comms") def cmd_imcchanlist(command): """ + imcchanlist + + Usage: + imcchanlist + Shows the list of cached channels from the IMC2 Channel list. """ source_object = command.source_object @@ -73,10 +87,15 @@ def cmd_imcchanlist(command): channel.policy) retval += '%s channels found.' % len(IMC2_CHANLIST.chan_list) source_object.emit_to(retval) -GLOBAL_CMD_TABLE.add_command("imcchanlist", cmd_imcchanlist) +GLOBAL_CMD_TABLE.add_command("imcchanlist", cmd_imcchanlist, help_category="Comms") def cmd_imclist(command): """ + imclist + + Usage: + imclist + Shows the list of cached games from the IMC2 Mud list. """ source_object = command.source_object @@ -88,10 +107,15 @@ def cmd_imclist(command): retval += '%s\n\r' % mudline[:78] retval += '%s active MUDs found.' % len(IMC2_MUDLIST.mud_list) source_object.emit_to(retval) -GLOBAL_CMD_TABLE.add_command("imclist", cmd_imclist) +GLOBAL_CMD_TABLE.add_command("imclist", cmd_imclist, help_category="Comms") def cmd_imcstatus(command): """ + imcstatus + + Usage: + imcstatus + Shows some status information for your IMC2 connection. """ source_object = command.source_object @@ -118,21 +142,24 @@ def cmd_imcstatus(command): source_object.emit_to(retval) GLOBAL_CMD_TABLE.add_command("imcstatus", cmd_imcstatus, - priv_tuple=('imc2.admin_imc_channels',)) + priv_tuple=('imc2.admin_imc_channels',), help_category="Comms") def cmd_IMC2chan(command): """ - @imc2chan IMCServer:IMCchannel channel + @imc2chan - Links an IMC channel to an existing - evennia channel. You can link as many existing + Usage: + @imc2chan : + + Links an IMC channel to an existing evennia + channel. You can link as many existing evennia channels as you like to the IMC channel this way. Running the command with an existing mapping will re-map the channels. - Use 'imcchanlist' to get a list of IMC channels and servers. - Note that both are case sensitive. + Use 'imcchanlist' to get a list of IMC channels and + servers. Note that both are case sensitive. """ source_object = command.source_object if not settings.IMC2_ENABLED: @@ -179,6 +206,6 @@ def cmd_IMC2chan(command): outstring += "Mapping set: %s." % mapping source_object.emit_to(outstring) -GLOBAL_CMD_TABLE.add_command("@imc2chan",cmd_IMC2chan,auto_help=True,staff_help=True, - priv_tuple=("imc2.admin_imc_channels",)) +GLOBAL_CMD_TABLE.add_command("@imc2chan",cmd_IMC2chan, + priv_tuple=("imc2.admin_imc_channels",), help_category="Comms") diff --git a/src/commands/info.py b/src/commands/info.py index e364ecc637..e3c0779923 100644 --- a/src/commands/info.py +++ b/src/commands/info.py @@ -17,25 +17,41 @@ from src.cmdtable import GLOBAL_CMD_TABLE def cmd_version(command): """ - Version info command. + @version - game version + + Usage: + @version + + Display the game version info """ retval = "-"*50 +"\n\r" retval += " Evennia %s\n\r" % (defines_global.EVENNIA_VERSION,) retval += " Django %s\n\r" % (django.get_version()) retval += "-"*50 command.source_object.emit_to(retval) -GLOBAL_CMD_TABLE.add_command("version", cmd_version), +GLOBAL_CMD_TABLE.add_command("@version", cmd_version, help_category="Admin"), def cmd_time(command): """ + @time + + Usage: + @time + Server local time. """ command.source_object.emit_to('Current server time : %s' % (time.strftime('%a %b %d %H:%M:%S %Y (%Z)', time.localtime(),))) -GLOBAL_CMD_TABLE.add_command("time", cmd_time), +GLOBAL_CMD_TABLE.add_command("@time", cmd_time, priv_tuple=("genperms.game_info",), + help_category="Admin") def cmd_uptime(command): """ + @uptime + + Usage: + @uptime + Server uptime and stats. """ source_object = command.source_object @@ -54,11 +70,18 @@ def cmd_uptime(command): loadavg = os.getloadavg() source_object.emit_to('Server load (1 min) : %.2f' % loadavg[0]) -GLOBAL_CMD_TABLE.add_command("uptime", cmd_uptime), +GLOBAL_CMD_TABLE.add_command("@uptime", cmd_uptime, priv_tuple=("genperms.game_info",), + help_category="Admin") def cmd_list(command): - """ - Shows some game related information. + """ + @list - list info + + Usage: + @list commands | flags | process + + Shows game related information depending + on which argument is given. """ server = command.session.server source_object = command.source_object @@ -99,10 +122,15 @@ def cmd_list(command): source_object.emit_to("Flags: "+" ".join(flags.SERVER_FLAGS)) else: source_object.emit_to(msg_invalid) -GLOBAL_CMD_TABLE.add_command("@list", cmd_list,priv_tuple=("genperms.game_info",)), +GLOBAL_CMD_TABLE.add_command("@list", cmd_list,priv_tuple=("genperms.game_info",), help_category="Admin") def cmd_ps(command): """ + @ps - list processes + + Usage + @ps + Shows the process/event table. """ source_object = command.source_object @@ -115,13 +143,23 @@ def cmd_ps(command): event.description)) source_object.emit_to("Totals: %d interval events" % (len(scheduler.schedule),)) GLOBAL_CMD_TABLE.add_command("@ps", cmd_ps, - priv_tuple=("genperms.process_control")), + priv_tuple=("genperms.process_control",), help_category="Admin") def cmd_stats(command): """ + @stats - show object stats + + Usage: + @stats + + Example: + @stats + -> + 4012 objects = 144 rooms, 212 exits, 613 things, 1878 players. (1165 garbage) + Shows stats about the database. - 4012 objects = 144 rooms, 212 exits, 613 things, 1878 players. (1165 garbage) """ + stats_dict = Object.objects.object_totals() command.source_object.emit_to( "%d objects = %d rooms, %d exits, %d things, %d players. (%d garbage)" % @@ -131,4 +169,4 @@ def cmd_stats(command): stats_dict["things"], stats_dict["players"], stats_dict["garbage"])) -GLOBAL_CMD_TABLE.add_command("@stats", cmd_stats, priv_tuple=("genperms.game_info",)), +GLOBAL_CMD_TABLE.add_command("@stats", cmd_stats, priv_tuple=("genperms.game_info",), help_category="Admin"), diff --git a/src/commands/irc.py b/src/commands/irc.py index 61c31232ec..406fbecbac 100644 --- a/src/commands/irc.py +++ b/src/commands/irc.py @@ -11,7 +11,10 @@ from src.channels.models import CommChannel def cmd_IRC2chan(command): """ - @irc2chan IRCchannel channel + @irc2chan - link irc to ingame channel + + Usage: + @irc2chan <#IRCchannel> Links an IRC channel (including #) to an existing evennia channel. You can link as many existing @@ -55,12 +58,16 @@ def cmd_IRC2chan(command): outstring += "Mapping set: %s." % mapping source_object.emit_to(outstring) -GLOBAL_CMD_TABLE.add_command("@irc2chan",cmd_IRC2chan,auto_help=True,staff_help=True, - priv_tuple=("irc.admin_irc_channels",)) +GLOBAL_CMD_TABLE.add_command("@irc2chan",cmd_IRC2chan, + priv_tuple=("irc.admin_irc_channels",), + help_category="Comms") def cmd_IRCjoin(command): """ - @ircjoin IRCchannel + @ircjoin - join a new irc channel + + Usage: + @ircjoin <#IRCchannel> Attempts to connect a bot to a new IRC channel (don't forget that IRC channels begin with a #). @@ -99,19 +106,25 @@ def cmd_IRCjoin(command): # irc.setName("%s:%s" % ("IRC",channel)) # irc.setServiceParent(mud_service.service_collection) -GLOBAL_CMD_TABLE.add_command("@ircjoin",cmd_IRCjoin,auto_help=True, - staff_help=True, - priv_tuple=("irc.admin_irc_channels",)) +GLOBAL_CMD_TABLE.add_command("@ircjoin",cmd_IRCjoin, + priv_tuple=("irc.admin_irc_channels",), + help_category="Comms") def cmd_IRCchanlist(command): """ ircchanlist + Usage: + ircchanlist + Lists all externally available IRC channels. """ source_object = command.source_object s = "Available IRC channels:" for c in IRC_CHANNELS: - s += "\n %s \t(nick '%s') on %s" % (c.factory.channel,c.factory.nickname,c.factory.network,) + s += "\n %s \t(nick '%s') on %s" % (c.factory.channel, + c.factory.nickname, + c.factory.network,) source_object.emit_to(s) -GLOBAL_CMD_TABLE.add_command("ircchanlist", cmd_IRCchanlist, auto_help=True) +GLOBAL_CMD_TABLE.add_command("ircchanlist", cmd_IRCchanlist, + help_category="Comms") diff --git a/src/commands/objmanip.py b/src/commands/objmanip.py index c5f8475522..b66ed31cc5 100644 --- a/src/commands/objmanip.py +++ b/src/commands/objmanip.py @@ -9,10 +9,21 @@ from src import locks from src import ansi from src.cmdtable import GLOBAL_CMD_TABLE from src import defines_global +from src.ansi import ANSITable def cmd_teleport(command): """ - Teleports an object somewhere. + teleport + + Usage: + teleport/switch [ = ] + + Switches: + quiet - don't inform the source and target + locations about the move. + + Teleports an object somewhere. If no object is + given we are teleporting ourselves. """ source_object = command.source_object @@ -66,10 +77,15 @@ def cmd_teleport(command): source_object.move_to(target_obj, quiet=tel_quietly) GLOBAL_CMD_TABLE.add_command("@teleport", cmd_teleport, - priv_tuple=("objects.teleport",)) + priv_tuple=("objects.teleport",), help_category="Building") def cmd_alias(command): """ + @alias + + Usage: + @alias = + Assigns an alias to a player object for ease of paging, etc. """ source_object = command.source_object @@ -118,8 +134,17 @@ GLOBAL_CMD_TABLE.add_command("@alias", cmd_alias) def cmd_wipe(command): """ - Wipes an object's attributes, or optionally only those matching a search - string. + @wipe - clears attributes + + Usage: + @wipe [/attribute-wildcard] + + Example: + @wipe box + @wipe box/colour + + Wipes all of an object's attributes, or optionally only those + matching the given attribute-wildcard search string. """ source_object = command.source_object attr_search = False @@ -166,11 +191,21 @@ def cmd_wipe(command): target_obj.clear_attribute(attr.get_name()) source_object.emit_to("%s - %d attributes wiped." % (target_obj.get_name(), len(attr_matches))) -GLOBAL_CMD_TABLE.add_command("@wipe", cmd_wipe,priv_tuple=("objects.wipe",)) +GLOBAL_CMD_TABLE.add_command("@wipe", cmd_wipe,priv_tuple=("objects.wipe",), + help_category="Building") def cmd_set(command): """ - Sets flags or attributes on objects. + @set - set attributes and flags + + Usage: + @set = + @set = : + @set = ! + @set = : + + Sets flags or attributes on objects. The two last forms + above unsets the flag and clears the attribute, respectively. """ source_object = command.source_object args = command.command_argument @@ -251,16 +286,26 @@ def cmd_set(command): s += '\nFlag %s=%s set.' % (target_name, flag.upper()) target.set_flag(flag, True) source_object.emit_to(s[1:]) -GLOBAL_CMD_TABLE.add_command("@set", cmd_set, priv_tuple=("objects.modify_attributes",)) +GLOBAL_CMD_TABLE.add_command("@set", cmd_set, priv_tuple=("objects.modify_attributes",), + help_category="Building") def cmd_cpattr(command): """ - copy an attribute to another object - - @cpattr / = / [,/,/,...] - @cpattr / = [,,,...] - @cpattr = / [,/,/,...] - @cpattr = [,,,...] + @cpattr - copy attributes + + Usage: + @cpattr / = / [,/,/,...] + @cpattr / = [,,,...] + @cpattr = / [,/,/,...] + @cpattr = [,,,...] + + Example: + @cpattr coolness = Anna/chillout, Anna/nicety, Tom/nicety + -> + copies the coolness attribute (defined on yourself), to attributes + on Anna and Tom. + + Copy the attribute one object to one or more attributes on another object. """ source_object = command.source_object args = command.command_argument @@ -333,14 +378,17 @@ def cmd_cpattr(command): to_objname, to_attr) source_object.emit_to(s) GLOBAL_CMD_TABLE.add_command("@cpattr", cmd_cpattr, - priv_tuple=("objects.modify_attributes",)) + priv_tuple=("objects.modify_attributes",), help_category="Building") def cmd_mvattr(command): """ - @mvattr =,[,[, =,[,[, + Searches for an object of a particular name. """ source_object = command.source_object @@ -421,13 +475,14 @@ def cmd_find(command): else: source_object.emit_to("No name matches found for: %s" % (searchstring,)) GLOBAL_CMD_TABLE.add_command("@find", cmd_find, - priv_tuple=("objects.info",)) + priv_tuple=("objects.info",), help_category="Building") def cmd_create(command): """ - @create + @create - create new objects - Usage: @create[/drop] objname [:parent] + Usage: + @create[/drop] objname [:parent] switch: drop - automatically drop the new object into your current location (this is not echoed) @@ -477,15 +532,20 @@ def cmd_create(command): GLOBAL_CMD_TABLE.add_command("@create", cmd_create, - priv_tuple=("objects.create",),auto_help=True,staff_help=True) + priv_tuple=("objects.create",), + help_category="Building") def cmd_copy(command): - """Usage: - @copy[/reset] [new_name] [, new_location] + """ + @copy - copy objects + + Usage: + @copy[/reset] [new_name] [, new_location] switch: - reset - make a 'clean' copy, without any changes that might have happened to the - original since it was first created. + reset - make a 'clean' copy off the object's script parent, thus + removing any changes that might have been made to the original + since it was first created. Create an identical copy of an object. """ @@ -542,12 +602,13 @@ def cmd_copy(command): reset_text = " (using default attrs/flags)" source_object.emit_to("Copied object '%s'%s%s%s." % (objname,name_text,loc_text,reset_text)) GLOBAL_CMD_TABLE.add_command("@copy", cmd_copy, - priv_tuple=("objects.create",),auto_help=True,staff_help=True) - - - + priv_tuple=("objects.create",), help_category="Building") + def cmd_nextfree(command): - """Usage: + """ + @nextfree + + Usage: @nextfree Returns the next free object number. @@ -555,10 +616,12 @@ def cmd_nextfree(command): nextfree = Object.objects.get_nextfree_dbnum() command.source_object.emit_to("Next free object number: #%s" % nextfree) GLOBAL_CMD_TABLE.add_command("@nextfree", cmd_nextfree, - priv_tuple=("objects.info",),auto_help=True,staff_help=True) + priv_tuple=("objects.info",), help_category="Building") def cmd_open(command): - """@open + """ + @open - create new exit + Usage: @open [:parent] [= [, [:parent]]] @@ -670,15 +733,17 @@ def cmd_open(command): source_object.emit_to("Created exit%s back from %s named %s." % \ (ptext, destination, new_object)) GLOBAL_CMD_TABLE.add_command("@open", cmd_open, - priv_tuple=("objects.dig",),auto_help=True,staff_help=True) + priv_tuple=("objects.dig",), help_category="Building") def cmd_chown(command): """ - Changes the ownership of an object. The new owner specified must be a - player object. + @chown - change ownerships - Forms: - @chown = + Usage: + @chown = + + Changes the ownership of an object. The new owner specified must be a + player object. """ source_object = command.source_object @@ -721,15 +786,19 @@ def cmd_chown(command): # We haven't provided a target. source_object.emit_to("Who should be the new owner of the object?") return -GLOBAL_CMD_TABLE.add_command("@chown", cmd_chown, priv_tuple=("objects.modify_attributes","objects.admin_ownership")) +GLOBAL_CMD_TABLE.add_command("@chown", cmd_chown, priv_tuple=("objects.modify_attributes", + "objects.admin_ownership"), + help_category="Building" ) def cmd_chzone(command): """ + @chzone - set zones + + Usage: + @chzone = + Changes an object's zone. The specified zone may be of any object type, but will typically be a THING. - - Forms: - @chzone = """ source_object = command.source_object @@ -773,10 +842,13 @@ def cmd_chzone(command): # We haven't provided a target zone. source_object.emit_to("What should the object's zone be set to?") return -GLOBAL_CMD_TABLE.add_command("@chzone", cmd_chzone, priv_tuple=("objects.dig",)) +GLOBAL_CMD_TABLE.add_command("@chzone", cmd_chzone, priv_tuple=("objects.dig",), + help_category="Building" ) def cmd_link(command): - """@link + """ + @link - connect objects + Usage: @link = @link = @@ -864,13 +936,17 @@ def cmd_link(command): else: source_object.emit_to("You set the home location of %s to %s%s." % (obj, destination, ohome_text)) GLOBAL_CMD_TABLE.add_command("@link", cmd_link, - priv_tuple=("objects.dig",), auto_help=True, staff_help=True) + priv_tuple=("objects.dig",), help_category="Building") def cmd_unlink(command): """ - Unlinks an object. - - @unlink + @unlink - unconnect objects + + Usage: + @unlink + + Unlinks an object, for example an exit, disconnecting + it from whatever it was connected to. """ source_object = command.source_object @@ -890,15 +966,19 @@ def cmd_unlink(command): target_obj.set_home(None) source_object.emit_to("You have unlinked %s." % target_obj.get_name()) GLOBAL_CMD_TABLE.add_command("@unlink", cmd_unlink, - priv_tuple=("objects.dig",)) + priv_tuple=("objects.dig",), help_category="Building") def cmd_dig(command): - """@dig + """ + @dig - build and connect new rooms + Usage: @dig[/switches] roomname [:parent] [= exit_to_there [: parent][;alias]] [, exit_to_here [: parent][;alias]] - switches: + + Switches: teleport - move yourself to the new room - example: + + Example: @dig kitchen = north; n, south; s This command is a convenient way to build rooms quickly; it creates the new room and you can optionally @@ -1018,13 +1098,16 @@ def cmd_dig(command): source_object.move_to(new_room) GLOBAL_CMD_TABLE.add_command("@dig", cmd_dig, - priv_tuple=("objects.dig",), auto_help=True, staff_help=True) + priv_tuple=("objects.dig",), help_category="Building") def cmd_name(command): """ - Handle naming an object. - - @name = + @name - name objects + + Usage: + @name = + + Handle naming an object. """ source_object = command.source_object @@ -1060,10 +1143,16 @@ def cmd_name(command): source_object.emit_to("You have renamed %s to %s." % (target_obj, ansi_name)) target_obj.set_name(new_name) -GLOBAL_CMD_TABLE.add_command("@name", cmd_name) +GLOBAL_CMD_TABLE.add_command("@name", cmd_name, priv_tuple=("objects.create",), + help_category="Building") def cmd_description(command): """ + @desc + + Usage: + @desc [obj =] + Set an object's description. """ source_object = command.source_object @@ -1099,20 +1188,21 @@ def cmd_description(command): else: source_object.emit_to("%s - description set." % target_obj) target_obj.set_attribute('desc', new_desc) -GLOBAL_CMD_TABLE.add_command("@describe", cmd_description) +GLOBAL_CMD_TABLE.add_command("@describe", cmd_description, priv_tuple=("objects.create",), + help_category="Building") def cmd_recover(command): """ - @recover - - Recovers @destroyed non-player objects. + @recover - undo object deletion Usage: - @recover[/switches] [obj [,obj2, ...]] + @recover[/switches] [obj [,obj2, ...]] switches: - ROOM - recover as ROOM type instead of THING - EXIT - recover as EXIT type instead of THING + ROOM - recover as ROOM type instead of THING + EXIT - recover as EXIT type instead of THING + + Recovers @destroyed non-player objects. If no argument is given, a list of all recoverable objects will be given. @@ -1165,14 +1255,12 @@ def cmd_recover(command): GLOBAL_CMD_TABLE.add_command("@recover", cmd_recover, - priv_tuple=("objects.create",),auto_help=True,staff_help=True) + priv_tuple=("objects.create",), help_category="Building") def cmd_destroy(command): """ - @destroy - - Destroys one or many objects. - + @destroy - send objects to trashbin + Usage: @destroy[/] obj [,obj2, obj3, ...] @@ -1182,6 +1270,7 @@ def cmd_destroy(command): switch overrides this safety. instant|now - Destroy the object immediately, without delay. + Destroys one or many objects. The objects are set to GOING and will be permanently destroyed next time the system does cleanup. Until then non-player objects can still be saved by using the @recover command. The contents of a room will be moved out before it is destroyed, @@ -1244,19 +1333,23 @@ 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=("objects.create",),auto_help=True,staff_help=True) + priv_tuple=("objects.create",), help_category="Building") def cmd_lock(command): - """@lock + """ + @lock - limit use of objects + Usage: @lock[/switch] [:type] [= [,key2,key3,...]] - switches: + Switches: add - add a lock (default) from object del - remove a lock from object list - view all locks on object (default) type: DefaultLock - the default lock type (default) + UseLock - prevents usage of objects' commands + EnterLock - blocking objects from entering the object Locks an object for everyone except those matching the keys. The keys can be of the following types (and searched in this order): @@ -1433,4 +1526,137 @@ def cmd_lock(command): source_object.emit_to("Added lock '%s' to %s with keys%s." % (ltype, obj.get_name(), kstring)) obj.set_attribute("LOCKS",obj_locks) -GLOBAL_CMD_TABLE.add_command("@lock", cmd_lock, priv_tuple=("objects.create",),auto_help=True, staff_help=True) +GLOBAL_CMD_TABLE.add_command("@lock", cmd_lock, priv_tuple=("objects.create",), help_category="Building") + +def cmd_examine(command): + """ + examine - detailed info on objects + + Usage: + examine [] + + The examine command shows detailed game info about an + object; which attributes/flags it has and what it + contains. If object is not specified, the current + location is examined. + """ + source_object = command.source_object + attr_search = False + + if not command.command_argument: + # If no arguments are provided, examine the invoker's location. + target_obj = source_object.get_location() + else: + # Look for a slash in the input, indicating an attribute search. + attr_split = command.command_argument.split("/", 1) + + # If the splitting by the "/" character returns a list with more than 1 + # entry, it's an attribute match. + if len(attr_split) > 1: + attr_search = True + # Strip the object search string from the input with the + # object/attribute pair. + obj_searchstr = attr_split[0] + attr_searchstr = attr_split[1].strip() + + # Protect against stuff like: ex me/ + if attr_searchstr == '': + source_object.emit_to('No attribute name provided.') + return + else: + # No slash in argument, just examine an object. + obj_searchstr = command.command_argument + + # Resolve the target object. + target_obj = source_object.search_for_object(obj_searchstr) + # Use search_for_object to handle duplicate/nonexistant results. + if not target_obj: + return + + # If the user doesn't control the object, just look at it instead. + if not source_object.controls_other(target_obj, builder_override=True): + command.command_string = 'look' + cmd_look(command) + return + + if attr_search: + # Player did something like: examine me/* or examine me/TE*. Return + # each matching attribute with its value. + attr_matches = target_obj.attribute_namesearch(attr_searchstr) + if attr_matches: + for attribute in attr_matches: + source_object.emit_to(attribute.get_attrline()) + else: + source_object.emit_to("No matching attributes found.") + else: + + # Player is examining an object. Return a full readout of attributes, + # along with detailed information about said object. + + string = "" + newl = "\r\n" + # Format the examine header area with general flag/type info. + + string += str(target_obj.get_name(fullname=True)) + newl + string += str("Type: %s Flags: %s" % (target_obj.get_type(), + target_obj.get_flags())) + newl + string += str("Owner: %s " % target_obj.get_owner()) + newl + string += str("Zone: %s" % target_obj.get_zone()) + newl + string += str("Parent: %s " % target_obj.get_script_parent()) + newl + + locks = target_obj.get_attribute_value("LOCKS") + if locks and "%s" % locks: + string += str("Locks: %s" % locks) + newl + + # Contents container lists for sorting by type. + con_players = [] + con_things = [] + con_exits = [] + + # Break each object out into their own list. + for obj in target_obj.get_contents(): + if obj.is_player(): + con_players.append(obj) + elif obj.is_exit(): + con_exits.append(obj) + elif obj.is_thing(): + con_things.append(obj) + + # Render the object's home or destination (for exits). + if not target_obj.is_room(): + if target_obj.is_exit(): + # The Home attribute on an exit is really its destination. + string += str("Destination: %s" % target_obj.get_home()) + newl + else: + # For everything else, home is home. + string += str("Home: %s" % target_obj.get_home()) + newl + # This obviously isn't valid for rooms. + string += str("Location: %s" % target_obj.get_location()) + newl + + # Render other attributes + for attribute in target_obj.get_all_attributes(): + string += str(attribute.get_attrline()) + newl + + # Render Contents display. + if con_players or con_things: + string += str("%sContents:%s" % (ANSITable.ansi["hilite"], + ANSITable.ansi["normal"])) + for player in con_players: + string += str(' %s' % newl + player.get_name(fullname=True)) + for thing in con_things: + string += str(' %s' % newl + thing.get_name(fullname=True)) + + # Render Exists display. + if con_exits: + string += str("%sExits:%s" % (newl + ANSITable.ansi["hilite"], + ANSITable.ansi["normal"])) + for exit in con_exits: + string += str(' %s' % newl + exit.get_name(fullname=True)) + + # Send it all + source_object.emit_to(string) + +GLOBAL_CMD_TABLE.add_command("examine", cmd_examine, priv_tuple=("objects.info",)) + + + diff --git a/src/commands/paging.py b/src/commands/paging.py index b7926b7aac..e45fa35f05 100644 --- a/src/commands/paging.py +++ b/src/commands/paging.py @@ -2,7 +2,6 @@ Paging command and support functions. """ from src.objects.models import Object -from src import defines_global from src.cmdtable import GLOBAL_CMD_TABLE def get_last_paged_objects(source_object): @@ -31,7 +30,14 @@ def get_last_paged_objects(source_object): def cmd_page(command): """ - Send a message to target user (if online). + page - send private message + + Usage: + page [ = ] + + Send a message to target user (if online). If no + argument is given, you will instead see who was the last + person you paged to. """ source_object = command.source_object # Get the last paged person(s) @@ -127,4 +133,4 @@ def cmd_page(command): # Now set the LASTPAGED attribute source_object.set_attribute("LASTPAGED", ','.join( ["#%d" % (x.id) for x in targets])) -GLOBAL_CMD_TABLE.add_command("page", cmd_page, priv_tuple=('channels.page',)) +GLOBAL_CMD_TABLE.add_command("page", cmd_page, priv_tuple=('channels.page',), help_category="Comms") diff --git a/src/commands/parents.py b/src/commands/parents.py index 7f280c7082..adb1a95174 100644 --- a/src/commands/parents.py +++ b/src/commands/parents.py @@ -2,10 +2,14 @@ Contains commands for managing script parents. """ from src import scripthandler +from src import defines_global from src.cmdtable import GLOBAL_CMD_TABLE def cmd_scriptcache(command): - """Usage + """ + @scriptcache + + Usage @scriptcache Shows the contents of the script cache. @@ -20,12 +24,21 @@ def cmd_scriptcache(command): retval += "%d cached parents" % len(cache_dict) command.source_object.emit_to(retval) GLOBAL_CMD_TABLE.add_command("@scriptcache", cmd_scriptcache, - priv_tuple=("genperms.builder"), - auto_help=True,staff_help=True) + priv_tuple=("genperms.builder",), help_category="Admin") def cmd_parent(command): """ - Sets an object's script parent. + @parent - set script parent + + Usage: + @parent = + + Example: + @parent button = examples.red_button + + Sets an object's script parent. The parent must be identified + by its location using dot-notation pointing to the script + parent module. """ source_object = command.source_object @@ -80,5 +93,5 @@ def cmd_parent(command): (target_obj,current_parent)) GLOBAL_CMD_TABLE.add_command("@parent", cmd_parent, - priv_tuple=("genperms.builder")) + priv_tuple=("genperms.builder",), help_category="Building" ) diff --git a/src/commands/privileged.py b/src/commands/privileged.py index bc53de6c22..1c32a00964 100644 --- a/src/commands/privileged.py +++ b/src/commands/privileged.py @@ -3,21 +3,30 @@ This file contains commands that require special permissions to use. These are generally @-prefixed commands, but there are exceptions. """ -from django.contrib.auth.models import Permission, Group, User -from django.contrib.contenttypes.models import ContentType +from django.contrib.auth.models import Permission, Group from django.conf import settings from src.objects.models import Object -from src import defines_global -from src import ansi from src import session_mgr from src import comsys from src.scripthandler import rebuild_cache -from src.util import functions_general from src.cmdtable import GLOBAL_CMD_TABLE +from src.helpsys.models import HelpEntry +from src.helpsys import helpsystem def cmd_reload(command): """ - Reloads all modules. + @reload - reload game subsystems + + Usage: + @reload/switches + + Switches: + aliases - alias definitions + commands - the command modules + scripts, parents - the script parent modules + all + + Reloads all the identified subsystems. """ source_object = command.source_object switches = command.command_switches @@ -44,12 +53,17 @@ def cmd_reload(command): comsys.cemit_mudinfo("... all Command modules were reloaded.") GLOBAL_CMD_TABLE.add_command("@reload", cmd_reload, - priv_tuple=("genperms.process_control",)), + priv_tuple=("genperms.process_control",), help_category="Admin") GLOBAL_CMD_TABLE.add_command("@restart", cmd_reload, - priv_tuple=("genperms.process_control",)), + priv_tuple=("genperms.process_control",), help_category="Admin") def cmd_boot(command): """ + @boot + + Usage + @boot + Boot a player object from the server. """ source_object = command.source_object @@ -119,10 +133,16 @@ def cmd_boot(command): session_mgr.remove_session(boot) return GLOBAL_CMD_TABLE.add_command("@boot", cmd_boot, - priv_tuple=("genperms.manage_players",)) + priv_tuple=("genperms.manage_players",), + help_category="Admin") def cmd_newpassword(command): """ + @newpassword + + Usage: + @newpassword = + Set a player's password. """ source_object = command.source_object @@ -157,10 +177,16 @@ def cmd_newpassword(command): target_obj.emit_to("%s has changed your password." % (source_object.get_name(show_dbref=False),)) GLOBAL_CMD_TABLE.add_command("@newpassword", cmd_newpassword, - priv_tuple=("genperms.manage_players",)) + priv_tuple=("genperms.manage_players",), + help_category="Admin") def cmd_home(command): """ + home + + Usage: + home + Teleport the player to their home. """ pobject = command.source_object @@ -174,8 +200,18 @@ GLOBAL_CMD_TABLE.add_command("home", cmd_home, def cmd_service(command): """ - Service management system. Allows for the listing, starting, and stopping - of services. + @service - manage services + + Usage: + @service[/switch] + + Switches: + start - activates a service + stop - stops a service + list - shows all available services + + Service management system. Allows for the listing, + starting, and stopping of services. """ source_object = command.source_object switches = command.command_switches @@ -242,17 +278,25 @@ def cmd_service(command): return GLOBAL_CMD_TABLE.add_command("@service", cmd_service, - priv_tuple=("genperms.process_control",)) + priv_tuple=("genperms.process_control",), + help_category="Admin") def cmd_shutdown(command): """ - Shut the server down gracefully. + @shutdown + + Usage: + @shutdown + + Shut the game server down gracefully. """ command.source_object.emit_to('Shutting down...') print 'Server shutdown by %s' % (command.source_object.get_name(show_dbref=False),) command.session.server.shutdown() GLOBAL_CMD_TABLE.add_command("@shutdown", cmd_shutdown, - priv_tuple=("genperms.process_control",)) + priv_tuple=("genperms.process_control",), + help_category="Admin") + # permission administration @@ -260,6 +304,7 @@ GLOBAL_CMD_TABLE.add_command("@shutdown", cmd_shutdown, # mess with, but which are not very useful from inside the game. While these # permissions are ok to use, we only show the permissions that we have defined # in our settings file in order to give better control. + APPS_NOSHOW = ("news","admin","auth","config","contentypes", "flatpages","news","sessions","sites") SETTINGS_PERM_NAMES = [] @@ -268,7 +313,9 @@ for apps in settings.PERM_ALL_DEFAULTS + settings.PERM_ALL_CUSTOM: SETTINGS_PERM_NAMES.append(permtuples[1]) def cmd_setperm(command): - """@setperm + """ + @setperm - set permissions + Usage: @setperm[/switch] [] = [] @@ -277,9 +324,9 @@ def cmd_setperm(command): del : delete a permission from list : list all permissions, or those set on - This command sets/clears individual permission bits on a user. - Use /list without any arguments to see all available permissions or those - defined on the argument. + This command sets/clears individual permission bits on a user. + Use /list without any arguments to see all available permissions or those + defined on the argument. """ source_object = command.source_object args = command.command_argument @@ -380,17 +427,21 @@ def cmd_setperm(command): obj.emit_to("%s removed your permission '%s'." % (source_object.get_name(show_dbref=False,no_ansi=True), permission.name)) GLOBAL_CMD_TABLE.add_command("@setperm", cmd_setperm, - priv_tuple=("auth.change_permission","genperms.admin_perm"), auto_help=True, staff_help=True) + priv_tuple=("auth.change_permission", + "genperms.admin_perm"), + help_category="Admin") def cmd_setgroup(command): - """@setgroup + """ + @setgroup - manage group memberships + Usage: @setgroup[/switch] [] [= ] - switches - add : add user to a group - del : remove user from a group - list : list all groups a user is part of, or list all available groups if no user is given + Switches: + add - add user to a group + del - remove user from a group + list - list all groups a user is part of, or list all available groups if no user is given Changes and views the group membership of a user. """ @@ -410,7 +461,8 @@ def cmd_setgroup(command): for p in g.permissions.all(): app = p.content_type.app_label if app not in APPS_NOSHOW: - s += "\n --- %s.%s%s\t%s" % (app, p.codename, (35 - len(app) - len(p.codename)) * " ", p.name) + s += "\n --- %s.%s%s\t%s" % (app, p.codename, + (35 - len(app) - len(p.codename)) * " ", p.name) source_object.emit_to(s) return #we have command arguments. @@ -484,5 +536,155 @@ def cmd_setgroup(command): obj.emit_to("%s removed you from group '%s'." % (source_object.get_name(show_dbref=False,no_ansi=True), group.name)) GLOBAL_CMD_TABLE.add_command("@setgroup", cmd_setgroup, - priv_tuple=("auth.change_group","genperms.admin_group"), auto_help=True, staff_help=True) + priv_tuple=("auth.change_group", + "genperms.admin_group"), + help_category="Admin") +def cmd_sethelp(command): + """ + @sethelp - edit the help database + + Usage: + @sethelp[/switches] [,category][(permissions)][:] + + Switches: + add - add or replace a new topic with text. + append - add text to the end of topic. + delete - remove help topic. + force - (used with add) create help topic also if the topic + already exists. + newl - (used with append) add a newline between the old + text and the appended text. + + Examples: + @sethelp/add throw : This throws something at ... + @sethelp/add throw, General (genperms.throwing) : This throws ... + @sethelp/add throw : 1st help entry + + [[@sethelp_markup]] + + @sethelp Help markup + + The entry in @sethelp supports markup to automatically divide the help text into + several sub-entries. The beginning of each new entry is marked in the form + + [ [Title, category, (privtuple)] ] (with no spaces between the square brackets) + + In the markup header, Title is mandatory, the other parts are optional. A new + help entry named Title will be created for each occurence. It is recommended + that the help entries should begin similarly since the system will then identify + them and better handle a list of recommended topics. + """ + + source_object = command.source_object + arg = command.command_argument + switches = command.command_switches + + if not arg or not switches: + source_object.emit_to("Usage: @sethelp/[add|del|append] [,category][:]") + return + + topicstr = "" + category = "" + text = "" + permtuple = () + + # analyze the argument + arg = arg.split(':', 1) + if len(arg) < 2: + # no : detected; this means we are deleting something. + topicstr = arg[0].strip() + else: + text = arg[1].strip() + # we have 4 possibilities: + # topicstr + # topicstr, category + # topicstr (perm1,perm2,...) + # topicstr, category, (perm1,perm2,...) + arg = arg[0].split('(',1) + if len(arg) > 1: + # we have a perm tuple + arg, permtuple = arg + try: + permtuple = permtuple.strip()[:-1] # cut last ')' + except IndexError: + source_object.emit_to("Malformed permission tuple. %s" % permtuple) + return + permtuple = tuple(permtuple.split(',')) + else: + # no perm tuple + arg = arg[0] + arg = arg.split(',', 1) + if len(arg) > 1: + # we have a category + category = arg[1].strip() + topicstr = arg[0].strip() + + if 'add' in switches: + # add a new help entry. + if not topicstr or not text: + source_object.emit_to("Usage: @sethelp/add [,category]:") + return + force_create = ('for' in switches) or ('force' in switches) + topics = helpsystem.edithelp.add_help_manual(source_object, topicstr, + category, text, + permissions=permtuple, + force=force_create) + if not topics: + return + if len(topics) == 1: + string = "The topic already exists. Use /force to overwrite it." + elif len(topics)>1: + string = "The following results are similar to '%s'." + string += " Make sure you are not misspelling, then " + string += "use the /force flag to create a new entry." + string += "\n ".join(topics) + source_object.emit_to(string) + + elif 'append' in switches or 'app' in switches: + # add text to the end of a help topic + if not topicstr or not text: + source_object.emit_to("Usage: @sethelp/append :") + return + # find the topic to append to + topics = HelpEntry.objects.find_topicmatch(source_object, topicstr) + if not topics: + source_object.emit_to("Help topic '%s' not found." % topicstr) + elif len(topics) > 1: + string = "Multiple matches to this topic. Refine your search." + string += "\n ".join(topics) + else: + # we have exactly one match. Extract all info from it, + # append the text and feed it back into the system. + newtext = topics[0].get_entrytext_ingame() + category = topics[0].category + perm_tuple = topics[0].canview + if perm_tuple: + perm_tuple = tuple(perm for perm in perm_tuple.split(',')) + + newl = "\n" + if 'newl' in switches or 'newline' in switches: + newl = "\n\n" + newtext += "%s%s" % (newl, text) + + topics = helpsystem.edithelp.add_help_manual(source_object, + topicstr, + category, + newtext, + perm_tuple, + force=True) + + elif 'del' in switches or 'delete' in switches: + #delete a help entry + topics = helpsystem.edithelp.del_help_manual(source_object, topicstr) + if not topics: + return + else: + string = "Multiple matches for '%s'. Please specify:" % topicstr + string += "\n ".join(topics) + +GLOBAL_CMD_TABLE.add_command("@sethelp", cmd_sethelp, + priv_tuple=("helpsys.add_help", + "helpsys.del_help", + "helpsys.admin_heelp"), + help_category="Admin") diff --git a/src/commands/search.py b/src/commands/search.py index 8f9ce6e851..4c6520170d 100644 --- a/src/commands/search.py +++ b/src/commands/search.py @@ -146,6 +146,11 @@ def build_query(source_object, search_query, search_player, search_type, def cmd_search(command): """ + search + + Usage: + search + Searches for owned objects as per MUX2. """ source_object = command.source_object @@ -232,4 +237,5 @@ def cmd_search(command): display_results(source_object, search_query) GLOBAL_CMD_TABLE.add_command("@search", cmd_search, - priv_tuple=("objects.info")), + priv_tuple=("objects.info",), + help_category="Building") diff --git a/src/commands/unloggedin.py b/src/commands/unloggedin.py index a993773c38..c7807f0228 100644 --- a/src/commands/unloggedin.py +++ b/src/commands/unloggedin.py @@ -3,7 +3,7 @@ Commands that are available from the connect screen. """ import traceback from django.contrib.auth.models import User -from src.objects.models import Attribute, Object +from src.objects.models import Object from src import defines_global from src.util import functions_general from src.cmdtable import GLOBAL_UNCON_CMD_TABLE @@ -47,7 +47,7 @@ def cmd_connect(command): else: uname = user.username session.login(user) -GLOBAL_UNCON_CMD_TABLE.add_command("connect", cmd_connect) +GLOBAL_UNCON_CMD_TABLE.add_command("connect", cmd_connect, auto_help_override=False) def cmd_create(command): """ @@ -113,7 +113,7 @@ def cmd_create(command): log_errmsg(traceback.format_exc()) raise -GLOBAL_UNCON_CMD_TABLE.add_command("create", cmd_create) +GLOBAL_UNCON_CMD_TABLE.add_command("create", cmd_create, auto_help_override=False) def cmd_quit(command): """ @@ -124,4 +124,4 @@ def cmd_quit(command): session = command.session session.msg("Good bye! Disconnecting ...") session.handle_close() -GLOBAL_UNCON_CMD_TABLE.add_command("quit", cmd_quit) +GLOBAL_UNCON_CMD_TABLE.add_command("quit", cmd_quit, auto_help_override=False) diff --git a/src/config_defaults.py b/src/config_defaults.py index 1a8997a312..6d1bfd347e 100644 --- a/src/config_defaults.py +++ b/src/config_defaults.py @@ -88,6 +88,7 @@ PERM_CHANNELS = ( ('page','May page other users.'),) # help system access permissions PERM_HELPSYS = ( + ("admin_help","May admin the help system"), ("staff_help", "May see staff help topics."), ("add_help", "May add or append to help entries"), ("del_help", "May delete help entries"),) @@ -135,31 +136,50 @@ PERM_ALL_CUSTOM = () # A dict defining the groups, on the form {group_name:(perm1,perm2,...),...} PERM_GROUPS = \ - {"Immortals":('irc.admin_irc_channels','imc2.admin_imc_channels','channels.emit_commchannel', - 'channels.channel_admin','channels.page','helpsys.staff_help','helpsys.add_help', - 'helpsys.del_help','objects.teleport','objects.wipe','objects.modify_attributes', - 'objects.info','objects.create','objects.dig','objects.see_dbref','objects.admin_ownership', - 'genperms.announce','genperms.admin_perm','genperms.admin_group','genperms.process_control', - 'genperms.manage_players','genperms.game_info'), - "Wizards": ('irc.admin_irc_channels','imc2.admin_imc_channels','channels.emit_commchannel', - 'channels.channel_admin','channels.page','helpsys.staff_help','helpsys.add_help', - 'helpsys.del_help','objects.teleport','objects.wipe','objects.see_dbref', - 'objects.modify_attributes', - 'objects.info','objects.create','objects.dig','objects.admin_ownership','genperms.announce', - 'genperms.game_info'), - "Builders":('channels.emit_commchannel','channels.page','helpsys.staff_help','helpsys.add_help', - 'helpsys.del_help','objects.teleport','objects.wipe','objects.see_dbref', - 'objects.modify_attributes', 'objects.info','objects.create','objects.dig', - 'genperms.game_info'), - "Player Helpers":('channels.emit_commchannel', 'channels.page', 'helpsys.staff_help', - 'helpsys.add_help','helpsys.del_help'), - "Players":('channels.emit_commchannel','channels.page') + {"Immortals":('irc.admin_irc_channels', 'imc2.admin_imc_channels', 'channels.emit_commchannel', + 'channels.channel_admin', 'channels.page', 'helpsys.admin_help', + 'helpsys.staff_help', 'helpsys.add_help', + 'helpsys.del_help', 'objects.teleport', 'objects.wipe', 'objects.modify_attributes', + 'objects.info','objects.create','objects.dig','objects.see_dbref', + 'objects.admin_ownership', 'genperms.announce', 'genperms.admin_perm', + 'genperms.admin_group', 'genperms.process_control', 'genperms.manage_players', + 'genperms.game_info'), + "Wizards": ('irc.admin_irc_channels', 'imc2.admin_imc_channels', 'channels.emit_commchannel', + 'channels.channel_admin', 'channels.page', 'helpsys.admin_help', + 'helpsys.staff_help', 'helpsys.add_help', + 'helpsys.del_help', 'objects.teleport', 'objects.wipe', 'objects.see_dbref', + 'objects.modify_attributes', 'objects.info', 'objects.create', 'objects.dig', + 'objects.admin_ownership', 'genperms.announce', 'genperms.game_info'), + "Builders":('channels.emit_commchannel', 'channels.page', 'helpsys.staff_help', + 'helpsys.add_help', 'helpsys.del_help', + 'objects.teleport', 'objects.wipe', 'objects.see_dbref', + 'objects.modify_attributes', 'objects.info', + 'objects.create', 'objects.dig', 'genperms.game_info'), + "Player Helpers":('channels.emit_commchannel', 'channels.page', 'helpsys.staff_help', + 'helpsys.add_help', 'helpsys.del_help'), + "Players":('channels.emit_commchannel', 'channels.page') } # By defining a default player group, all players may start with some permissions pre-set. PERM_DEFAULT_PLAYER_GROUP = "Players" +## Help system +## Evennia allows automatic help-updating of commands by use of the auto-help system +## which use the command's docstrings for documentation, automatically updating it +## as commands are reloaded. Auto-help is a powerful way to keep your help database +## up-to-date, but it will also overwrite manual changes made +## to the help database using other means (@set_help, admin interface etc), so +## for a production environment you might want to turn auto-help off. You can +## later activate auto-help on a per-command basis (e.g. when developing a new command) +## using the auto_help_override argument to add_command(). + +# activate the auto-help system +HELP_AUTO_ENABLED = True +# Add a dynamically calculated 'See also' footer to help entries +HELP_SHOW_RELATED = True + +## Channels +## Your names of various default comm channels for emitting debug- or informative messages. -# Your names of various default comm channels for emitting debug- or informative messages. COMMCHAN_MUD_INFO = 'MUDInfo' COMMCHAN_MUD_CONNECTIONS = 'MUDConnections' COMMCHAN_IMC2_INFO = 'MUDInfo' diff --git a/src/helpsys/helpsystem.py b/src/helpsys/helpsystem.py new file mode 100644 index 0000000000..bd1166ba11 --- /dev/null +++ b/src/helpsys/helpsystem.py @@ -0,0 +1,411 @@ +""" +Support functions for the help system. +Allows adding help to the data base from inside the mud as +well as creating auto-docs of commands based on their doc strings. +The system supports help-markup for multiple help entries as well +as a dynamically updating help index. +""" +import textwrap +from django.conf import settings +from src.helpsys.models import HelpEntry +from src import logger +from src import defines_global + + +class EditHelp(object): + """ + This sets up an object able to perform normal editing + operations on the help database. + """ + def __init__(self, indent=4, width=70): + """ + We check if auto-help is active or not and + set some formatting options. + """ + self.indent = indent # indentation of help text + self.width = width # width of help text + + def format_help_text(self, help_text): + """ + This formats the help entry text for proper left-side indentation. + + The first line is adjusted to the proper indentation and the + subsequent lines are then adjusted proportionally to the first; + so indentation relative this first line remains intact. + """ + lines = help_text.expandtabs().splitlines() + + # strip empty lines above and below the text + while True: + if lines and not lines[0].strip(): + lines.pop(0) + else: + break + while True: + if lines and not lines[-1].strip(): + lines.pop() + else: + break + if not lines: + return "" + + # produce a list of the indentations of each line initially + indentlist = [len(line) - len(line.lstrip()) for line in lines] + + # use the first line to set the shift + lineshift = indentlist[0] - self.indent + + # shift everything to the left + indentlist = [max(self.indent, indent-lineshift) for indent in indentlist] + trimmed = [] + for il, line in enumerate(lines): + indentstr = " " * indentlist[il] + trimmed.append("%s%s" % (indentstr, line.strip())) + return "\n".join(trimmed) + + def parse_markup_header(self, subtopic_header): + """ + The possible markup headers for splitting the help into sections are: + [[TopicTitle]] + [[TopicTitle,category]] + [[TopicTitle(perm1,perm2)]] + [[TopicTitle,category(perm1,perm2)]] + """ + subtitle = "" + subcategory = "" + subpermissions = () + #identifying the header parts. The header can max have three parts: + # topicname, category (perm1,perm2,...) + try: + # find the permission tuple + lindex = subtopic_header.index('(') + rindex = subtopic_header.index(')') + if lindex < rindex: + permtuple = subtopic_header[lindex+1:rindex] + subpermissions = tuple([p.strip() + for p in permtuple.split(',')]) + subtopic_header = subtopic_header[:lindex] + except ValueError: + # no permission tuple found + pass + # see if we have a name, category pair. + try: + subtitle, subcategory = subtopic_header.split(',') + subtitle, subcategory = subtitle.strip(), subcategory.strip() + except ValueError: + subtitle = subtopic_header.strip() + # we are done, return a tuple with the results + return ( subtitle, subcategory, subpermissions ) + + def format_help_entry(self, helptopic, category, helptext, permissions=None): + """ + helptopic (string) - name of the full help entry + helptext (string) - the help entry (may contain sections) + permissions (tuple) - tuple with permission/group names + defined for the entire help entry. + (markup permissions override those) + Handles help markup in order to split help into subsections. + + These markup markers will be assumed to start a new line, regardless + of where they are located in the help entry. If no permission string + tuple and/or category is given, the overall permission/category of + the entire help entry is used. + """ + # sanitize input + topics = [] + if '[[' not in helptext: + formatted_text = self.format_help_text(helptext) + topics.append((helptopic, category, + formatted_text, permissions)) + return topics + + subtopics = helptext.split('[[') + + if subtopics[0]: + # the very first entry (before any markup) is the normal + # help entry for the helptopic at hand. + formatted_text = self.format_help_text(subtopics[0]) + topics.append((helptopic, category, formatted_text, permissions)) + + for subtopic in subtopics[1:]: + # handle all extra topics designated with markup + try: + subtopic_header, subtopic_text = subtopic.split(']]', 1) + except ValueError: + # if we have no ending, the entry is malformed and + # we ignore this entry (better than overwriting + # something in the database). + logger.log_errmsg("Malformed help markup in %s: '%s'\n (missing end ']]' )" % \ + (helptopic, subtopic)) + continue + # parse and format the help entry parts + subtopic_header = self.parse_markup_header(subtopic_header) + if not subtopic_header[0]: + # we require a topic title. + logger.log_errmsg("Malformed help markup in '%s': Missing title." % subtopic_header) + return + # parse the header and use defaults + subtopic_name = subtopic_header[0] + subtopic_category = subtopic_header[1] + subtopic_text = self.format_help_text(subtopic_text) + subtopic_permissions = subtopic_header[2] + if not subtopic_category: + # no category set; inherit from main topic + subtopic_category = category + if not subtopic_permissions: + # no permissions set; inherit from main topic + subtopic_permissions = permissions + + # We have a finished topic, add it to the list as a topic tuple. + topics.append((subtopic_name, subtopic_category, + subtopic_text, subtopic_permissions)) + return topics + + def create_help(self, newtopic): + """ + Add a help entry to the database, replace an old one if it exists. + topic (tuple) - this is a formatted tuple of data as prepared + by format_help_entry, on the form (title, category, text, (perm_tuple)) + """ + #sanity checks; + topicname = newtopic[0] + category = newtopic[1] + entrytext = newtopic[2] + permissions = newtopic[3] + + if not (topicname or entrytext): + # don't create anything if there we + # are missing vital parts + return + if not category: + # this will force the default + category = "General" + if permissions: + # the permissions tuple might be mangled; + # make sure we build a string properly. + if type(permissions) != type(tuple()): + permissions = "%s" % permissions + else: + permissions = ", ".join(permissions) + else: + permissions = "" + + # check if the help topic already exist. + oldtopic = HelpEntry.objects.filter(topicname__iexact=newtopic[0]) + if oldtopic: + #replace an old help file + topic = oldtopic[0] + topic.category = category + topic.entrytext = entrytext + topic.canview = permissions + topic.save() + else: + #we have a new topic - create a new help object + new_entry = HelpEntry(topicname=topicname, + category=category, + entrytext=entrytext, + canview=permissions) + new_entry.save() + + def add_help_auto(self, topicstr, category, entrytext, permissions=()): + """ + This is used by the auto_help system to add help one or more + help entries to the system. + """ + # sanity checks + if permissions and type(permissions) != type(tuple()): + string = "Auto-Help: malformed perm_tuple %s: %s -> %s (fixed)" % \ + (topicstr,permissions, (permissions,)) + logger.log_errmsg(string) + permissions = (permissions,) + + # identify markup and do nice formatting as well as eventual + # related entries to the help entries. + logger.log_infomsg("auto-help in: %s %s %s %s" % (topicstr, category, entrytext, permissions)) + topics = self.format_help_entry(topicstr, category, + entrytext, permissions) + logger.log_infomsg("auto-help: %s -> %s" % (topicstr,topics)) + # create the help entries: + if topics: + for topic in topics: + self.create_help(topic) + + def add_help_manual(self, pobject, topicstr, category, + entrytext, permissions=(), force=False): + """ + This is used when a player wants to add a help entry to the database + manually (most often from inside the game) + + force - this is given by the player and forces an overwrite also if the + entry already exists or there are multiple similar matches to + the entry. + """ + # permission check: + if not (pobject.is_superuser() or pobject.has_perm("helpsys.add_help")): + pobject.emit_to(defines_global.NOPERMS_MSG) + return None + # do a more fuzzy search to warn in case in case we are misspelling. + topic = HelpEntry.objects.find_topicmatch(pobject, topicstr) + if topic and not force: + return topic + self.add_help_auto(topicstr, category, entrytext, permissions) + pobject.emit_to("Added/appended help topic '%s'." % topicstr) + + def del_help_auto(self, topicstr): + """ + Delete a help entry from the data base. Automatic version. + """ + topic = HelpEntry.objects.filter(topicname__iexact=topicstr) + if topic: + topic[0].delete() + + def del_help_manual(self, pobject, topicstr): + """ + Deletes an entry from the database. Interactive version. + Note that it makes no sense to delete auto-added help entries this way since + they will be re-added on the next @reload. This is mostly useful for cleaning + the database from doublet or orphaned entries, or when auto-help is turned off. + """ + # find topic with permission checks + if not (pobject.is_superuser() or pobject.has_perm("helpsys.del_help")): + pobject.emit_to(defines_global.NOPERMS_MSG) + return None + topic = HelpEntry.objects.find_topicmatch(pobject, topicstr) + if not topic or len(topic) > 1: + return topic + # we have an exact match. Delete topic. + topic[0].delete() + pobject.emit_to("Help entry '%s' deleted." % topicstr) + + def homogenize_database(self, category): + """ + This sets the entire help database to one category. + It can be used to mark an initially loaded help database + in a particular category, for later filtering. + + In evennia dev version, this is done with MUX help database. + """ + entries = HelpEntry.objects.all() + for entry in entries: + entry.category = category + entry.save() + logger.log_infomsg("Help database homogenized to category %s" % category) + + def autoclean_database(self, topiclist): + """ + This syncs the entire help database against a reference topic + list, deleting non-used or duplicate help entries that can be + the result of auto-help misspellings etc. + """ + pass + +class ViewHelp(object): + """ + This class contains ways to view the + help database in a dynamical fashion. + """ + def __init__(self, indent=4, width=78, category_cols=4, entry_cols=6): + """ + indent (int) - number of spaces to indent tables with + width (int) - width of index tables + category_cols (int) - number of collumns per row for + category tables + entry_cols (int) - number of collumns per row for help entries + """ + self.width = width + self.indent = indent + self.category_cols = category_cols + self.entry_cols = entry_cols + self.show_related = settings.HELP_SHOW_RELATED + + def make_table(self, items, cols): + """ + This takes a list of string items and displays them in collumn order, + (sorted horizontally-first), ie + A A A A + A B B B + B B C C + C C + cols is the number of collumns to format. + """ + items.sort() + if not items or not cols: + return [] + length = len(items) + # split the list into sublists of length cols + rows = [items[i:i+cols] for i in xrange(0, length, cols)] + # build the table + string = "" + for row in rows: + string += self.indent * " " + ", ".join(row) + "\n" + return string + + def index_full(self, pobject): + """ + This lists all available topics in the help index, + ordered after category. + + The MUX category is not shown, it is for development + reference only. + """ + entries = HelpEntry.objects.all() + + categories = [e.category for e in entries if e.category != 'MUX'] + categories = list(set(categories)) # make list unique + categories.sort() + table = "" + for category in categories: + topics = [e.topicname.lower() for e in entries.filter(category__iexact=category) + if e.can_view(pobject)] + + # pretty-printing the list + header = "--- Topics in category %s:" % category + nl = self.width - len(header) + if not topics: + text = self.indent*" " + "[There are no topics relevant to you in this category.]\n\r" + else: + text = self.make_table(topics, self.entry_cols) + table += "\r\n%s%s\n\r\n\r%s" % (header, "-"*nl, text) + return table + + def index_categories(self): + """ + This lists all categories defined in the help index. + """ + entries = HelpEntry.objects.all() + categories = [e.category for e in entries] + categories = list(set(categories)) # make list unique + return self.make_table(categories, self.category_cols) + + def index_category(self, pobject, category): + """ + List the help entries within a certain category + """ + entries = HelpEntry.objects.find_topics_with_category(pobject, category) + if not entries: + return [] + # filter out those we can actually view + helptopics = [e.topicname.lower() for e in entries if e.can_view(pobject)] + if not helptopics: + # we don't have permission to view anything in this category + return " [There are no topics relevant to you in this category.]\n\r" + return self.make_table(helptopics, self.entry_cols) + + def suggest_help(self, pobject, topic): + """ + This goes through the help database, searching for relatively + close matches to this topic. If those are found, they are + added as a nice footer to the end of the topic entry. + """ + if not self.show_related: + return None + topicname = topic.topicname + return HelpEntry.objects.find_topicsuggestions(pobject, topicname) + +# Object instances +edithelp = EditHelp(indent=3, + width=80) +viewhelp = ViewHelp(indent=3, + width=80, + category_cols=4, + entry_cols=4) diff --git a/src/helpsys/management/__init__.py b/src/helpsys/management/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/helpsys/management/commands/__init__.py b/src/helpsys/management/commands/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/src/helpsys/management/commands/edit_helpfiles.py b/src/helpsys/management/commands/edit_helpfiles.py deleted file mode 100644 index 7e08995cf2..0000000000 --- a/src/helpsys/management/commands/edit_helpfiles.py +++ /dev/null @@ -1,230 +0,0 @@ -""" -Support commands for a more advanced help system. -Allows adding help to the data base from inside the mud as -well as creating auto-docs of commands based on their doc strings. -The system supports help-markup for multiple help entries as well -as a dynamically updating help index. -""" -from django.contrib.auth.models import User -from src.helpsys.models import HelpEntry -from src.ansi import ANSITable - -# -# Helper functions -# - -def _privileged_help_search(topicstr): - """ - searches the topic data base without needing to know who calls it. Needed - for autohelp functionality. Will show all help entries, also those set to staff - only. - """ - if topicstr.isdigit(): - t_query = HelpEntry.objects.filter(id=topicstr) - else: - exact_match = HelpEntry.objects.filter(topicname__iexact=topicstr) - if exact_match: - t_query = exact_match - else: - t_query = HelpEntry.objects.filter(topicname__istartswith=topicstr) - return t_query - - -def _create_help(topicstr, entrytext, staff_only=False, force_create=False, - pobject=None, noauth=False): - """ - Add a help entry to the database, replace an old one if it exists. - - Note - noauth=True will bypass permission checks, so do not use this from - inside mud, it is needed by the autohelp system only. - """ - - if noauth: - #do not check permissions (for autohelp) - topic = _privileged_help_search(topicstr) - elif pobject: - #checks access rights before searching (this should have been - #done already at the command level) - if not pobject.has_perm("helpsys.add_help"): return [] - topic = HelpEntry.objects.find_topicmatch(pobject, topicstr) - else: - return [] - - if len(topic) == 1: - #replace an old help file - topic = topic[0] - topic.entrytext = entrytext - topic.staff_only = staff_only - topic.save() - return [topic] - elif len(topic) > 1 and not force_create: - #a partial match, return it for inspection. - return topic - else: - #we have a new topic - create a new help object - new_entry = HelpEntry(topicname=topicstr, - entrytext=entrytext, - staff_only=staff_only) - new_entry.save() - return [new_entry] - -def handle_help_markup(topicstr, entrytext, staff_only, identifier="<> and - <> to override the staff_only flag on a per-subtopic - basis. - """ - topic_list = entrytext.split(identifier) - topic_dict = {} - staff_dict = {} - for txt in topic_list: - txt = txt.rstrip() - if txt.count('>>'): - topic, text = txt.split('>>',1) - text = text.rstrip() - topic = topic.lower() - - if topic in topic_dict.keys(): - #do not allow multiple topics of the same name - return {}, [] - if 'all:' in topic: - topic = topic[4:] - staff_dict[topic] = False - elif 'staff:' in topic: - topic = topic[6:] - staff_dict[topic] = True - else: - staff_dict[topic] = staff_only - topic_dict[topic] = text - else: - #no markup, just add the entry as-is - topic = topicstr.lower() - topic_dict[topic] = txt - staff_dict[topic] = staff_only - return topic_dict, staff_dict - -def format_footer(top, text, topic_dict, staff_dict): - """ - Formats the subtopic with a 'Related Topics:' footer. If mixed - staff-only flags are set, those help entries without the staff-only flag - will not see staff-only help files recommended in the footer. This allows - to separate out the staff-only help switches etc into a separate - help file so as not to confuse normal players. - """ - if text: - #only include non-staff related footers to non-staff commands - if staff_dict[top]: - other_topics = other_topics = filter(lambda o: o != top, topic_dict.keys()) - else: - other_topics = other_topics = filter(lambda o: o != top and not staff_dict[o], - topic_dict.keys()) - if other_topics: - footer = ANSITable.ansi['normal'] + "\n\r\n\r Related Topics: " - for t in other_topics: - footer += t + ', ' - footer = footer[:-2] + '.' - return text + footer - else: - return text - else: - return False - -# -# Access functions -# - -def add_help(topicstr, entrytext, staff_only=False, force_create=False, - pobject=None, auto_help=False): - """ - Add a help topic to the database. This is also usable by autohelp, with auto=True. - - Allows <> markup in the help text, to automatically spawn - subtopics. For creating mixed staff/ordinary subtopics, the <> and - <> commands can override the overall staff_only setting for - that entry only. - """ - identifier = '< 4: + # next search with a cropped version of the command. + ttemp = self.filter(category__iexact=category).filter(topicname__icontains=crop[:4]) + ttemp = [topic for topic in ttemp if topic not in topics] + topics = list(topics) + list(ttemp) + # we need to clean away the given help entry. + return [topic for topic in topics if topic.topicname.lower() != topicstr.lower()] + + def find_topics_with_category(self, pobject, category): + """ + Search topics having a particular category + """ + t_query = self.filter(category__iexact=category) + return [topic for topic in t_query if topic.can_view(pobject)] diff --git a/src/helpsys/models.py b/src/helpsys/models.py index e3781cf02d..c0a3cd9d79 100644 --- a/src/helpsys/models.py +++ b/src/helpsys/models.py @@ -11,12 +11,21 @@ class HelpEntry(models.Model): A generic help entry. """ topicname = models.CharField(max_length=255, unique=True) - entrytext = models.TextField(blank=True, null=True) - staff_only = models.BooleanField(default=False) + category = models.CharField(max_length=255, default="General") + canview = models.CharField(max_length=255, blank=True) + entrytext = models.TextField(blank=True) + + #deprecated, only here to allow MUX helpfile load. + staff_only = models.BooleanField(default=False) objects = HelpEntryManager() class Meta: + """ + Permissions here defines access to modifying help + entries etc, not which entries can be viewed (that + is controlled by the canview field). + """ verbose_name_plural = "Help entries" ordering = ['topicname'] permissions = settings.PERM_HELPSYS @@ -29,7 +38,24 @@ class HelpEntry(models.Model): Returns the topic's name. """ return self.topicname + + def get_category(self): + """ + Returns the category of this help entry. + """ + return self.category + def can_view(self, pobject): + """ + Check if the pobject has the necessary permission/group + to view this help entry. + """ + perm = self.canview.split(',') + if not perm or (len(perm) == 1 and not perm[0]) or \ + pobject.has_perm("helpsys.admin_help"): + return True + return pobject.has_perm_list(perm) + def get_entrytext_ingame(self): """ Gets the entry text for in-game viewing. diff --git a/src/helpsys/management/commands/update_helpfiles.py b/src/helpsys/update_from_file.py similarity index 100% rename from src/helpsys/management/commands/update_helpfiles.py rename to src/helpsys/update_from_file.py diff --git a/src/initial_setup.py b/src/initial_setup.py index 1e72bd2f5a..b3ea6c7ba6 100644 --- a/src/initial_setup.py +++ b/src/initial_setup.py @@ -11,6 +11,8 @@ from django.conf import settings from src.objects.models import Object from src.config.models import ConfigValue, CommandAlias, ConnectScreen from src import comsys, defines_global, logger +from src.helpsys import helpsystem + def get_god_user(): """ Returns the initially created 'god' User object. @@ -131,6 +133,16 @@ def import_help_files(): """ management.call_command('loaddata', 'docs/help_files.json', verbosity=0) +def categorize_initial_helpdb(): + """ + This makes sure that the initially loaded + database is separated into its own + help category. + """ + default_category = "MUX" + print " Moving initial imported help db to help category '%s'." % default_category + helpsystem.edithelp.homogenize_database(default_category) + def handle_setup(): """ Main logic for the module. @@ -142,3 +154,4 @@ def handle_setup(): create_groups() create_channels() import_help_files() + categorize_initial_helpdb() diff --git a/src/objects/models.py b/src/objects/models.py index ca11ae97c5..d8469830d0 100755 --- a/src/objects/models.py +++ b/src/objects/models.py @@ -4,11 +4,13 @@ This is where all of the crucial, core object models reside. import re import traceback -try: import cPickle as pickle -except ImportError: import pickle +try: + import cPickle as pickle +except ImportError: + import pickle from django.db import models -from django.contrib.auth.models import User, Group +from django.contrib.auth.models import User from django.conf import settings from src.objects.util import object as util_object from src.objects.managers.object import ObjectManager @@ -24,8 +26,6 @@ from src import logger import src.flags from src.util import functions_general -from src.logger import log_infomsg - class Attribute(models.Model): """ Attributes are things that are specific to different types of objects. For @@ -46,9 +46,9 @@ class Attribute(models.Model): def __str__(self): return "%s(%s)" % (self.attr_name, self.id) - """ - BEGIN COMMON METHODS - """ + # + # BEGIN COMMON METHODS + # def get_name(self): """ Returns an attribute's name. @@ -74,7 +74,8 @@ class Attribute(models.Model): """ Returns True if the attribute is hidden. """ - if self.attr_hidden or self.get_name().upper() in defines_global.HIDDEN_ATTRIBS: + if self.attr_hidden or self.get_name().upper() \ + in defines_global.HIDDEN_ATTRIBS: return True else: return False @@ -83,7 +84,8 @@ class Attribute(models.Model): """ Returns True if the attribute is unsettable. """ - if self.get_name().upper() in defines_global.NOSET_ATTRIBS: return True + if self.get_name().upper() in defines_global.NOSET_ATTRIBS: + return True else: return False @@ -103,21 +105,28 @@ class Object(models.Model): ROOM, or other entities within the database. Pretty much anything in the game is an object. Objects may be one of several different types, and may be parented to allow for differing behaviors. - - We eventually want to find some way to implement object parents via loadable - modules or sub-classing. """ name = models.CharField(max_length=255) ansi_name = models.CharField(max_length=255) - owner = models.ForeignKey('self', related_name="obj_owner", blank=True, null=True) - zone = models.ForeignKey('self', related_name="obj_zone", blank=True, null=True) - script_parent = models.CharField(max_length=255, blank=True, null=True) - home = models.ForeignKey('self', related_name="obj_home", blank=True, null=True) + owner = models.ForeignKey('self', + related_name="obj_owner", + blank=True, null=True) + zone = models.ForeignKey('self', + related_name="obj_zone", + blank=True, null=True) + script_parent = models.CharField(max_length=255, + blank=True, null=True) + home = models.ForeignKey('self', + related_name="obj_home", + blank=True, null=True) type = models.SmallIntegerField(choices=defines_global.OBJECT_TYPES) - location = models.ForeignKey('self', related_name="obj_location", blank=True, null=True) + location = models.ForeignKey('self', + related_name="obj_location", + blank=True, null=True) flags = models.TextField(blank=True, null=True) nosave_flags = models.TextField(blank=True, null=True) - date_created = models.DateField(editable=False, auto_now_add=True) + date_created = models.DateField(editable=False, + auto_now_add=True) # 'scriptlink' is a 'get' property for retrieving a reference to the correct # script object. Defined down by get_scriptlink() @@ -125,10 +134,15 @@ class Object(models.Model): objects = ObjectManager() - #state system can set a particular command table to be used (not persistent). + # state system can set a particular command + # table to be used (not persistent). state = None class Meta: + """ + Define permission types on the object class and + how it is ordered in the database. + """ ordering = ['-date_created', 'id'] permissions = settings.PERM_OBJECTS @@ -147,13 +161,17 @@ class Object(models.Model): """ return "#%s" % str(self.id) - """ - BEGIN COMMON METHODS - """ - def search_for_object(self, ostring, emit_to_obj=None, search_contents=True, - search_location=True, dbref_only=False, - limit_types=False, search_aliases=False, - attribute_name=None): + # + # BEGIN COMMON METHODS + # + def search_for_object(self, ostring, + emit_to_obj=None, + search_contents=True, + search_location=True, + dbref_only=False, + limit_types=False, + search_aliases=False, + attribute_name=None): """ Perform a standard object search, handling multiple results and lack thereof gracefully. @@ -187,13 +205,15 @@ class Object(models.Model): attribute_name=attribute_name) if len(results) > 1: - s = "More than one match for '%s' (please narrow target):" % ostring + string = "More than one match for '%s' (please narrow target):" % ostring for num, result in enumerate(results): invtext = "" if result.get_location() == self: invtext = " (carried)" - s += "\n %i-%s%s" % (num+1, result.get_name(show_dbref=False),invtext) - emit_to_obj.emit_to(s) + string += "\n %i-%s%s" % (num+1, + result.get_name(show_dbref=False), + invtext) + emit_to_obj.emit_to(string) return False elif len(results) == 0: emit_to_obj.emit_to("I don't see that here.") @@ -224,13 +244,18 @@ class Object(models.Model): for session in sessions: session.msg(parse_ansi(message)) - def execute_cmd(self, command_str, session=None): + def execute_cmd(self, command_str, session=None, ignore_state=False): """ Do something as this object. + + bypass_state - ignore the fact that a player is in a state + (means the normal command table will be used + no matter what) """ # The Command object has all of the methods for parsing and preparing # for searching and execution. Send it to the handler once populated. - cmdhandler.handle(cmdhandler.Command(self, command_str, session=session)) + cmdhandler.handle(cmdhandler.Command(self, command_str, session=session), + ignore_state=ignore_state) def emit_to_contents(self, message, exclude=None): """ @@ -384,7 +409,8 @@ class Object(models.Model): # When builder_override is enabled, a builder permission means # the object controls the other. - if builder_override and not other_obj.is_player() and self.has_group('Builders'): + if builder_override and not other_obj.is_player() \ + and self.has_group('Builders'): return True # They've failed to meet any of the above conditions. @@ -466,7 +492,8 @@ class Object(models.Model): uobj.is_active = False uobj.save() except: - functions_general.log_errmsg('Destroying object %s but no matching player.' % (self,)) + string = 'Destroying object %s but no matching player.' % (self,) + functions_general.log_errmsg(string) # Set the object type to GOING self.type = defines_global.OTYPE_GOING @@ -511,7 +538,7 @@ class Object(models.Model): """ # Gather up everything, other than exits and going/garbage, that is under # the belief this is its location. - objs = self.obj_location.filter(type__in=[1,2,3]) + objs = self.obj_location.filter(type__in=[1, 2, 3]) default_home_id = ConfigValue.objects.get_configvalue('default_home') try: default_home = Object.objects.get(id=default_home_id) @@ -644,7 +671,8 @@ class Object(models.Model): """ Returns a QuerySet of an object's attributes. """ - return [attr for attr in self.attribute_set.all() if not attr.is_hidden()] + return [attr for attr in self.attribute_set.all() + if not attr.is_hidden()] def clear_all_attributes(self): @@ -684,9 +712,11 @@ class Object(models.Model): re.IGNORECASE) # If the regular expression search returns a match object, add to results. if exclude_noset: - return [attr for attr in attrs if match_exp.search(attr.get_name()) and not attr.is_hidden() and not attr.is_noset()] + return [attr for attr in attrs if match_exp.search(attr.get_name()) + and not attr.is_hidden() and not attr.is_noset()] else: - return [attr for attr in attrs if match_exp.search(attr.get_name()) and not attr.is_hidden()] + return [attr for attr in attrs if match_exp.search(attr.get_name()) + and not attr.is_hidden()] def has_flag(self, flag): @@ -751,7 +781,10 @@ class Object(models.Model): self.save() def unset_flag(self, flag): - self.set_flag(flag,value=False) + """ + Clear the flag. + """ + self.set_flag(flag, value=False) def get_flags(self): """ @@ -815,7 +848,8 @@ class Object(models.Model): try: return self.location except: - functions_general.log_errmsg("Object '%s(#%d)' has invalid location: #%s" % (self.name,self.id,self.location_id)) + functions_general.log_errmsg("Object '%s(#%d)' has invalid location: #%s" % \ + (self.name,self.id,self.location_id)) return False def get_scriptlink(self): @@ -827,7 +861,7 @@ class Object(models.Model): # Load the script reference into the object's attribute. self.scriptlink_cached = scripthandler.scriptlink(self, - script_to_load) + script_to_load) if self.scriptlink_cached: # If the scriptlink variable can't be populated, this will fail # silently and let the exception hit in the scripthandler. @@ -857,7 +891,8 @@ class Object(models.Model): script_parent: (string) String pythonic import path of the script parent assuming the python path is game/gamesrc/parents. """ - if script_parent != None and scripthandler.scriptlink(self, str(script_parent).strip()): + if script_parent != None and scripthandler.scriptlink(self, + str(script_parent).strip()): #assigning a custom parent self.script_parent = str(script_parent).strip() self.save() @@ -1046,7 +1081,10 @@ class Object(models.Model): #state access functions - def get_state(self): + def get_state(self): + """ + Returns the player's current state. + """ return self.state def set_state(self, state_name=None): diff --git a/src/session_mgr.py b/src/session_mgr.py index b6a027b115..40ecadf6f0 100644 --- a/src/session_mgr.py +++ b/src/session_mgr.py @@ -5,7 +5,6 @@ import time from django.contrib.auth.models import User from src.config.models import ConfigValue from src import logger -from src.util import functions_general # Our list of connected sessions. session_list = [] diff --git a/src/statetable.py b/src/statetable.py index 05439741f6..f26a3b317f 100644 --- a/src/statetable.py +++ b/src/statetable.py @@ -1,32 +1,43 @@ """ -The state system allows the player to enter states/modes where only a special set of commands -are available. This can be used for all sorts of useful things: - - in-game menus (picking an option from a list, much more powerful than using 'rooms') - - inline text editors (entering text into a buffer) - - npc conversation (select replies) - - only allowing certain commands in special situations (e.g. special attack commands - when in 'combat' mode) - - adding special commands to the normal set (e.g. a 'howl' command when in 'werewolf' state) - - deactivating certain commands (e.g. removing the 'say' command in said 'werewolf' state ...) +State table -Basically the GLOBAL_STATE_TABLE contains a dict with command tables keyed after the -name of the state. To use, a function must set the 'state' variable on a player object -using the obj.set_state() function. The GLOBAL_STATE_TABLE will then be searched by the -command handler and if the state is defined its command table is used instead +The state system allows the player to enter states/modes where only a +special set of commands are available. This can be used for all sorts +of useful things: - in-game menus (picking an option from a list, much +more powerful than using 'rooms') - inline text editors (entering text +into a buffer) - npc conversation (select replies) - only allowing +certain commands in special situations (e.g. special attack commands +when in 'combat' mode) - adding special commands to the normal set +(e.g. a 'howl' command when in 'werewolf' state) - deactivating +certain commands (e.g. removing the 'say' command in said 'werewolf' +state ...) + +Basically the GLOBAL_STATE_TABLE contains a dict with command tables +keyed after the name of the state. To use, a function must set the +'state' variable on a player object using the obj.set_state() +function. The GLOBAL_STATE_TABLE will then be searched by the command +handler and if the state is defined its command table is used instead of the normal global command table. -The state system is pluggable, in the same way that commands are added to the global command -table, commands are added to the GLOBAL_STATE_TABLE using add_command supplying in -addition the name of the state. The main difference is that new states must first be created -using the GLOBAL_STATE_TABLE.add_state() command. See examples in game/gamesrc. +The state system is pluggable, in the same way that commands are added +to the global command table, commands are added to the +GLOBAL_STATE_TABLE using add_command supplying in addition the name of +the state. The main difference is that new states must first be +created using the GLOBAL_STATE_TABLE.add_state() command. See examples +in game/gamesrc. """ +from django.conf import settings from cmdtable import CommandTable, GLOBAL_CMD_TABLE from logger import log_errmsg -import src.helpsys.management.commands.edit_helpfiles as edit_help +from src import defines_global +from src.helpsys import helpsystem +from src.helpsys.models import HelpEntry class StateTable(object): - + """ + This implements a variant of the command table handling states. + """ state_table = None def __init__(self): @@ -35,9 +46,9 @@ class StateTable(object): self.state_flags = {} def add_state(self, state_name, - global_cmds=None, global_filter=[], + global_cmds=None, global_filter=None, allow_exits=False, allow_obj_cmds=False, - exit_command=False): + help_command=True, exit_command=False): """ Creates a new game state. Each state has its own unique set of commands and can change how the game works. @@ -76,51 +87,58 @@ class StateTable(object): special conditions or clean-up operations before allowing a player to exit (e.g. combat states and text editors), in which case this feature should be turned off and handled by custom exit commands. + help_command """ state_name = state_name.strip() #create state self.state_table[state_name] = CommandTable() - - if global_cmds != None: - if global_cmds == 'all': - f = lambda c: True - elif global_cmds == 'include': - f = lambda c: c in global_filter - if not 'help' in global_filter: - global_filter.append('help') - elif global_cmds == 'exclude': - f = lambda c: c not in global_filter - else: - log_errmsg("ERROR: in statetable, state %s: Unknown global_cmds flag '%s'." % - (state_name, global_cmds)) - return - for cmd in filter(f,GLOBAL_CMD_TABLE.ctable.keys()): - self.state_table[state_name].ctable[cmd] = \ - GLOBAL_CMD_TABLE.get_command_tuple(cmd) - if exit_command: - #if we import global commands, we use the normal help index; thus add - #help for @exit to the global index. - self.state_table[state_name].add_command("@exit", - cmd_state_exit, - auto_help=True) - else: - #when no global cmds are imported, we create a small custom - #state-based help index instead - self.help_index.add_state(state_name) - self.add_command(state_name,'help',cmd_state_help) - if exit_command: - #add the @exit command - self.state_table[state_name].add_command("@exit", - cmd_state_exit) - self.help_index.add_state_help(state_name, "@exit", - cmd_state_exit.__doc__) #store special state flags self.state_flags[state_name] = {} + self.state_flags[state_name]['globals'] = global_cmds self.state_flags[state_name]['exits'] = allow_exits self.state_flags[state_name]['obj_cmds'] = allow_obj_cmds + + if global_cmds == 'all': + # we include all global commands + func = lambda c: True + elif global_cmds == 'include': + # selective global inclusion + func = lambda c: c in global_filter + if not 'help' in global_filter: + global_filter.append('help') + elif global_cmds == 'exclude': + # selective global exclusion + func = lambda c: c not in global_filter + else: + # no global commands + func = lambda c: False + # add copies of the global command defs to the state's command table. + for cmd in filter(func, GLOBAL_CMD_TABLE.ctable.keys()): + self.state_table[state_name].ctable[cmd] = \ + GLOBAL_CMD_TABLE.get_command_tuple(cmd) + + # create a stand-alone state-based help index + self.help_index.add_state(state_name) + + # if the auto-help command is not wanted, just make a custom command + # overwriting this default 'help' command. Keeps 'info' as a way to have + # both a custom help command and state auto-help; replace this too + # to completely hide auto-help functionality in the state. + self.add_command(state_name, 'help', cmd_state_help) + self.add_command(state_name, 'info', cmd_state_help) + + if exit_command: + #add the @exit command + self.state_table[state_name].add_command("@exit", + cmd_state_exit) + self.help_index.add_state_help(state_name, + "@exit", + "General", + cmd_state_exit.__doc__) + def del_state(self, state_name): """ Permanently deletes a state from the state table. Make sure no users are in @@ -147,12 +165,12 @@ class StateTable(object): return try: del self.state_table[state_name].ctable[command_string] - err = self.help_index.del_state_help(state_name,command_string) + self.help_index.del_state_help(state_name, command_string) except KeyError: pass def add_command(self, state_name, command_string, function, priv_tuple=None, - extra_vals=None, auto_help=False, staff_help=False): + extra_vals=None, help_category="", priv_help_tuple=None): """ Transparently add commands to a specific state. This command is similar to the normal @@ -163,12 +181,12 @@ class StateTable(object): function: (reference) command function object priv_tuple: (tuple) String tuple of permissions required for command. extra_vals: (dict) Dictionary to add to the Command object. - auto_help: (bool) Activate the auto_help system. By default this stores the - help inside the statetable only (not in the main help database), and so - the help entries are only available when the player is actually inside - the state. Note that the auto_help system of state-commands do not - support <> markup. - staff_help: (bool) Help entry is only available for staff players. + + Auto-help functionality: (only used if settings.HELP_AUTO_ENABLED=True) + help_category (str): An overall help category where auto-help will place + the help entry. If not given, 'General' is assumed. + priv_help_tuple (tuple) String tuple of permissions required to view this + help entry. If nothing is given, priv_tuple is used. """ if state_name not in self.state_table.keys(): @@ -177,21 +195,30 @@ class StateTable(object): return state_name = state_name.strip() - #handle auto-help for the state commands - if auto_help: - if self.help_index.has_state(state_name): - #add the help text to state help index only, don't add - #it to the global help index - self.help_index.add_state_help(state_name,command_string, - function.__doc__, - staff_help=staff_help) - auto_help = False - #finally add the new command to the state's command table + # handle the creation of an auto-help entry in the + # stand-alone help index + + topicstr = command_string + if not help_category: + help_category = state_name + help_category = help_category.capitalize() + + self.help_index.add_state_help(state_name, + topicstr, + help_category, + function.__doc__, + priv_help_tuple) + + # finally add the new command to the state's command table + self.state_table[state_name].add_command(command_string, - function, priv_tuple, - extra_vals,auto_help=auto_help, - staff_help=staff_help) + function, + priv_tuple, + extra_vals, + help_category, + priv_help_tuple, + auto_help_override=False) def get_cmd_table(self, state_name): """ @@ -202,150 +229,157 @@ class StateTable(object): else: return None - def get_state_flags(self, state_name): + def get_exec_rights(self, state_name): """ - Return the state flags for a particular state. + Used by the cmdhandler. Accesses the relevant state flags + concerned with execution access for a particular state. """ if self.state_flags.has_key(state_name): - return self.state_flags[state_name]['exits'],\ - self.state_flags[state_name]['obj_cmds'] + return self.state_flags[state_name]['exits'], \ + self.state_flags[state_name]['obj_cmds'] else: - return False, False - - + return False, False, False + class StateHelpIndex(object): """ - Handles the dynamic state help system. + Handles the dynamic state help system. This is + a non-db based help system intended for the special + commands associated with a state. + + The system gives preference to help matches within + the state, but defers to the normal, global help + system when it fails to find a help entry match. """ help_index = None def __init__(self): self.help_index = {} - self.identifier = '<> and <> markup.""" - if self.help_index.has_key(state): - - text = text.rstrip() - if self.identifier in text: - topic_dict, staff_dict = edit_help.handle_help_markup(command, text, staff_help, - 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_help, entry) - else: - self.help_index[state][command] = (staff_help, text) - - def del_state_help(self, state, topic): - """Manually delete a help topic from state help system. Note that this is - only going to last until the next @reload unless you also turn off auto_help - for the relevant topic.""" - if self.help_index.has_key(state) and self.help_index[state].has_key(topic): - del self.help_index[state][topic] - return True - else: - return False + def add_state_help(self, state, topicstr, help_category, + help_text, priv_help_tuple=()): + """ + Store help for a command under a certain state. + Supports [[Title, category, (priv_tuple)]] markup + """ - 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): + if not self.help_index.has_key(state): + return + # produce nicely formatted help entries, taking markup + # into account. + topics = helpsystem.edithelp.format_help_entry(topicstr, + help_category, + help_text, + priv_help_tuple) + # store in state's dict-based database + for topic in topics: + self.help_index[state][topic[0]] = topic + + 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): + # this is a state help entry. 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 + # check permissions + if help_tuple and help_tuple[2]: + if help_tuple[3] and not caller.has_perm_list(help_tuple[3]): + return None + return help_tuple[2] + else: + return None + - def get_state_index(self,caller, state): + def get_state_index(self, 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 + tuples = self.help_index[state].items() + items = [tup[0] for tup in tuples] + table = helpsystem.viewhelp.make_table(items, 6) + return table -#default commands available for all special states +# default commands available for all special states. These +# are added to states during the state initialization if +# the proper flags are set. def cmd_state_exit(command): - """@exit (when in a state) + """ + @exit - exit from a state - This command only works when inside certain special game 'states' (like a menu or - editor or similar situations). + Usage: + @exit - 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.""" + This command only works when inside certain special game 'states' + (like a menu or editor or similar situations). + 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. + """ source_object = command.source_object source_object.clear_state() source_object.emit_to("... Exited.") source_object.execute_cmd('look') + ## 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. + + ## Note that at this time we are not displaying categories in the state help system; + ## (although they are stored). Instead the state itself is treated as a + ## category in itself. + def cmd_state_help(command): """ - help (while in a special state) + help - view help database - 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. + Usage: + help + + Shows the available help on . Use without a topic + to get the index. """ source_object = command.source_object - state = source_object.get_state() args = command.command_argument switches = command.command_switches help_index = GLOBAL_STATE_TABLE.help_index - + state = source_object.get_state() + 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\r" % state - for i in index: - s += " %s\n\r" % i - s = s[:-2] - source_object.emit_to(s) + # first show the normal help index + source_object.execute_cmd("help", ignore_state=True) + + text = help_index.get_state_index(state) + if text: + # Try to list the state-specific help entries after the main list + string = "\n%s%s%s\n\r\n\r%s" % ("---", " Help topics for %s: " % \ + state.capitalize(), "-"*(30-len(state)), text) + source_object.emit_to(string) return + + # try to first find a matching state help topic, then defer to global help + topicstr = args.strip() + helptext = help_index.get_state_help(source_object, state, topicstr) + if helptext: + source_object.emit_to("\n%s" % helptext) else: - args = args.strip() + source_object.execute_cmd("help %s" % topicstr, ignore_state=True) - if 'del' in switches: - if not source_object.is_staff(): - source_object.emit_to("Only staff can delete help topics.") - return - if help_index.del_state_help(state,args): - source_object.emit_to("Topic %s deleted." % args) - else: - source_object.emit_to("Topic %s not found." % args) - return - - 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) - -#import this into modules +#import this instance into your modules GLOBAL_STATE_TABLE = StateTable()