# EvMenu ```shell Is your answer yes or no? _________________________________________ [Y]es! - Answer yes. [N]o! - Answer no. [A]bort - Answer neither, and abort. > Y You chose yes! Thanks for your answer. Goodbye! ``` _EvMenu_ is used for generate branching multi-choice menus. Each menu 'node' can accepts specific options as input or free-form input. Depending what the player chooses, they are forwarded to different nodes in the menu. The `EvMenu` utility class is located in [evennia/utils/evmenu.py](evennia.utils.evmenu). It allows for easily adding interactive menus to the game; for example to implement Character creation, building commands or similar. Below is an example of offering NPC conversation choices: This is how the example menu at the top of this page will look in code: ```python from evennia.utils import evmenu def _handle_answer(caller, raw_input, **kwargs): answer = kwargs.get("answer") caller.msg(f"You chose {answer}!") return "end" # name of next node def node_question(caller, raw_input, **kwargs): text = "Is your answer yes or no?" options = ( {"key": ("[Y]es!", "yes", "y"), "desc": Answer yes.", "goto": _handle_answer, {"answer": "yes"}}, {"key": ("[N]o!", "no", "n"), "desc": "Answer no.", "goto": _handle_answer, {"answer": "no"}}, {"key": ("[A]bort", "abort", "a"), "desc": "Answer neither, and abort.", "goto": "end"} ) return text, options def node_end(caller, raw_input, **kwargs): text "Thanks for your answer. Goodbye!" return text, None # empty options ends the menu evmenu.EvMenu(caller, {"start": node_question, "end": node_end}) ``` Note the call to `EvMenu` at the end; this immediately creates the menu for the `caller`. It also assigns the two node-functions to menu node-names `start` and `end`, which is what the menu then uses to reference the nodes. Each node of the menu is a function that returns the text and a list of dicts describing the choices you can make on that node. Each option details what it should show (key/desc) as well as which node to go to (goto) next. The "goto" should be the name of the next node to go (if `None`, the same node will be rerun again). Above, the `Abort` option gives the "end" node name just as a string whereas the yes/no options instead uses the callable `_handle_answer` but pass different arguments to it. `_handle_answer` then returns the name of the next node (this allows you to perform actions when making a choice before you move on to the next node the menu). Note that `_handle_answer` is _not_ a node in the menu, it's just a helper function. When choosing 'yes' (or 'no') what happens here is that `_handle_answer` gets called and echoes your choice before directing to the "end" node, which exits the menu (since it doesn't return any options). You can also write menus using the [EvMenu templating language](#evmenu-templating-language). This allows you to use a text string to generate simpler menus with less boiler plate. Let's create exactly the same menu using the templating language: ```python from evennia.utils import evmenu def _handle_answer(caller, raw_input, **kwargs): answer = kwargs.get("answer") caller.msg(f"You chose {answer}!") return "end" # name of next node menu_template = """ ## node start Is your answer yes or no? ## options [Y]es!;yes;y: Answer yes. -> handle_answer(answer=yes) [N]o!;no;n: Answer no. -> handle_answer(answer=no) [A]bort;abort;a: Answer neither, and abort. -> end ## node end Thanks for your answer. Goodbye! """ evmenu.template2menu(caller, menu_template, {"handle_answer": _handle_answer}) ``` As seen, the `_handle_answer` is the same, but the menu structure is described in the `menu_template` string. The `template2menu` helper uses the template-string and a mapping of callables (we must add `_handle_answer` here) to build a full EvMenu for us. Here's another menu example, where we can choose how to interact with an NPC: ``` The guard looks at you suspiciously. "No one is supposed to be in here ..." he says, a hand on his weapon. _______________________________________________ 1. Try to bribe him [Cha + 10 gold] 2. Convince him you work here [Int] 3. Appeal to his vanity [Cha] 4. Try to knock him out [Luck + Dex] 5. Try to run away [Dex] ``` ```python def _skill_check(caller, raw_string, **kwargs): skills = kwargs.get("skills", []) gold = kwargs.get("gold", 0) # perform skill check here, decide if check passed or not # then decide which node-name to return based on # the result ... return next_node_name def node_guard(caller, raw_string, **kwarg): text = ( 'The guard looks at you suspiciously.\n' '"No one is supposed to be in here ..."\n' 'he says, a hand on his weapon.' options = ( {"desc": "Try to bribe on [Cha + 10 gold]", "goto": (_skill_check, {"skills": ["Cha"], "gold": 10})}, {"desc": "Convince him you work here [Int].", "goto": (_skill_check, {"skills": ["Int"]})}, {"desc": "Appeal to his vanity [Cha]", "goto": (_skill_check, {"skills": ["Cha"]})}, {"desc": "Try to knock him out [Luck + Dex]", "goto": (_skill_check, {"skills"" ["Luck", "Dex"]})}, {"desc": "Try to run away [Dex]", "goto": (_skill_check, {"skills": ["Dex"]})} return text, options ) # EvMenu called below, with all the nodes ... ``` Note that by skipping the `key` of the options, we instead get an (auto-generated) list of numbered options to choose from. Here the `_skill_check` helper will check (roll your stats, exactly what this means depends on your game) to decide if your approach succeeded. It may then choose to point you to nodes that continue the conversation or maybe dump you into combat! ## Launching the menu Initializing the menu is done using a call to the `evennia.utils.evmenu.EvMenu` class. This is the most common way to do so - from inside a [Command](./Commands.md): ```python # in, for example gamedir/commands/command.py from evennia.utils.evmenu import EvMenu class CmdTestMenu(Command): key = "testcommand" def func(self): EvMenu(self.caller, "world.mymenu") ``` When running this command, the menu will start using the menu nodes loaded from `mygame/world/mymenu.py`. See next section on how to define menu nodes. The `EvMenu` has the following optional callsign: ```python EvMenu(caller, menu_data, startnode="start", cmdset_mergetype="Replace", cmdset_priority=1, auto_quit=True, auto_look=True, auto_help=True, cmd_on_exit="look", persistent=False, startnode_input="", session=None, debug=False, **kwargs) ``` - `caller` (Object or Account): is a reference to the object using the menu. This object will get a new [CmdSet](./Command-Sets.md) assigned to it, for handling the menu. - `menu_data` (str, module or dict): is a module or python path to a module where the global-level functions will each be considered to be a menu node. Their names in the module will be the names by which they are referred to in the module. Importantly, function names starting with an underscore `_` will be ignored by the loader. Alternatively, this can be a direct mapping `{"nodename":function, ...}`. - `startnode` (str): is the name of the menu-node to start the menu at. Changing this means that you can jump into a menu tree at different positions depending on circumstance and thus possibly re-use menu entries. - `cmdset_mergetype` (str): This is usually one of "Replace" or "Union" (see [CmdSets](Command- Sets). The first means that the menu is exclusive - the user has no access to any other commands while in the menu. The Union mergetype means the menu co-exists with previous commands (and may overload them, so be careful as to what to name your menu entries in this case). - `cmdset_priority` (int): The priority with which to merge in the menu cmdset. This allows for advanced usage. - `auto_quit`, `auto_look`, `auto_help` (bool): If either of these are `True`, the menu automatically makes a `quit`, `look` or `help` command available to the user. The main reason why you'd want to turn this off is if you want to use the aliases "q", "l" or "h" for something in your menu. The `auto_help` also activates the ability to have arbitrary "tool tips" in your menu node (see below), At least `quit` is highly recommend - if `False`, the menu *must* itself supply an "exit node" (a node without any options), or the user will be stuck in the menu until the server reloads (or eternally if the menu is `persistent`)! - `cmd_on_exit` (str): This command string will be executed right *after* the menu has closed down. From experience, it's useful to trigger a "look" command to make sure the user is aware of the change of state; but any command can be used. If set to `None`, no command will be triggered after exiting the menu. - `persistent` (bool) - if `True`, the menu will survive a reload (so the user will not be kicked out by the reload - make sure they can exit on their own!) - `startnode_input` (str or (str, dict) tuple): Pass an input text or a input text + kwargs to the start node as if it was entered on a fictional previous node. This can be very useful in order to start a menu differently depending on the Command's arguments in which it was initialized. - `session` (Session): Useful when calling the menu from an [Account](./Accounts.md) in `MULTISESSION_MODE` higher than 2, to make sure only the right Session sees the menu output. - `debug` (bool): If set, the `menudebug` command will be made available in the menu. Use it to list the current state of the menu and use `menudebug ` to inspect a specific state variable from the list. - All other keyword arguments will be available as initial data for the nodes. They will be available in all nodes as properties on `caller.ndb._evmenu` (see below). These will also survive a `reload` if the menu is `persistent`. You don't need to store the EvMenu instance anywhere - the very act of initializing it will store it as `caller.ndb._evmenu` on the `caller`. This object will be deleted automatically when the menu is exited and you can also use it to store your own temporary variables for access throughout the menu. Temporary variables you store on a persistent `_evmenu` as it runs will *not* survive a `@reload`, only those you set as part of the original `EvMenu` call. ## The Menu nodes The EvMenu nodes consist of functions on one of these forms. ```python def menunodename1(caller): # code return text, options def menunodename2(caller, raw_string): # code return text, options def menunodename3(caller, raw_string, **kwargs): # code return text, options ``` > While all of the above forms are okay, it's recommended to stick to the third and last form since it gives the most flexibility. The previous forms are mainly there for backwards compatibility with existing menus from a time when EvMenu was less able and may become deprecated at some time in the future. ### Input arguments to the node - `caller` (Object or Account): The object using the menu - usually a Character but could also be a Session or Account depending on where the menu is used. - `raw_string` (str): If this is given, it will be set to the exact text the user entered on the *previous* node (that is, the command entered to get to this node). On the starting-node of the menu, this will be an empty string, unless `startnode_input` was set. - `kwargs` (dict): These extra keyword arguments are extra optional arguments passed to the node when the user makes a choice on the *previous* node. This may include things like status flags and details about which exact option was chosen (which can be impossible to determine from `raw_string` alone). Just what is passed in `kwargs` is up to you when you create the previous node. ### Return values from the node Each node function must return two variables, `text` and `options`. #### text The `text` variable is either a string or a tuple. This is the simplest form: ```python text = "Node text" ``` This is what will be displayed as text in the menu node when entering it. You can modify this dynamically in the node if you want. Returning a `None` node text text is allowed - this leads to a node with no text and only options. ```python text = ("Node text", "help text to show with h|elp") ``` In this form, we also add an optional help text. If `auto_help=True` when initializing the EvMenu, the user will be able to use `h` or `help` to see this text when viewing this node. If the user were to provide a custom option overriding `h` or `help`, that will be shown instead. If `auto_help=True` and no help text is provided, using `h|elp` will give a generic error message. ```python text = ("Node text", {"help topic 1": "Help 1", ("help topic 2", "alias1", ...): "Help 2", ...}) ``` This is 'tooltip' or 'multi-help category' mode. This also requires `auto_help=True` when initializing the EvMenu. By providing a `dict` as the second element of the `text` tuple, the user will be able to help about any of these topics. Use a tuple as key to add multiple aliases to the same help entry. This allows the user to get more detailed help text without leaving the given node. Note that in 'tooltip' mode, the normal `h|elp` command won't work. The `h|elp` entry must be added manually in the dict. As an example, this would reproduce the normal help functionality: ```python text = ("Node text", {("help", "h"): "Help entry...", ...}) ``` #### options The `options` list describe all the choices available to the user when viewing this node. If `options` is returned as `None`, it means that this node is an *Exit node* - any text is displayed and then the menu immediately exits, running the `exit_cmd` if given. Otherwise, `options` should be a list (or tuple) of dictionaries, one for each option. If only one option is available, a single dictionary can also be returned. This is how it could look: ```python def node_test(caller, raw_string, **kwargs): text = "A goblin attacks you!" options = ( {"key": ("Attack", "a", "att"), "desc": "Strike the enemy with all your might", "goto": "node_attack"}, {"key": ("Defend", "d", "def"), "desc": "Hold back and defend yourself", "goto": (_defend, {"str": 10, "enemyname": "Goblin"})}) return text, options ``` This will produce a menu node looking like this: ``` A goblin attacks you! ________________________________ Attack: Strike the enemy with all your might Defend: Hold back and defend yourself ``` ##### option-key 'key' The option's `key` is what the user should enter in order to choose that option. If given as a tuple, the first string of that tuple will be what is shown on-screen while the rest are aliases for picking that option. In the above example, the user could enter "Attack" (or "attack", it's not case-sensitive), "a" or "att" in order to attack the goblin. Aliasing is useful for adding custom coloring to the choice. The first element of the aliasing tuple should then be the colored version, followed by a version without color - since otherwise the user would have to enter the color codes to select that choice. Note that the `key` is *optional*. If no key is given, it will instead automatically be replaced with a running number starting from `1`. If removing the `key` part of each option, the resulting menu node would look like this instead: ``` A goblin attacks you! ________________________________ 1: Strike the enemy with all your might 2: Hold back and defend yourself ``` Whether you want to use a key or rely on numbers is mostly a matter of style and the type of menu. EvMenu accepts one important special `key` given only as `"_default"`. This key is used when a user enters something that does not match any other fixed keys. It is particularly useful for getting user input: ```python def node_readuser(caller, raw_string, **kwargs): text = "Please enter your name" options = {"key": "_default", "goto": "node_parse_input"} return text, options ``` A `"_default"` option does not show up in the menu, so the above will just be a node saying `"Please enter your name"`. The name they entered will appear as `raw_string` in the next node. #### option-key 'desc' This simply contains the description as to what happens when selecting the menu option. For `"_default"` options or if the `key` is already long or descriptive, it is not strictly needed. But usually it's better to keep the `key` short and put more detail in `desc`. #### option-key 'goto' This is the operational part of the option and fires only when the user chooses said option. Here are three ways to write it ```python def _action_two(caller, raw_string, **kwargs): # do things ... return "calculated_node_to_go_to" def _action_three(caller, raw_string, **kwargs): # do things ... return "node_four", {"mode": 4} def node_select(caller, raw_string, **kwargs): text = ("select one", "help - they all do different things ...") options = ({"desc": "Option one", "goto": "node_one"}, {"desc": "Option two", "goto": _action_two}, {"desc": "Option three", "goto": (_action_three, {"key": 1, "key2": 2})} ) return text, options ``` As seen above, `goto` could just be pointing to a single `nodename` string - the name of the node to go to. When given like this, EvMenu will look for a node named like this and call its associated function as ```python nodename(caller, raw_string, **kwargs) ``` Here, `raw_string` is always the input the user entered to make that choice and `kwargs` are the same as those `kwargs` that already entered the *current* node (they are passed on). Alternatively the `goto` could point to a "goto-callable". Such callables are usually defined in the same module as the menu nodes and given names starting with `_` (to avoid being parsed as nodes themselves). These callables will be called the same as a node function - `callable(caller, raw_string, **kwargs)`, where `raw_string` is what the user entered on this node and `**kwargs` is forwarded from the node's own input. The `goto` option key could also point to a tuple `(callable, kwargs)` - this allows for customizing the kwargs passed into the goto-callable, for example you could use the same callable but change the kwargs passed into it depending on which option was actually chosen. The "goto callable" must either return a string `"nodename"` or a tuple `("nodename", mykwargs)`. This will lead to the next node being called as either `nodename(caller, raw_string, **kwargs)` or `nodename(caller, raw_string, **mykwargs)` - so this allows changing (or replacing) the options going into the next node depending on what option was chosen. There is one important case - if the goto-callable returns `None` for a `nodename`, *the current node will run again*, possibly with different kwargs. This makes it very easy to re-use a node over and over, for example allowing different options to update some text form being passed and manipulated for every iteration. ### Temporary storage When the menu starts, the EvMenu instance is stored on the caller as `caller.ndb._evmenu`. Through this object you can in principle reach the menu's internal state if you know what you are doing. This is also a good place to store temporary, more global variables that may be cumbersome to keep passing from node to node via the `**kwargs`. The `_evmnenu` will be deleted automatically when the menu closes, meaning you don't need to worry about cleaning anything up. If you want *permanent* state storage, it's instead better to use an Attribute on `caller`. Remember that this will remain after the menu closes though, so you need to handle any needed cleanup yourself. ### Customizing Menu formatting The `EvMenu` display of nodes, options etc are controlled by a series of formatting methods on the `EvMenu` class. To customize these, simply create a new child class of `EvMenu` and override as needed. Here is an example: ```python from evennia.utils.evmenu import EvMenu class MyEvMenu(EvMenu): def nodetext_formatter(self, nodetext): """ Format the node text itself. Args: nodetext (str): The full node text (the text describing the node). Returns: nodetext (str): The formatted node text. """ def helptext_formatter(self, helptext): """ Format the node's help text Args: helptext (str): The unformatted help text for the node. Returns: helptext (str): The formatted help text. """ def options_formatter(self, optionlist): """ Formats the option block. Args: optionlist (list): List of (key, description) tuples for every option related to this node. caller (Object, Account or None, optional): The caller of the node. Returns: options (str): The formatted option display. """ def node_formatter(self, nodetext, optionstext): """ Formats the entirety of the node. Args: nodetext (str): The node text as returned by `self.nodetext_formatter`. optionstext (str): The options display as returned by `self.options_formatter`. caller (Object, Account or None, optional): The caller of the node. Returns: node (str): The formatted node to display. """ ``` See `evennia/utils/evmenu.py` for the details of their default implementations. ## EvMenu templating language In evmenu.py are two helper functions `parse_menu_template` and `template2menu` that is used to parse a _menu template_ string into an EvMenu: evmenu.template2menu(caller, menu_template, goto_callables) One can also do it in two steps, by generate a menutree and using that to call EvMenu normally: menutree = evmenu.parse_menu_template(caller, menu_template, goto_callables) EvMenu(caller, menutree) With this latter solution, one could mix and match normally created menu nodes with those generated by the template engine. The `goto_callables` is a mapping `{"funcname": callable, ...}`, where each callable must be a module-global function on the form `funcname(caller, raw_string, **kwargs)` (like any goto-callable). The `menu_template` is a multi-line string on the following form: ```python menu_template = """ ## node node1 Text for node ## options key1: desc1 -> node2 key2: desc2 -> node3 key3: desc3 -> node4 """ ``` Each menu node is defined by a `## node ` containing the text of the node, followed by `## options` Also `## NODE` and `## OPTIONS` work. No python code logics is allowed in the template, this code is not evaluated but parsed. More advanced dynamic usage requires a full node-function. Except for defining the node/options, `#` act as comments - everything following will be ignored by the template parser. ### Template Options The option syntax is : [desc ->] nodename or function-call The 'desc' part is optional, and if that is not given, the `->` can be skipped too: key: nodename The key can both be strings and numbers. Separate the aliases with `;`. key: node1 1: node2 key;k: node3 foobar;foo;bar;f;b: node4 Starting the key with the special letter `>` indicates that what follows is a glob/regex matcher. >: node1 - matches empty input > foo*: node1 - everything starting with foo > *foo: node3 - everything ending with foo > [0-9]+?: node4 - regex (all numbers) > *: node5 - catches everything else (put as last option) Here's how to call a goto-function from an option: key: desc -> myfunc(foo=bar) For this to work `template2menu` or `parse_menu_template` must be given a dict that includes `{"myfunc": _actual_myfunc_callable}`. All callables to be available in the template must be mapped this way. Goto callables act like normal EvMenu goto-callables and should have a callsign of `_actual_myfunc_callable(caller, raw_string, **kwargs)` and return the next node (passing dynamic kwargs into the next node does not work with the template - use the full EvMenu if you want advanced dynamic data passing). Only no or named keywords are allowed in these callables. So myfunc() # OK myfunc(foo=bar) # OK myfunc(foo) # error! This is because these properties are passed as `**kwargs` into the goto callable. ### Templating example ```python from random import random from evennia.utils import evmenu def _gamble(caller, raw_string, **kwargs): caller.msg("You roll the dice ...") if random() < 0.5: return "loose" else: return "win" template_string = """ ## node start Death patiently holds out a set of bone dice to you. "ROLL" he says. ## options 1: Roll the dice -> gamble() 2: Try to talk yourself out of rolling -> start ## node win The dice clatter over the stones. "LOOKS LIKE YOU WIN THIS TIME" says Death. # (this ends the menu since there are no options) ## node loose The dice clatter over the stones. "YOUR LUCK RAN OUT" says Death. "YOU ARE COMING WITH ME." # (this ends the menu, but what happens next - who knows!) """ # map the in-template callable-name to real python code goto_callables = {"gamble": _gamble} # this starts the evmenu for the caller evmenu.template2menu(caller, template_string, goto_callables) ``` ## Asking for one-line input This describes two ways for asking for simple questions from the user. Using Python's `input` will *not* work in Evennia. `input` will *block* the entire server for *everyone* until that one player has entered their text, which is not what you want. ### The `yield` way In the `func` method of your Commands (only) you can use Python's built-in `yield` command to request input in a similar way to `input`. It looks like this: ```python result = yield("Please enter your answer:") ``` This will send "Please enter your answer" to the Command's `self.caller` and then pause at that point. All other players at the server will be unaffected. Once caller enteres a reply, the code execution will continue and you can do stuff with the `result`. Here is an example: ```python from evennia import Command class CmdTestInput(Command): key = "test" def func(self): result = yield("Please enter something:") self.caller.msg(f"You entered {result}.") result2 = yield("Now enter something else:") self.caller.msg(f"You now entered {result2}.") ``` Using `yield` is simple and intuitive, but it will only access input from `self.caller` and you cannot abort or time out the pause until the player has responded. Under the hood, it is actually just a wrapper calling `get_input` described in the following section. > Important Note: In Python you *cannot mix `yield` and `return ` in the same method*. It has > to do with `yield` turning the method into a > [generator](https://www.learnpython.org/en/Generators). A `return` without an argument works, you > can just not do `return `. This is usually not something you need to do in `func()` anyway, > but worth keeping in mind. ### The `get_input` way The evmenu module offers a helper function named `get_input`. This is wrapped by the `yield` statement which is often easier and more intuitive to use. But `get_input` offers more flexibility and power if you need it. While in the same module as `EvMenu`, `get_input` is technically unrelated to it. The `get_input` allows you to ask and receive simple one-line input from the user without launching the full power of a menu to do so. To use, call `get_input` like this: ```python get_input(caller, prompt, callback) ``` Here `caller` is the entity that should receive the prompt for input given as `prompt`. The `callback` is a callable `function(caller, prompt, user_input)` that you define to handle the answer from the user. When run, the caller will see `prompt` appear on their screens and *any* text they enter will be sent into the callback for whatever processing you want. Below is a fully explained callback and example call: ```python from evennia import Command from evennia.utils.evmenu import get_input def callback(caller, prompt, user_input): """ This is a callback you define yourself. Args: caller (Account or Object): The one being asked for input prompt (str): A copy of the current prompt user_input (str): The input from the account. Returns: repeat (bool): If not set or False, exit the input prompt and clean up. If returning anything True, stay in the prompt, which means this callback will be called again with the next user input. """ caller.msg(f"When asked '{prompt}', you answered '{user_input}'.") get_input(caller, "Write something! ", callback) ``` This will show as ``` Write something! > Hello When asked 'Write something!', you answered 'Hello'. ``` Normally, the `get_input` function quits after any input, but as seen in the example docs, you could return True from the callback to repeat the prompt until you pass whatever check you want. > Note: You *cannot* link consecutive questions by putting a new `get_input` call inside the > callback If you want that you should use an EvMenu instead (see the [Repeating the same > node](./EvMenu.md#example-repeating-the-same-node) example above). Otherwise you can either peek at the > implementation of `get_input` and implement your own mechanism (it's just using cmdset nesting) or > you can look at [this extension suggested on the mailing > list](https://groups.google.com/forum/#!category-topic/evennia/evennia-questions/16pi0SfMO5U). #### Example: Yes/No prompt Below is an example of a Yes/No prompt using the `get_input` function: ```python def yesno(caller, prompt, result): if result.lower() in ("y", "yes", "n", "no"): # do stuff to handle the yes/no answer # ... # if we return None/False the prompt state # will quit after this else: # the answer is not on the right yes/no form caller.msg("Please answer Yes or No. \n{prompt}") @ # returning True will make sure the prompt state is not exited return True # ask the question get_input(caller, "Is Evennia great (Yes/No)?", yesno) ``` ## The `@list_node` decorator The `evennia.utils.evmenu.list_node` is an advanced decorator for use with `EvMenu` node functions. It is used to quickly create menus for manipulating large numbers of items. ``` text here ______________________________________________ 1. option1 7. option7 13. option13 2. option2 8. option8 14. option14 3. option3 9. option9 [p]revius page 4. option4 10. option10 page 2 5. option5 11. option11 [n]ext page 6. option6 12. option12 ``` The menu will automatically create an multi-page option listing that one can flip through. One can inpect each entry and then select them with prev/next. This is how it is used: ```python from evennia.utils.evmenu import list_node ... _options(caller): return ['option1', 'option2', ... 'option100'] _select(caller, menuchoice, available_choices): # analyze choice return "next_node" @list_node(options, select=_select, pagesize=10) def node_mylist(caller, raw_string, **kwargs): ... return text, options ``` The `options` argument to `list_node` is either a list, a generator or a callable returning a list of strings for each option that should be displayed in the node. The `select` is a callable in the example above but could also be the name of a menu node. If a callable, the `menuchoice` argument holds the selection done and `available_choices` holds all the options available. The callable should return the menu to go to depending on the selection (or `None` to rerun the same node). If the name of a menu node, the selection will be passed as `selection` kwarg to that node. The decorated node itself should return `text` to display in the node. It must return at least an empty dictionary for its options. It returning options, those will supplement the options auto-created by the `list_node` decorator. ## Example Menus Here is a diagram to help visualize the flow of data from node to node, including goto-callables in-between: ``` ┌─ │ def nodeA(caller, raw_string, **kwargs): │ text = "Choose how to operate on 2 and 3." │ options = ( │ { │ "key": "A", │ "desc": "Multiply 2 with 3", │ "goto": (_callback, {"type": "mult", "a": 2, "b": 3}) │ }, ───────────────────┬──────────── │ { │ │ "key": "B", └───────────────┐ │ "desc": "Add 2 and 3", │ Node A│ "goto": (_callback, {"type": "add", "a": 2, "b": 3}) │ │ }, ─────────────────┬───────────── │ │ { │ │ │ "key": "C", │ │ │ "desc": "Show the value 5", │ │ │ "goto": ("node_B", {"c": 5}) │ │ │ } ───────┐ │ │ │ ) └──────────┼─────────────────┼───┐ │ return text, options │ │ │ └─ ┌──────────┘ │ │ │ │ │ │ ┌──────────────────────────┘ │ ┌─ ▼ ▼ │ │ def _callback(caller, raw_string, **kwargs): │ │ if kwargs["type"] == "mult": │ │ return "node_B", {"c": kwargs["a"] * kwargs["b"]} │ Goto- │ ───────────────┬──────────────── │ callable│ │ │ │ └───────────────────┐ │ │ │ │ │ elif kwargs["type"] == "add": │ │ │ return "node_B", {"c": kwargs["a"] + kwargs["b"]} │ │ └─ ────────┬─────────────────────── │ │ │ │ │ │ ┌────────────────────────┼──────────┘ │ │ │ │ │ ┌──────────────────────┘ ┌─ ▼ ▼ ▼ │ def nodeB(caller, raw_string, **kwargs): Node B│ text = "Result of operation: " + kwargs["c"] │ return text, {} └─ ┌─ Menu │ EvMenu(caller, {"node_A": nodeA, "node_B": nodeB}, startnode="node_A") Start│ └─ ``` Above we create a very simple/stupid menu (in the `EvMenu` call at the end) where we map the node identifier `"node_A"` to the Python function `nodeA` and `"node_B"` to the function `nodeB`. We start the menu in `"node_A"` where we get three options A, B and C. Options A and B will route via a a goto-callable `_callback` that either multiples or adds the numbers 2 and 3 together before continuing to `"node_B"`. Option C routes directly to `"node_B"`, passing the number 5. In every step, we pass a dict which becomes the ingoing `**kwargs` in the next step. If we didn't pass anything (it's optional), the next step's `**kwargs` would just be empty. More examples: - **[Simple branching menu](./EvMenu.md#example-simple-branching-menu)** - choose from options - **[Dynamic goto](./EvMenu.md#example-dynamic-goto)** - jumping to different nodes based on response - **[Set caller properties](./EvMenu.md#example-set-caller-properties)** - a menu that changes things - **[Getting arbitrary input](./EvMenu.md#example-get-arbitrary-input)** - entering text - **[Storing data between nodes](./EvMenu.md#example-storing-data-between-nodes)** - keeping states and information while in the menu - **[Repeating the same node](./EvMenu.md#example-repeating-the-same-node)** - validating within the node before moving to the next - **[Yes/No prompt](#example-yesno-prompt)** - entering text with limited possible responses (this is *not* using EvMenu but the conceptually similar yet technically unrelated `get_input` helper function accessed as `evennia.utils.evmenu.get_input`). ### Example: Simple branching menu Below is an example of a simple branching menu node leading to different other nodes depending on choice: ```python # in mygame/world/mychargen.py def define_character(caller): text = \ """ What aspect of your character do you want to change next? """ options = ({"desc": "Change the name", "goto": "set_name"}, {"desc": "Change the description", "goto": "set_description"}) return text, options EvMenu(caller, "world.mychargen", startnode="define_character") ``` This will result in the following node display: ``` What aspect of your character do you want to change next? _________________________ 1: Change the name 2: Change the description ``` Note that since we didn't specify the "name" key, EvMenu will let the user enter numbers instead. In the following examples we will not include the `EvMenu` call but just show nodes running inside the menu. Also, since `EvMenu` also takes a dictionary to describe the menu, we could have called it like this instead in the example: ```python EvMenu(caller, {"define_character": define_character}, startnode="define_character") ``` ### Example: Dynamic goto ```python def _is_in_mage_guild(caller, raw_string, **kwargs): if caller.tags.get('mage', category="guild_member"): return "mage_guild_welcome" else: return "mage_guild_blocked" def enter_guild: text = 'You say to the mage guard:' options ({'desc': 'I need to get in there.', 'goto': _is_in_mage_guild}, {'desc': 'Never mind', 'goto': 'end_conversation'}) return text, options ``` This simple callable goto will analyse what happens depending on who the `caller` is. The `enter_guild` node will give you a choice of what to say to the guard. If you try to enter, you will end up in different nodes depending on (in this example) if you have the right [Tag](./Tags.md) set on yourself or not. Note that since we don't include any 'key's in the option dictionary, you will just get to pick between numbers. ### Example: Set caller properties Here is an example of passing arguments into the `goto` callable and use that to influence which node it should go to next: ```python def _set_attribute(caller, raw_string, **kwargs): "Get which attribute to modify and set it" attrname, value = kwargs.get("attr", (None, None)) next_node = kwargs.get("next_node") caller.attributes.add(attrname, attrvalue) return next_node def node_background(caller): text = \ f""" {caller.key} experienced a traumatic event in their childhood. What was it? """ options = ({"key": "death", "desc": "A violent death in the family", "goto": (_set_attribute, {"attr": ("experienced_violence", True), "next_node": "node_violent_background"})}, {"key": "betrayal", "desc": "The betrayal of a trusted grown-up", "goto": (_set_attribute, {"attr": ("experienced_betrayal", True), "next_node": "node_betrayal_background"})}) return text, options ``` This will give the following output: ``` Kovash the magnificent experienced a traumatic event in their childhood. What was it? ____________________________________________________ death: A violent death in the family betrayal: The betrayal of a trusted grown-up ``` Note above how we use the `_set_attribute` helper function to set the attribute depending on the User's choice. In thie case the helper function doesn't know anything about what node called it - we even tell it which nodename it should return, so the choices leads to different paths in the menu. We could also imagine the helper function analyzing what other choices ### Example: Get arbitrary input An example of the menu asking the user for input - any input. ```python def _set_name(caller, raw_string, **kwargs): inp = raw_string.strip() prev_entry = kwargs.get("prev_entry") if not inp: # a blank input either means OK or Abort if prev_entry: caller.key = prev_entry caller.msg(f"Set name to {prev_entry}.") return "node_background" else: caller.msg("Aborted.") return "node_exit" else: # re-run old node, but pass in the name given return None, {"prev_entry": inp} def enter_name(caller, raw_string, **kwargs): # check if we already entered a name before prev_entry = kwargs.get("prev_entry") if prev_entry: text = "Current name: {}.\nEnter another name or to accept." else: text = "Enter your character's name or to abort." options = {"key": "_default", "goto": (_set_name, {"prev_entry": prev_entry})} return text, options ``` This will display as ``` Enter your character's name or to abort. > Gandalf Current name: Gandalf Enter another name or to accept. > Set name to Gandalf. ``` Here we re-use the same node twice for reading the input data from the user. Whatever we enter will be caught by the `_default` option and passed into the helper function. We also pass along whatever name we have entered before. This allows us to react correctly on an "empty" input - continue to the node named `"node_background"` if we accept the input or go to an exit node if we presses Return without entering anything. By returning `None` from the helper function we automatically re-run the previous node, but updating its ingoing kwargs to tell it to display a different text. ### Example: Storing data between nodes A convenient way to store data is to store it on the `caller.ndb._evmenu` which you can reach from every node. The advantage of doing this is that the `_evmenu` NAttribute will be deleted automatically when you exit the menu. ```python def _set_name(caller, raw_string, **kwargs): caller.ndb._evmenu.charactersheet = {} caller.ndb._evmenu.charactersheet['name'] = raw_string caller.msg(f"You set your name to {raw_string}") return "background" def node_set_name(caller): text = 'Enter your name:' options = {'key': '_default', 'goto': _set_name} return text, options ... def node_view_sheet(caller): text = f"Character sheet:\n {self.ndb._evmenu.charactersheet}" options = ({"key": "Accept", "goto": "finish_chargen"}, {"key": "Decline", "goto": "start_over"}) return text, options ``` Instead of passing the character sheet along from node to node through the `kwargs` we instead set it up temporarily on `caller.ndb._evmenu.charactersheet`. This makes it easy to reach from all nodes. At the end we look at it and, if we accept the character the menu will likely save the result to permanent storage and exit. > One point to remember though is that storage on `caller.ndb._evmenu` is not persistent across > `@reloads`. If you are using a persistent menu (using `EvMenu(..., persistent=True)` you should use > `caller.db` to store in-menu data like this as well. You must then yourself make sure to clean it > when the user exits the menu. ### Example: Repeating the same node Sometimes you want to make a chain of menu nodes one after another, but you don't want the user to be able to continue to the next node until you have verified that what they input in the previous node is ok. A common example is a login menu: ```python def _check_username(caller, raw_string, **kwargs): # we assume lookup_username() exists if not lookup_username(raw_string): # re-run current node by returning `None` caller.msg("|rUsername not found. Try again.") return None else: # username ok - continue to next node return "node_password" def node_username(caller): text = "Please enter your user name." options = {"key": "_default", "goto": _check_username} return text, options def _check_password(caller, raw_string, **kwargs): nattempts = kwargs.get("nattempts", 0) if nattempts > 3: caller.msg("Too many failed attempts. Logging out") return "node_abort" elif not validate_password(raw_string): caller.msg("Password error. Try again.") return None, {"nattempts", nattempts + 1} else: # password accepted return "node_login" def node_password(caller, raw_string, **kwargs): text = "Enter your password." options = {"key": "_default", "goto": _check_password} return text, options ``` This will display something like ``` --------------------------- Please enter your username. --------------------------- > Fo ------------------------------ Username not found. Try again. ______________________________ abort: (back to start) ------------------------------ > Foo --------------------------- Please enter your password. --------------------------- > Bar -------------------------- Password error. Try again. -------------------------- ``` And so on. Here the goto-callables will return to the previous node if there is an error. In the case of password attempts, this will tick up the `nattempts` argument that will get passed on from iteration to iteration until too many attempts have been made. ### Defining nodes in a dictionary You can also define your nodes directly in a dictionary to feed into the `EvMenu` creator. ```python def mynode(caller): # a normal menu node function return text, options menu_data = {"node1": mynode, "node2": lambda caller: ( "This is the node text", ({"key": "lambda node 1", "desc": "go to node 1 (mynode)", "goto": "node1"}, {"key": "lambda node 2", "desc": "go to thirdnode", "goto": "node3"})), "node3": lambda caller, raw_string: ( # ... etc ) } # start menu, assuming 'caller' is available from earlier EvMenu(caller, menu_data, startnode="node1") ``` The keys of the dictionary become the node identifiers. You can use any callable on the right form to describe each node. If you use Python `lambda` expressions you can make nodes really on the fly. If you do, the lambda expression must accept one or two arguments and always return a tuple with two elements (the text of the node and its options), same as any menu node function. Creating menus like this is one way to present a menu that changes with the circumstances - you could for example remove or add nodes before launching the menu depending on some criteria. The drawback is that a `lambda` expression [is much more limited](https://docs.python.org/2/tutorial/controlflow.html#lambda-expressions) than a full function - for example you can't use other Python keywords like `if` inside the body of the `lambda`. Unless you are dealing with a relatively simple dynamic menu, defining menus with lambda's is probably more work than it's worth: You can create dynamic menus by instead making each node function more clever. See the [NPC shop tutorial](../Howtos/Tutorial-NPC-Merchants.md) for an example of this.