Update the building menu, following Griatch's feedback

This commit is contained in:
Vincent Le Goff 2018-09-04 20:33:54 +02:00
parent 415322fe1a
commit fa31367a76
2 changed files with 164 additions and 24 deletions

View file

@ -3,11 +3,39 @@ Module containing the building menu system.
Evennia contributor: vincent-lg 2018
Building menus are similar to `EvMenu`, except that they have been
specifically designed to edit information as a builder. Creating a
building menu in a command allows builders quick-editing of a
given object, like a room. Here is an example of output you could
obtain when editing the room:
Building menus are in-game menus, not unlike `EvMenu` though using a
different approach. Building menus have been specifically designed to edit
information as a builder. Creating a building menu in a command allows
builders quick-editing of a given object, like a room. If you follow the
steps below to add the contrib, you will have access to an `@edit` command
that will edit any default object offering to change its key and description.
1. Import the `GenericBuildingCmd` class from this contrib in your `mygame/commands/default_cmdset.py` file:
```python
from evennia.contrib.building_menu import GenericBuildingCmd
```
2. Below, add the command in the `CharacterCmdSet`:
```python
# ... These lines should exist in the file
class CharacterCmdSet(default_cmds.CharacterCmdSet):
key = "DefaultCharacter"
def at_cmdset_creation(self):
super(CharacterCmdSet, self).at_cmdset_creation()
# ... add the line below
self.add(GenericBuildingCmd())
```
The `@edit` command will allow you to edit any object. You will need to
specify the object name or ID as an argument. For instance: `@edit here`
will edit the current room. However, building menus can perform much more
than this very simple example, read on for more details.
Building menus can be set to edit about anything. Here is an example of
output you could obtain when editing the room:
```
Editing the room: Limbo(#2)
@ -51,12 +79,24 @@ and enter t, she will be in the title choice. She can change the title
(it will write in the room's `key` attribute) and then go back to the
main menu using `@`.
`add_choice` has a lot of arguments and offer a great deal of
`add_choice` has a lot of arguments and offers a great deal of
flexibility. The most useful ones is probably the usage of callbacks,
as you can set almost any argument in `add_choice` to be a callback, a
function that you have defined above in your module. This function will be
called when the menu element is triggered.
Notice that in order to edit a description, the best method to call isn't
`add_choice`, but `add_choice_edit`. This is a convenient shortcut
which is available to quickly open an `EvEditor` when entering this choice
and going back to the menu when the editor closes.
```
class RoomBuildingMenu(BuildingMenu):
def init(self, room):
self.add_choice("title", "t", attr="key")
self.add_choice_edit("description", key="d", attr="db.desc")
```
When you wish to create a building menu, you just need to import your
class, create it specifying your intended caller and object to edit,
then call `open`:
@ -66,6 +106,8 @@ from <wherever> import RoomBuildingMenu
class CmdEdit(Command):
key = "redit"
def func(self):
menu = RoomBuildingMenu(self.caller, self.caller.location)
menu.open()
@ -114,7 +156,7 @@ def _menu_savefunc(caller, buf):
return True
def _menu_quitfunc(caller):
caller.cmdset.add(BuildingMenuCmdSet, permanent=calelr.ndb._building_menu and caller.ndb._building_menu.persistent or False)
caller.cmdset.add(BuildingMenuCmdSet, permanent=caller.ndb._building_menu and caller.ndb._building_menu.persistent or False)
if caller.ndb._building_menu:
caller.ndb._building_menu.move(back=True)
@ -129,7 +171,7 @@ def _call_or_get(value, menu=None, choice=None, string=None, obj=None, caller=No
menu (BuildingMenu, optional): the building menu to pass to value
if it is a callable.
choice (Choice, optional): the choice to pass to value if a callable.
string (str, optional): the raw string to pass to value if a callback. if a callable.
string (str, optional): the raw string to pass to value if a callback.
obj (Object): the object to pass to value if a callable.
caller (Account or Object, optional): the caller to pass to value
if a callable.
@ -202,7 +244,10 @@ def menu_setattr(menu, choice, obj, string):
"""
attr = getattr(choice, "attr", None) if choice else None
if choice is None or string is None or attr is None or menu is None:
log_err("The `menu_setattr` function was called to set the attribute {} of object {} to {}, but the choice {} of menu {} or another information is missing.".format(attr, obj, repr(string), choice, menu))
log_err(dedent("""
The `menu_setattr` function was called to set the attribute {} of object {} to {},
but the choice {} of menu {} or another information is missing.
""".format(attr, obj, repr(string), choice, menu)).strip("\n")).strip()
return
for part in attr.split(".")[:-1]:
@ -219,6 +264,11 @@ def menu_quit(caller, menu):
caller (Account or Object): the caller.
menu (BuildingMenu): the building menu to close.
Note:
This callback is used by default when using the
`BuildingMenu.add_choice_quit` method. This method is called
automatically if the menu has no parent.
"""
if caller is None or menu is None:
log_err("The function `menu_quit` was called with missing arguments: caller={}, menu={}".format(caller, menu))
@ -231,7 +281,7 @@ def menu_quit(caller, menu):
def menu_edit(caller, choice, obj):
"""
Open the EvEditor to edit a specified field.
Open the EvEditor to edit a specified attribute.
Args:
caller (Account or Object): the caller.
@ -437,13 +487,13 @@ class BuildingMenu(object):
"""
Class allowing to create and set building menus to edit specific objects.
A building menu is a kind of `EvMenu` designed to edit objects by
builders, although it can be used for players in some contexts. You
could, for instance, create a building menu to edit a room with a
A building menu is somewhat similar to `EvMenu`, but designed to edit
objects by builders, although it can be used for players in some contexts.
You could, for instance, create a building menu to edit a room with a
sub-menu for the room's key, another for the room's description,
another for the room's exits, and so on.
To add choices (sub-menus), you should call `add_choice` (see the
To add choices (simple sub-menus), you should call `add_choice` (see the
full documentation of this method). With most arguments, you can
specify either a plain string or a callback. This callback will be
called when the operation is to be performed.
@ -492,9 +542,13 @@ class BuildingMenu(object):
self.persistent = persistent
self.choices = []
self.cmds = {}
self.can_quit = False
if obj:
self.init(obj)
if not parents and not self.can_quit:
# Automatically add the menu to quit
self.add_choice_quit(key=None)
self._add_keys_choice()
@property
@ -686,16 +740,26 @@ class BuildingMenu(object):
key = key.lower()
aliases = aliases or []
aliases = [a.lower() for a in aliases]
if on_enter is None and on_nomatch is None:
if attr is None:
raise ValueError("The choice {} has neither attr nor callback, specify one of these as arguments".format(title))
if attr and on_nomatch is None:
on_nomatch = menu_setattr
if key and key in self.cmds:
raise ValueError("A conflict exists between {} and {}, both use key or alias {}".format(self.cmds[key], title, repr(key)))
if attr:
if glance is None:
glance = "{obj." + attr + "}"
if text is None:
text = """
-------------------------------------------------------------------------------
{attr} for {{obj}}(#{{obj.id}})
You can change this value simply by entering it.
Use |y{back}|n to go back to the main menu.
Current value: |c{{{obj_attr}}}|n
""".format(attr=attr, obj_attr="obj." + attr, back="|n or |y".join(self.keys_go_back))
choice = Choice(title, key=key, aliases=aliases, attr=attr, text=text, glance=glance, on_enter=on_enter, on_nomatch=on_nomatch, on_leave=on_leave,
menu=self, caller=self.caller, obj=self.obj)
self.choices.append(choice)
@ -731,7 +795,7 @@ class BuildingMenu(object):
"""
on_enter = on_enter or menu_edit
return self.add_choice(title, key=key, aliases=aliases, attr=attr, glance=glance, on_enter=on_enter)
return self.add_choice(title, key=key, aliases=aliases, attr=attr, glance=glance, on_enter=on_enter, text="")
def add_choice_quit(self, title="quit the menu", key="q", aliases=None, on_enter=None):
"""
@ -753,6 +817,7 @@ class BuildingMenu(object):
"""
on_enter = on_enter or menu_quit
self.can_quit = True
return self.add_choice(title, key=key, aliases=aliases, on_enter=on_enter)
def open(self):
@ -767,6 +832,11 @@ class BuildingMenu(object):
"""
caller = self.caller
self._save()
# Remove the same-key cmdset if exists
if caller.cmdset.has(BuildingMenuCmdSet):
caller.cmdset.remove(BuildingMenuCmdSet)
self.caller.cmdset.add(BuildingMenuCmdSet, permanent=self.persistent)
self.display()
@ -923,7 +993,11 @@ class BuildingMenu(object):
if choice.glance:
glance = _call_or_get(choice.glance, menu=self, choice=choice, caller=self.caller, string="", obj=self.obj)
glance = glance.format(obj=self.obj, caller=self.caller)
try:
glance = glance.format(obj=self.obj, caller=self.caller)
except:
import pdb;pdb.set_trace()
ret += ": " + glance
return ret
@ -978,3 +1052,70 @@ class BuildingMenu(object):
return
return building_menu
# Generic building menu and command
class GenericBuildingMenu(BuildingMenu):
"""A generic building menu, allowing to edit any object.
This is more a demonstration menu. By default, it allows to edit the
object key and description. Nevertheless, it will be useful to demonstrate
how building menus are meant to be used.
"""
def init(self, obj):
"""Build the meny, adding the 'key' and 'description' choices.
Args:
obj (Object): any object to be edited, like a character or room.
Note:
The 'quit' choice will be automatically added, though you can
call `add_choice_quit` to add this choice with different options.
"""
self.add_choice("key", key="k", attr="key", glance="{obj.key}", text="""
-------------------------------------------------------------------------------
Editing the key of {{obj.key}}(#{{obj.id}})
You can change the simply by entering it.
Use |y{back}|n to go back to the main menu.
Current key: |c{{obj.key}}|n
""".format(back="|n or |y".join(self.keys_go_back)))
self.add_choice_edit("description", key="d", attr="db.desc")
class GenericBuildingCmd(Command):
"""
Generic building command.
Syntax:
@edit [object]
Open a building menu to edit the specified object. This menu allows to
change the object's key and description.
Examples:
@edit here
@edit self
@edit #142
"""
key = "@edit"
def func(self):
if not self.args.strip():
self.msg("You should provide an argument to this function: the object to edit.")
return
obj = self.caller.search(self.args.strip(), global_search=True)
if not obj:
return
menu = GenericBuildingMenu(self.caller, obj)
menu.open()

View file

@ -1712,7 +1712,6 @@ class TestBuildingMenu(CommandTest):
super(TestBuildingMenu, self).setUp()
self.menu = BuildingMenu(caller=self.char1, obj=self.room1, title="test")
self.menu.add_choice("title", key="t", attr="key")
self.menu.add_choice_quit()
def test_quit(self):
"""Try to quit the building menu."""
@ -1774,9 +1773,9 @@ class TestBuildingMenu(CommandTest):
def on_nomatch_t2(caller, menu):
menu.move("t3") # this time the key matters
t1 = self.menu.add_choice("what", key="t1", attr="t1", on_nomatch=on_nomatch_t1)
t2 = self.menu.add_choice("and", key="t1.*", attr="t2", on_nomatch=on_nomatch_t2)
t3 = self.menu.add_choice("why", key="t1.*.t3", attr="t3")
t1 = self.menu.add_choice("what", key="t1", on_nomatch=on_nomatch_t1)
t2 = self.menu.add_choice("and", key="t1.*", on_nomatch=on_nomatch_t2)
t3 = self.menu.add_choice("why", key="t1.*.t3")
self.menu.open()
# Move into t1