Update the building menu, following Griatch's feedback
This commit is contained in:
parent
415322fe1a
commit
fa31367a76
2 changed files with 164 additions and 24 deletions
|
|
@ -3,11 +3,39 @@ Module containing the building menu system.
|
||||||
|
|
||||||
Evennia contributor: vincent-lg 2018
|
Evennia contributor: vincent-lg 2018
|
||||||
|
|
||||||
Building menus are similar to `EvMenu`, except that they have been
|
Building menus are in-game menus, not unlike `EvMenu` though using a
|
||||||
specifically designed to edit information as a builder. Creating a
|
different approach. Building menus have been specifically designed to edit
|
||||||
building menu in a command allows builders quick-editing of a
|
information as a builder. Creating a building menu in a command allows
|
||||||
given object, like a room. Here is an example of output you could
|
builders quick-editing of a given object, like a room. If you follow the
|
||||||
obtain when editing the room:
|
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)
|
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
|
(it will write in the room's `key` attribute) and then go back to the
|
||||||
main menu using `@`.
|
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,
|
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
|
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
|
function that you have defined above in your module. This function will be
|
||||||
called when the menu element is triggered.
|
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
|
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,
|
class, create it specifying your intended caller and object to edit,
|
||||||
then call `open`:
|
then call `open`:
|
||||||
|
|
@ -66,6 +106,8 @@ from <wherever> import RoomBuildingMenu
|
||||||
|
|
||||||
class CmdEdit(Command):
|
class CmdEdit(Command):
|
||||||
|
|
||||||
|
key = "redit"
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
menu = RoomBuildingMenu(self.caller, self.caller.location)
|
menu = RoomBuildingMenu(self.caller, self.caller.location)
|
||||||
menu.open()
|
menu.open()
|
||||||
|
|
@ -114,7 +156,7 @@ def _menu_savefunc(caller, buf):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _menu_quitfunc(caller):
|
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:
|
if caller.ndb._building_menu:
|
||||||
caller.ndb._building_menu.move(back=True)
|
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
|
menu (BuildingMenu, optional): the building menu to pass to value
|
||||||
if it is a callable.
|
if it is a callable.
|
||||||
choice (Choice, optional): the choice to pass to value if 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.
|
obj (Object): the object to pass to value if a callable.
|
||||||
caller (Account or Object, optional): the caller to pass to value
|
caller (Account or Object, optional): the caller to pass to value
|
||||||
if a callable.
|
if a callable.
|
||||||
|
|
@ -202,7 +244,10 @@ def menu_setattr(menu, choice, obj, string):
|
||||||
"""
|
"""
|
||||||
attr = getattr(choice, "attr", None) if choice else None
|
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:
|
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
|
return
|
||||||
|
|
||||||
for part in attr.split(".")[:-1]:
|
for part in attr.split(".")[:-1]:
|
||||||
|
|
@ -219,6 +264,11 @@ def menu_quit(caller, menu):
|
||||||
caller (Account or Object): the caller.
|
caller (Account or Object): the caller.
|
||||||
menu (BuildingMenu): the building menu to close.
|
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:
|
if caller is None or menu is None:
|
||||||
log_err("The function `menu_quit` was called with missing arguments: caller={}, menu={}".format(caller, menu))
|
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):
|
def menu_edit(caller, choice, obj):
|
||||||
"""
|
"""
|
||||||
Open the EvEditor to edit a specified field.
|
Open the EvEditor to edit a specified attribute.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
caller (Account or Object): the caller.
|
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.
|
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
|
A building menu is somewhat similar to `EvMenu`, but designed to edit
|
||||||
builders, although it can be used for players in some contexts. You
|
objects by builders, although it can be used for players in some contexts.
|
||||||
could, for instance, create a building menu to edit a room with a
|
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,
|
sub-menu for the room's key, another for the room's description,
|
||||||
another for the room's exits, and so on.
|
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
|
full documentation of this method). With most arguments, you can
|
||||||
specify either a plain string or a callback. This callback will be
|
specify either a plain string or a callback. This callback will be
|
||||||
called when the operation is to be performed.
|
called when the operation is to be performed.
|
||||||
|
|
@ -492,9 +542,13 @@ class BuildingMenu(object):
|
||||||
self.persistent = persistent
|
self.persistent = persistent
|
||||||
self.choices = []
|
self.choices = []
|
||||||
self.cmds = {}
|
self.cmds = {}
|
||||||
|
self.can_quit = False
|
||||||
|
|
||||||
if obj:
|
if obj:
|
||||||
self.init(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()
|
self._add_keys_choice()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -686,16 +740,26 @@ class BuildingMenu(object):
|
||||||
key = key.lower()
|
key = key.lower()
|
||||||
aliases = aliases or []
|
aliases = aliases or []
|
||||||
aliases = [a.lower() for a in aliases]
|
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:
|
if attr and on_nomatch is None:
|
||||||
on_nomatch = menu_setattr
|
on_nomatch = menu_setattr
|
||||||
|
|
||||||
if key and key in self.cmds:
|
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)))
|
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,
|
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)
|
menu=self, caller=self.caller, obj=self.obj)
|
||||||
self.choices.append(choice)
|
self.choices.append(choice)
|
||||||
|
|
@ -731,7 +795,7 @@ class BuildingMenu(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
on_enter = on_enter or menu_edit
|
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):
|
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
|
on_enter = on_enter or menu_quit
|
||||||
|
self.can_quit = True
|
||||||
return self.add_choice(title, key=key, aliases=aliases, on_enter=on_enter)
|
return self.add_choice(title, key=key, aliases=aliases, on_enter=on_enter)
|
||||||
|
|
||||||
def open(self):
|
def open(self):
|
||||||
|
|
@ -767,6 +832,11 @@ class BuildingMenu(object):
|
||||||
"""
|
"""
|
||||||
caller = self.caller
|
caller = self.caller
|
||||||
self._save()
|
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.caller.cmdset.add(BuildingMenuCmdSet, permanent=self.persistent)
|
||||||
self.display()
|
self.display()
|
||||||
|
|
||||||
|
|
@ -923,7 +993,11 @@ class BuildingMenu(object):
|
||||||
|
|
||||||
if choice.glance:
|
if choice.glance:
|
||||||
glance = _call_or_get(choice.glance, menu=self, choice=choice, caller=self.caller, string="", obj=self.obj)
|
glance = _call_or_get(choice.glance, menu=self, choice=choice, caller=self.caller, string="", obj=self.obj)
|
||||||
|
try:
|
||||||
glance = glance.format(obj=self.obj, caller=self.caller)
|
glance = glance.format(obj=self.obj, caller=self.caller)
|
||||||
|
except:
|
||||||
|
import pdb;pdb.set_trace()
|
||||||
|
|
||||||
ret += ": " + glance
|
ret += ": " + glance
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
@ -978,3 +1052,70 @@ class BuildingMenu(object):
|
||||||
return
|
return
|
||||||
|
|
||||||
return building_menu
|
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()
|
||||||
|
|
|
||||||
|
|
@ -1712,7 +1712,6 @@ class TestBuildingMenu(CommandTest):
|
||||||
super(TestBuildingMenu, self).setUp()
|
super(TestBuildingMenu, self).setUp()
|
||||||
self.menu = BuildingMenu(caller=self.char1, obj=self.room1, title="test")
|
self.menu = BuildingMenu(caller=self.char1, obj=self.room1, title="test")
|
||||||
self.menu.add_choice("title", key="t", attr="key")
|
self.menu.add_choice("title", key="t", attr="key")
|
||||||
self.menu.add_choice_quit()
|
|
||||||
|
|
||||||
def test_quit(self):
|
def test_quit(self):
|
||||||
"""Try to quit the building menu."""
|
"""Try to quit the building menu."""
|
||||||
|
|
@ -1774,9 +1773,9 @@ class TestBuildingMenu(CommandTest):
|
||||||
def on_nomatch_t2(caller, menu):
|
def on_nomatch_t2(caller, menu):
|
||||||
menu.move("t3") # this time the key matters
|
menu.move("t3") # this time the key matters
|
||||||
|
|
||||||
t1 = self.menu.add_choice("what", key="t1", attr="t1", on_nomatch=on_nomatch_t1)
|
t1 = self.menu.add_choice("what", key="t1", on_nomatch=on_nomatch_t1)
|
||||||
t2 = self.menu.add_choice("and", key="t1.*", attr="t2", on_nomatch=on_nomatch_t2)
|
t2 = self.menu.add_choice("and", key="t1.*", on_nomatch=on_nomatch_t2)
|
||||||
t3 = self.menu.add_choice("why", key="t1.*.t3", attr="t3")
|
t3 = self.menu.add_choice("why", key="t1.*.t3")
|
||||||
self.menu.open()
|
self.menu.open()
|
||||||
|
|
||||||
# Move into t1
|
# Move into t1
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue