Largely rewrote and refactored the help system.
The help entry database structure has changed! You have to resync or purge your database or your will get problems! New features: * Help entry access now fully controlled by evennia permissions * Categories for each help entry * All entries are created dynamically, with a See also: footer calculated after the current state of the database. * Indexes and topic list calculated on the fly (alphabetically/after category) * Added auto-help help entries for all default commands. * Only shows commands _actually implemented_ - MUX help db moved into 'MUX' category which is not shown by default. * More powerful auto-help markup - supports categories and permissions (and inheritance). * Global on/off switch for auto-help, when entering production * Auto_help_override switch for selectively activating auto-help when developing new commands (like the old system). * Refactored State help system; no more risk of overwriting global help entries. * State help now defers to main help db when no match found; makes system more transparent. * State help entries also support categories/permissions (state categories are not used much though). Other updates: * Added more commands to the batch processor * Many bug-fixes. /Griatch
This commit is contained in:
parent
46e2cd3ecb
commit
8074617285
27 changed files with 1995 additions and 1072 deletions
|
|
@ -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 <<TOPIC:mytitle>> 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 = '<<TOPIC:'
|
||||
|
||||
def add_state(self,state_name):
|
||||
def add_state(self, state_name):
|
||||
"Create a new state"
|
||||
self.help_index[state_name] = {}
|
||||
|
||||
def has_state(self,state_name):
|
||||
def has_state(self, state_name):
|
||||
"Checks if we have this state"
|
||||
return self.help_index.has_key(state_name)
|
||||
|
||||
def add_state_help(self, state,command,text,staff_help=False):
|
||||
"""Store help for a command under a certain state.
|
||||
Supports <<TOPIC:MyTopic>> and <<TOPIC:STAFF:MyTopic>> 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 <topic> (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 <topic>
|
||||
|
||||
Shows the available help on <topic>. 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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue