Added new bettermenu system
This commit is contained in:
parent
72049eeddd
commit
698e226763
1 changed files with 654 additions and 0 deletions
654
evennia/contrib/bettermenusystem.py
Normal file
654
evennia/contrib/bettermenusystem.py
Normal file
|
|
@ -0,0 +1,654 @@
|
||||||
|
"""
|
||||||
|
(Better) MenuSystem
|
||||||
|
|
||||||
|
Evennia contribution - Griatch 2015
|
||||||
|
|
||||||
|
|
||||||
|
This implements a better menu system for Evennia. Contrary to the old
|
||||||
|
contrib menusystem, this is controlled from a simple module with
|
||||||
|
function definitions, rather than building a set of classes with
|
||||||
|
arguments.
|
||||||
|
|
||||||
|
To start the menu, just import the Menu class from this module,
|
||||||
|
and call
|
||||||
|
|
||||||
|
from evennia.contrib.bettermenusystem import Menu
|
||||||
|
|
||||||
|
Menu(caller, menu_module_path,
|
||||||
|
startnode="start", allow_quit=True,
|
||||||
|
cmdset_mergetype="Replace", cmdset_priority=1):
|
||||||
|
|
||||||
|
Where `caller` is the Object to use the menu on - it will get a new
|
||||||
|
cmdset while using the Menu. The menu_module_path is the python path
|
||||||
|
to a python module containing function defintions. By adjusting the
|
||||||
|
keyword options of the Menu() initialization call you can start the
|
||||||
|
menu at different places in the menu definition file, adjust if the
|
||||||
|
menu command should overload the normal commands or not etc.
|
||||||
|
|
||||||
|
The menu is defined in a module with function defintions:
|
||||||
|
|
||||||
|
def nodename1(caller):
|
||||||
|
# code
|
||||||
|
return text, options
|
||||||
|
|
||||||
|
The return values must be given in the above order, but each can be
|
||||||
|
given as None as well
|
||||||
|
|
||||||
|
text (str or tuple): Text shown at this node. If a tuple, the second
|
||||||
|
element in the tuple is a help text to display at this node when
|
||||||
|
the user enters the menu help command there.
|
||||||
|
helptext (str): Help text shown at this node.
|
||||||
|
options (tuple): ( {'key': name, # can also be a list of aliases
|
||||||
|
'desc': description, # option description
|
||||||
|
'goto': nodekey, # node to go to when chosen
|
||||||
|
'exec': nodekey, # node or callback to trigger as callback when chosen
|
||||||
|
{...}, ...)
|
||||||
|
|
||||||
|
If key is not given, the option will automatically be identified by
|
||||||
|
its number 1..N.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```python
|
||||||
|
|
||||||
|
# in menu_module.py
|
||||||
|
|
||||||
|
def node1(caller):
|
||||||
|
text = ("This is a node text",
|
||||||
|
"This is help text for this node")
|
||||||
|
options = ({"key": "testing",
|
||||||
|
"desc": "Select this to go to node 2"
|
||||||
|
"goto": "node2",
|
||||||
|
"exec": "callback1"},
|
||||||
|
{"desc": "Go to node 3."
|
||||||
|
"goto": "node3"} )
|
||||||
|
return text, options
|
||||||
|
|
||||||
|
def callback1(caller):
|
||||||
|
# this is called when choosing the "testing" option in node1
|
||||||
|
# (before going to node2). It needs not have return values.
|
||||||
|
caller.msg("Callback called!")
|
||||||
|
|
||||||
|
def node2(caller):
|
||||||
|
text = '''
|
||||||
|
This is node 2. It only allows you to go back
|
||||||
|
to the original node1. This extra indent will
|
||||||
|
be stripped. We don't include a help text.
|
||||||
|
'''
|
||||||
|
options = {"goto": "node1"}
|
||||||
|
return text, options
|
||||||
|
|
||||||
|
def node3(caller):
|
||||||
|
text = "This ends the menu since there are no options."
|
||||||
|
return text, None
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
When starting this menu with `Menu(caller, "path.to.menu_module")`,
|
||||||
|
the first node will look something like this:
|
||||||
|
|
||||||
|
This is a node text
|
||||||
|
______________________________________
|
||||||
|
|
||||||
|
testing: Select this to go to node 2
|
||||||
|
2: Go to node 3
|
||||||
|
|
||||||
|
Where you can both enter "testing" and "1" to select the first option.
|
||||||
|
If the client supports MXP, they may also mouse-click on "testing" to
|
||||||
|
do the same. When making this selection, a function "callback1" in the
|
||||||
|
same Using `help` will show the help text, otherwise a list of
|
||||||
|
available commands while in menu mode.
|
||||||
|
|
||||||
|
The menu tree is exited either by using the in-menu quit command or by
|
||||||
|
reaching a node without any options.
|
||||||
|
|
||||||
|
|
||||||
|
For a menu demo, import CmdTestDemo form this
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from textwrap import dedent
|
||||||
|
from inspect import isfunction, getargspec
|
||||||
|
from django.conf import settings
|
||||||
|
from evennia import syscmdkeys
|
||||||
|
from evennia import Command, CmdSet
|
||||||
|
from evennia.utils.evtable import EvTable
|
||||||
|
from evennia.utils.ansi import ANSIString
|
||||||
|
from evennia.utils.utils import mod_import, make_iter, pad, m_len
|
||||||
|
|
||||||
|
# read from protocol NAWS later?
|
||||||
|
_MAX_TEXT_WIDTH = settings.CLIENT_DEFAULT_WIDTH
|
||||||
|
|
||||||
|
_CMD_NOMATCH = syscmdkeys.CMD_NOMATCH
|
||||||
|
_CMD_NOINPUT = syscmdkeys.CMD_NOINPUT
|
||||||
|
|
||||||
|
# Return messages
|
||||||
|
|
||||||
|
_ERR_NOT_IMPLEMENTED = "Menu node '{nodename}' is not implemented. Make another choice."
|
||||||
|
_ERR_GENERAL = "Error in menu node '{nodename}'."
|
||||||
|
_ERR_NO_OPTION_DESC = "No description."
|
||||||
|
_HELP_FULL = "Commands: <menu option>, help, quit"
|
||||||
|
_HELP_NO_QUIT = "Commands: <menu option>, help"
|
||||||
|
_HELP_NO_OPTIONS = "Commands: help, quit"
|
||||||
|
_HELP_NO_OPTIONS_NO_QUIT = "Commands: help"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MenuError(RuntimeError):
|
||||||
|
"""
|
||||||
|
Error raised by menu when facing internal errors.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
#------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Menu command and command set
|
||||||
|
#
|
||||||
|
#------------------------------------------------------------
|
||||||
|
|
||||||
|
class CmdMenuNode(Command):
|
||||||
|
"""
|
||||||
|
Menu options.
|
||||||
|
|
||||||
|
"""
|
||||||
|
key = "look"
|
||||||
|
aliases = ["l", _CMD_NOMATCH, _CMD_NOINPUT]
|
||||||
|
locks = "cmd:all()"
|
||||||
|
help_category = "Menu"
|
||||||
|
|
||||||
|
def func(self):
|
||||||
|
"""
|
||||||
|
Implement all menu commands.
|
||||||
|
|
||||||
|
"""
|
||||||
|
caller = self.caller
|
||||||
|
menu = caller.ndb._menutree
|
||||||
|
|
||||||
|
if not menu:
|
||||||
|
err = "Menu object not found as %s.ndb._menutree!" % (caller)
|
||||||
|
self.caller.msg(err)
|
||||||
|
raise MenuError(err)
|
||||||
|
|
||||||
|
# flags and data
|
||||||
|
raw_string = self.raw_string
|
||||||
|
cmd = raw_string.strip().lower()
|
||||||
|
options = menu.options
|
||||||
|
allow_quit = menu.allow_quit
|
||||||
|
default = menu.default
|
||||||
|
|
||||||
|
if cmd in options:
|
||||||
|
# this will overload the other commands
|
||||||
|
# if it has the same name!
|
||||||
|
goto, callback = options[cmd]
|
||||||
|
if callback:
|
||||||
|
menu.callback(callback, raw_string)
|
||||||
|
if goto:
|
||||||
|
menu.goto(goto, raw_string)
|
||||||
|
elif cmd in ("look", "l"):
|
||||||
|
caller.msg(menu.nodetext)
|
||||||
|
elif cmd in ("help", "h"):
|
||||||
|
caller.msg(menu.helptext)
|
||||||
|
elif allow_quit and cmd in ("quit", "q", "exit"):
|
||||||
|
menu.close_menu()
|
||||||
|
caller.execute_cmd("look")
|
||||||
|
elif default:
|
||||||
|
goto, callback = default
|
||||||
|
if callback:
|
||||||
|
menu.callback(callback, raw_string)
|
||||||
|
if goto:
|
||||||
|
menu.goto(goto, raw_string)
|
||||||
|
else:
|
||||||
|
caller.msg("Choose an option or try 'help'.")
|
||||||
|
|
||||||
|
if not (options or default):
|
||||||
|
# no options - we are at the end of the menu.
|
||||||
|
menu.close_menu()
|
||||||
|
caller.execute_cmd("looK")
|
||||||
|
|
||||||
|
|
||||||
|
class MenuCmdSet(CmdSet):
|
||||||
|
"""
|
||||||
|
The Menu cmdset replaces the current cmdset.
|
||||||
|
|
||||||
|
"""
|
||||||
|
key = "menu_cmdset"
|
||||||
|
priority = 1
|
||||||
|
mergetype = "Replace"
|
||||||
|
no_objs = True
|
||||||
|
no_exits = True
|
||||||
|
no_channels = False
|
||||||
|
|
||||||
|
def at_cmdset_creation(self):
|
||||||
|
"""
|
||||||
|
Called when creating the set.
|
||||||
|
"""
|
||||||
|
self.add(CmdMenuNode())
|
||||||
|
|
||||||
|
#------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Menu main class
|
||||||
|
#
|
||||||
|
#------------------------------------------------------------
|
||||||
|
|
||||||
|
class Menu(object):
|
||||||
|
"""
|
||||||
|
This object represents an operational menu. It is initialized from
|
||||||
|
a menufile.py instruction.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, caller, menufile, startnode="start",
|
||||||
|
allow_quit=True, cmdset_mergetype="Replace", cmdset_priority=1):
|
||||||
|
"""
|
||||||
|
Initialize the menu tree and start the caller onto the first node.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
caller (str): The user of the menu.
|
||||||
|
menufile (str): The full or relative path to the menufile.
|
||||||
|
startnode (str, optional): The starting node in the menufile.
|
||||||
|
allow_quit (bool, optional): Allow user to use quit or
|
||||||
|
exit to leave the menu at any point. Recommended during
|
||||||
|
development!
|
||||||
|
cmdset_mergetype (str, optional): 'Replace' (default) means the menu
|
||||||
|
commands will be exclusive - no other normal commands will
|
||||||
|
be usable while the user is in the menu. 'Union' means the
|
||||||
|
menu commands will be integrated with the existing commands
|
||||||
|
(it will merge with `merge_priority`), if so, make sure that
|
||||||
|
the menu's command names don't collide with existing commands
|
||||||
|
in an unexpected way. Also the CMD_NOMATCH and CMD_NOINPUT will
|
||||||
|
be overloaded by the menu cmdset. Other cmdser mergetypes
|
||||||
|
has little purpose for the menu.
|
||||||
|
cmdset_priority (int, optional): The merge priority for the
|
||||||
|
menu command set. The default (1) is usually enough for most
|
||||||
|
types of menus.
|
||||||
|
Raises:
|
||||||
|
MenuError: If the start/end node is not found in menu tree.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self._caller = caller
|
||||||
|
self._startnode = startnode
|
||||||
|
self._menutree = self._parse_menufile(menufile)
|
||||||
|
|
||||||
|
if startnode not in self._menutree:
|
||||||
|
raise MenuError("Start node '%s' not in menu tree!" % startnode)
|
||||||
|
|
||||||
|
# variables made available to the command
|
||||||
|
self.allow_quit = allow_quit
|
||||||
|
self.default = None
|
||||||
|
self.nodetext = None
|
||||||
|
self.helptext = None
|
||||||
|
self.options = None
|
||||||
|
|
||||||
|
# store ourself on the object
|
||||||
|
self._caller.ndb._menutree = self
|
||||||
|
|
||||||
|
# set up the menu command on the caller
|
||||||
|
menu_cmdset = MenuCmdSet()
|
||||||
|
menu_cmdset.mergetype = str(cmdset_mergetype).lower().capitalize() or "Replace"
|
||||||
|
menu_cmdset.priority = int(cmdset_priority)
|
||||||
|
self._caller.cmdset.add(menu_cmdset)
|
||||||
|
# start the menu
|
||||||
|
self.goto(self._startnode, "")
|
||||||
|
|
||||||
|
def _parse_menufile(self, menufile):
|
||||||
|
"""
|
||||||
|
Parse a menufile, split it into #node sections, convert
|
||||||
|
each to an executable python code and store in a dictionary map.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
menufile (str or module): The python.path to the menufile,
|
||||||
|
or the python module itself.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
menutree (dict): A {nodekey: func}
|
||||||
|
|
||||||
|
"""
|
||||||
|
module = mod_import(menufile)
|
||||||
|
return dict((key, func) for key, func in module.__dict__.items()
|
||||||
|
if isfunction(func) and not key.startswith("_"))
|
||||||
|
|
||||||
|
def _format_node(self, nodetext, optionlist):
|
||||||
|
"""
|
||||||
|
Format the node text + option section
|
||||||
|
|
||||||
|
Args:
|
||||||
|
nodetext (str): The node text
|
||||||
|
optionlist (list): List of (key, desc) pairs.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
string (str): The options section, including
|
||||||
|
all needed spaces.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
This will adjust the columns of the options, first to use
|
||||||
|
a maxiumum of 4 rows (expanding in columns), then gradually
|
||||||
|
growing to make use of the screen space.
|
||||||
|
|
||||||
|
"""
|
||||||
|
#
|
||||||
|
# handle the node text
|
||||||
|
#
|
||||||
|
|
||||||
|
nodetext = dedent(nodetext).strip()
|
||||||
|
|
||||||
|
nodetext_width_max = max(m_len(line) for line in nodetext.split("\n"))
|
||||||
|
|
||||||
|
if not optionlist:
|
||||||
|
# return the node text "naked".
|
||||||
|
separator1 = "_" * nodetext_width_max + "\n\n" if nodetext_width_max else ""
|
||||||
|
separator2 = "\n" if nodetext_width_max else "" + "_" * nodetext_width_max
|
||||||
|
return separator1 + nodetext + separator2
|
||||||
|
|
||||||
|
#
|
||||||
|
# handle the options
|
||||||
|
#
|
||||||
|
|
||||||
|
# column separation distance
|
||||||
|
colsep = 4
|
||||||
|
|
||||||
|
nlist = len(optionlist)
|
||||||
|
|
||||||
|
# get the widest option line in the table.
|
||||||
|
table_width_max = -1
|
||||||
|
table = []
|
||||||
|
for key, desc in optionlist:
|
||||||
|
table_width_max = max(table_width_max,
|
||||||
|
max(m_len(p) for p in key.split("\n")) +
|
||||||
|
max(m_len(p) for p in desc.split("\n")) + colsep)
|
||||||
|
table.append(ANSIString(" {lc%s{lt{w%s{n{le: %s" % (key, key, desc)))
|
||||||
|
|
||||||
|
ncols = (_MAX_TEXT_WIDTH // table_width_max) + 1 # number of ncols
|
||||||
|
nlastcol = nlist % ncols # number of elements left in last row
|
||||||
|
|
||||||
|
# get the amount of rows needed (start with 4 rows)
|
||||||
|
nrows = 4
|
||||||
|
while nrows * ncols < nlist:
|
||||||
|
nrows += 1
|
||||||
|
ncols = nlist // nrows # number of full columns
|
||||||
|
nlastcol = nlist % nrows # number of elements in last column
|
||||||
|
|
||||||
|
# get the final column count
|
||||||
|
ncols = ncols + 1 if nlastcol > 0 else ncols
|
||||||
|
if ncols > 1:
|
||||||
|
# only extend if longer than one column
|
||||||
|
table.extend([" " for i in xrange(nrows-nlastcol)])
|
||||||
|
|
||||||
|
# build the actual table grid
|
||||||
|
table = [table[icol*nrows:(icol*nrows) + nrows] for icol in xrange(0, ncols)]
|
||||||
|
|
||||||
|
# adjust the width of each column
|
||||||
|
total_width = 0
|
||||||
|
for icol in xrange(len(table)):
|
||||||
|
col_width = max(max(m_len(p) for p in part.split("\n")) for part in table[icol]) + colsep
|
||||||
|
table[icol] = [pad(part, width=col_width + colsep, align="l") for part in table[icol]]
|
||||||
|
total_width += col_width
|
||||||
|
|
||||||
|
# format the table into columns
|
||||||
|
table = EvTable(table=table, border="none")
|
||||||
|
|
||||||
|
# build the page
|
||||||
|
total_width = max(total_width, nodetext_width_max)
|
||||||
|
separator1 = "_" * total_width + "\n\n" if nodetext_width_max else ""
|
||||||
|
separator2 = "\n" + "_" * total_width + "\n\n" if total_width else ""
|
||||||
|
return separator1 + nodetext + separator2 + unicode(table)
|
||||||
|
|
||||||
|
def _execute_node(self, nodename, raw_string):
|
||||||
|
"""
|
||||||
|
Execute a node.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
nodename (str): Name of node.
|
||||||
|
raw_string (str): The raw default string entered on the
|
||||||
|
previous node (only used if the node accepts it as an
|
||||||
|
argument)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
nodetext, options (tuple): The node text (a string or a
|
||||||
|
tuple and the options tuple, if any.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
node = self._menutree[nodename]
|
||||||
|
except KeyError:
|
||||||
|
self._caller.msg(_ERR_NOT_IMPLEMENTED.format(nodename=nodename))
|
||||||
|
raise MenuError
|
||||||
|
try:
|
||||||
|
# the node should return data as (text, options)
|
||||||
|
if len(getargspec(node).args) > 1:
|
||||||
|
# a node accepting raw_string
|
||||||
|
nodetext, options = node(self._caller, raw_string)
|
||||||
|
else:
|
||||||
|
# a normal node, only accepting caller
|
||||||
|
nodetext, options = node(self._caller)
|
||||||
|
except KeyError:
|
||||||
|
self._caller.msg(_ERR_NOT_IMPLEMENTED.format(nodename=nodename))
|
||||||
|
raise MenuError
|
||||||
|
except Exception:
|
||||||
|
self._caller.msg(_ERR_GENERAL.format(nodename=nodename))
|
||||||
|
raise
|
||||||
|
return nodetext, options
|
||||||
|
|
||||||
|
|
||||||
|
def callback(self, nodename, raw_string):
|
||||||
|
"""
|
||||||
|
Run a node as a callback. This makes no use of the return
|
||||||
|
values from the node.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
nodename (str): Name of node.
|
||||||
|
raw_string (str): The raw default string entered on the
|
||||||
|
previous node (only used if the node accepts it as an
|
||||||
|
argument)
|
||||||
|
|
||||||
|
"""
|
||||||
|
if callable(nodename):
|
||||||
|
# this is a direct callable - execute it directly
|
||||||
|
try:
|
||||||
|
if len(getargspec(nodename).args) > 1:
|
||||||
|
# callable accepting raw_string
|
||||||
|
nodename(self._caller, raw_string)
|
||||||
|
else:
|
||||||
|
# normal callable, only the caller as arg
|
||||||
|
nodename(self._caller)
|
||||||
|
except Exception:
|
||||||
|
self._caller.msg(_ERR_GENERAL.format(nodename=nodename))
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
# nodename is a string; lookup as node
|
||||||
|
try:
|
||||||
|
# execute the node; we make no use of the return values here.
|
||||||
|
self._execute_node(nodename, raw_string)
|
||||||
|
except MenuError:
|
||||||
|
return
|
||||||
|
|
||||||
|
def goto(self, nodename, raw_string):
|
||||||
|
"""
|
||||||
|
Run a node by name
|
||||||
|
|
||||||
|
Args:
|
||||||
|
nodename (str): Name of node.
|
||||||
|
raw_string (str): The raw default string entered on the
|
||||||
|
previous node (only used if the node accepts it as an
|
||||||
|
argument)
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# execute the node, make use of the returns.
|
||||||
|
nodetext, options = self._execute_node(nodename, raw_string)
|
||||||
|
except MenuError:
|
||||||
|
return
|
||||||
|
|
||||||
|
# validation of the node return values
|
||||||
|
helptext = ""
|
||||||
|
if hasattr(nodetext, "__iter__"):
|
||||||
|
if len(nodetext) > 1:
|
||||||
|
nodetext, helptext = nodetext[:2]
|
||||||
|
else:
|
||||||
|
nodetext = nodetext[0]
|
||||||
|
nodetext = str(nodetext) or ""
|
||||||
|
options = [options] if isinstance(options, dict) else options
|
||||||
|
|
||||||
|
# this will be displayed in the given order
|
||||||
|
display_options = []
|
||||||
|
# this is used for lookup
|
||||||
|
self.options = {}
|
||||||
|
self.default = None
|
||||||
|
if options:
|
||||||
|
for inum, dic in enumerate(options):
|
||||||
|
# fix up the option dicts
|
||||||
|
keys = make_iter(dic.get("key"))
|
||||||
|
if "_default" in keys:
|
||||||
|
keys = [key for key in keys if key != "_default"]
|
||||||
|
desc = dic.get("desc", dic.get("text", _ERR_NO_OPTION_DESC).strip())
|
||||||
|
goto, execute = dic.get("goto", None), dic.get("exec", None)
|
||||||
|
self.default = (goto, execute)
|
||||||
|
else:
|
||||||
|
keys = list(make_iter(dic.get("key", str(inum+1).strip()))) + [str(inum+1)]
|
||||||
|
desc = dic.get("desc", dic.get("text", _ERR_NO_OPTION_DESC).strip())
|
||||||
|
goto, execute = dic.get("goto", None), dic.get("exec", None)
|
||||||
|
|
||||||
|
if keys:
|
||||||
|
display_options.append((keys[0], desc))
|
||||||
|
for key in keys:
|
||||||
|
if goto or execute:
|
||||||
|
self.options[key.strip().lower()] = (goto, execute)
|
||||||
|
|
||||||
|
self.nodetext = self._format_node(nodetext, display_options)
|
||||||
|
|
||||||
|
# handle the helptext
|
||||||
|
if helptext:
|
||||||
|
self.helptext = helptext
|
||||||
|
elif options:
|
||||||
|
self.helptext = _HELP_FULL if self.allow_quit else _HELP_NO_QUIT
|
||||||
|
else:
|
||||||
|
self.helptext = _HELP_NO_OPTIONS if self.allow_quit else _HELP_NO_OPTIONS_NO_QUIT
|
||||||
|
|
||||||
|
self._caller.execute_cmd("look")
|
||||||
|
|
||||||
|
def close_menu(self):
|
||||||
|
"""
|
||||||
|
Shutdown menu; occurs when reaching the end node.
|
||||||
|
"""
|
||||||
|
self._caller.cmdset.remove(MenuCmdSet)
|
||||||
|
del self._caller.ndb._menutree
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# test menu strucure and testing command
|
||||||
|
#
|
||||||
|
#------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_start_node(caller):
|
||||||
|
text = """
|
||||||
|
This is an example menu.
|
||||||
|
|
||||||
|
If you enter anything except the valid options, your input will be
|
||||||
|
recorded and you will be brought to a menu entry showing your
|
||||||
|
input.
|
||||||
|
|
||||||
|
Select options or use 'quit' to exit the menu.
|
||||||
|
"""
|
||||||
|
options = ({"key": ("{yS{net", "s"),
|
||||||
|
"desc": "Set an attribute on yourself.",
|
||||||
|
"exec": lambda caller: caller.attributes.add("menuattrtest", "Test value"),
|
||||||
|
"goto": "test_set_node"},
|
||||||
|
{"key": ("{yV{niew", "v"),
|
||||||
|
"desc": "View your own name",
|
||||||
|
"goto": "test_view_node"},
|
||||||
|
{"key": ("{yQ{nuit", "quit", "q", "Q"),
|
||||||
|
"desc": "Quit this menu example.",
|
||||||
|
"goto": "test_end_node"},
|
||||||
|
{"key": "_default",
|
||||||
|
"goto": "test_displayinput_node"})
|
||||||
|
return text, options
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_node(caller):
|
||||||
|
text = ("""
|
||||||
|
The attribute 'menuattrtest' was set to
|
||||||
|
|
||||||
|
{w%s{n
|
||||||
|
|
||||||
|
(check it with examine after quitting the menu).
|
||||||
|
|
||||||
|
This node's has only one option, and one of its key aliases is the
|
||||||
|
string "_default", meaning it will catch any input, in this case
|
||||||
|
to return to the main menu. So you can e.g. press <return> to go
|
||||||
|
back now.
|
||||||
|
""" % caller.db.menuattrtest,
|
||||||
|
# optional help text for this node
|
||||||
|
"""
|
||||||
|
This is the help entry for this node. It is created by returning
|
||||||
|
the node text as a tuple - the second string in that tuple will be
|
||||||
|
used as the help text.
|
||||||
|
""")
|
||||||
|
|
||||||
|
options = {"key": ("back (default)", "_default"),
|
||||||
|
"desc": "back to main",
|
||||||
|
"goto": "test_start_node"}
|
||||||
|
return text, options
|
||||||
|
|
||||||
|
|
||||||
|
def test_view_node(caller):
|
||||||
|
text = """
|
||||||
|
Your name is {g%s{n!
|
||||||
|
|
||||||
|
This node's option has no explicit key (nor the "_default" key
|
||||||
|
set), and so gets assigned a number automatically. You can infact
|
||||||
|
-always- use numbers (1...N) to refer to listed options also if you
|
||||||
|
don't see a string option key (try it!).
|
||||||
|
""" % caller.key
|
||||||
|
options = {"desc": "back to main",
|
||||||
|
"goto": "test_start_node"}
|
||||||
|
return text, options
|
||||||
|
|
||||||
|
|
||||||
|
def test_displayinput_node(caller, raw_string):
|
||||||
|
text = """
|
||||||
|
You entered the text:
|
||||||
|
|
||||||
|
"{w%s{n"
|
||||||
|
|
||||||
|
... which could now be handled or stored here in some way if this
|
||||||
|
was not just an example.
|
||||||
|
|
||||||
|
This node has an option with a single alias "_default", which
|
||||||
|
makes it hidden from view. It catches all input (except the
|
||||||
|
in-menu help/quit commands) and will, in this case, bring you back
|
||||||
|
to the start node.
|
||||||
|
""" % raw_string
|
||||||
|
options = {"key": "_default",
|
||||||
|
"goto": "test_start_node"}
|
||||||
|
return text, options
|
||||||
|
|
||||||
|
|
||||||
|
def test_end_node(caller):
|
||||||
|
text = """
|
||||||
|
This is the end of the menu and since it has no options the menu
|
||||||
|
will exit here, followed by a call of the "look" command.
|
||||||
|
"""
|
||||||
|
return text, None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CmdTestMenu(Command):
|
||||||
|
"""
|
||||||
|
Test menu
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
testmenu <menumodule>
|
||||||
|
|
||||||
|
Starts a demo menu from a menu node definition module.
|
||||||
|
|
||||||
|
"""
|
||||||
|
key = "testmenu"
|
||||||
|
|
||||||
|
def func(self):
|
||||||
|
|
||||||
|
if not self.args:
|
||||||
|
self.caller.msg("Usage: testmenu <menumodule>")
|
||||||
|
return
|
||||||
|
# start menu
|
||||||
|
Menu(self.caller, self.args.strip(), startnode="test_start_node", cmdset_mergetype="Replace")
|
||||||
Loading…
Add table
Add a link
Reference in a new issue