diff --git a/docs/1.0-dev/.buildinfo b/docs/1.0-dev/.buildinfo index 049458006d..1362d1ac51 100644 --- a/docs/1.0-dev/.buildinfo +++ b/docs/1.0-dev/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 9adbe4f91e54ddd9ee1242ebb9714806 +config: 20e6a00accd2c45a252d90d346328ff4 tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/1.0-dev/Components/Accounts.html b/docs/1.0-dev/Components/Accounts.html index 68b3e1bbeb..9273107a7d 100644 --- a/docs/1.0-dev/Components/Accounts.html +++ b/docs/1.0-dev/Components/Accounts.html @@ -18,7 +18,7 @@ - + -

Attributes allow you to to store arbitrary data on objects and make sure the data survives a server reboot. An Attribute can store pretty much any -Python data structure and data type, like numbers, strings, lists, dicts etc. You can also -store (references to) database objects like characters and rooms.

- -
-

Managing Attributes in Code

-

Attributes are usually handled in code. All Typeclassed entities -(Accounts, Objects, Scripts and -Channels) can (and usually do) have Attributes associated with them. There +

Attributes allow you to to store arbitrary data on objects and make sure the data survives a server reboot. An Attribute can store pretty much any Python data structure and data type, like numbers, strings, lists, dicts etc. You can also store (references to) database objects like characters and rooms.

+
+

Working with Attributes

+

Attributes are usually handled in code. All Typeclassed entities (Accounts, Objects, Scripts and Channels) can (and usually do) have Attributes associated with them. There are three ways to manage Attributes, all of which can be mixed.

-

Using .db

The simplest way to get/set Attributes is to use the .db shortcut. This allows for setting and getting Attributes that lack a category (having category None)

@@ -239,8 +223,7 @@ the category
will be None and can thus also be found via .db. None is considered a category of its own, so you won’t find None-category Attributes mixed with Attributes having categories.

-

Here are the methods of the AttributeHandler. See -the AttributeHandler API for more details.

+

Here are the methods of the AttributeHandler. See the AttributeHandler API for more details.

  • has(...) - this checks if the object has an Attribute with this key. This is equivalent to doing obj.db.attrname except you can also check for a specific `category.

  • @@ -330,16 +313,45 @@ The drawback is that without a database precense you can’t find the Attribute char.attributes.get("sleepy") # now returns True char.sleepy # now returns True, involves db access - -

    You can e.g. del char.strength to set the value back to the default (the value defined -in the AttributeProperty).

    +

    You can e.g. del char.strength to set the value back to the default (the value defined in the AttributeProperty).

    See the AttributeProperty API for more details on how to create it with special options, like giving access-restrictions.

+
+

Properties of Attributes

+

An Attribute object is stored in the database. It has the following properties:

+
    +
  • key - the name of the Attribute. When doing e.g. obj.db.attrname = value, this property is set +to attrname.

  • +
  • value - this is the value of the Attribute. This value can be anything which can be pickled - +objects, lists, numbers or what have you (see +this section for more info). In the +example +obj.db.attrname = value, the value is stored here.

  • +
  • category - this is an optional property that is set to None for most Attributes. Setting this +allows to use Attributes for different functionality. This is usually not needed unless you want +to use Attributes for very different functionality (Nicks is an example of using +Attributes in this way). To modify this property you need to use the Attribute Handler

  • +
  • strvalue - this is a separate value field that only accepts strings. This severely limits the +data possible to store, but allows for easier database lookups. This property is usually not used +except when re-using Attributes for some other purpose (Nicks use it). It is only +accessible via the Attribute Handler.

  • +
+

There are also two special properties:

+
    +
  • attrtype - this is used internally by Evennia to separate Nicks, from Attributes (Nicks +use Attributes behind the scenes).

  • +
  • model - this is a natural-key describing the model this Attribute is attached to. This is on +the form appname.modelclass, like objects.objectdb. It is used by the Attribute and +NickHandler to quickly sort matches in the database. Neither this nor attrtype should normally +need to be modified.

  • +
+

Non-database attributes are not stored in the database and have no equivalence +to category nor strvalue, attrtype or model.

-

Managing Attributes in-game

+

Managing Attributes in-game

Attributes are mainly used by code. But one can also allow the builder to use Attributes to ‘turn knobs’ in-game. For example a builder could want to manually tweak the “level” Attribute of an enemy NPC to lower its difficuly.

@@ -375,7 +387,7 @@ set mypobj/mystring = [1, 2, foo] # foo is invalid Python (no quotes)

For the last line you’ll get a warning and the value instead will be saved as a string "[1, 2, foo]".

-

Locking and checking Attributes

+

Locking and checking Attributes

While the set command is limited to builders, individual Attributes are usually not locked down. You may want to lock certain sensitive Attributes, in particular for games where you allow player building. You can add such limitations by adding a lock string @@ -418,6 +430,7 @@ To check the lockst

The same keywords are available to use with obj.attributes.set() and obj.attributes.remove(), those will check for the attredit lock type.

+

What types of data can I save in an Attribute?

The database doesn’t know anything about Python objects, so Evennia must serialize Attribute @@ -613,38 +626,6 @@ instead of _SaverLi explicitly save it back to the Attribute for it to save.

-
-

Properties of Attributes

-

An Attribute object is stored in the database. It has the following properties:

-
    -
  • key - the name of the Attribute. When doing e.g. obj.db.attrname = value, this property is set -to attrname.

  • -
  • value - this is the value of the Attribute. This value can be anything which can be pickled - -objects, lists, numbers or what have you (see -this section for more info). In the -example -obj.db.attrname = value, the value is stored here.

  • -
  • category - this is an optional property that is set to None for most Attributes. Setting this -allows to use Attributes for different functionality. This is usually not needed unless you want -to use Attributes for very different functionality (Nicks is an example of using -Attributes in this way). To modify this property you need to use the Attribute Handler

  • -
  • strvalue - this is a separate value field that only accepts strings. This severely limits the -data possible to store, but allows for easier database lookups. This property is usually not used -except when re-using Attributes for some other purpose (Nicks use it). It is only -accessible via the Attribute Handler.

  • -
-

There are also two special properties:

-
    -
  • attrtype - this is used internally by Evennia to separate Nicks, from Attributes (Nicks -use Attributes behind the scenes).

  • -
  • model - this is a natural-key describing the model this Attribute is attached to. This is on -the form appname.modelclass, like objects.objectdb. It is used by the Attribute and -NickHandler to quickly sort matches in the database. Neither this nor attrtype should normally -need to be modified.

  • -
-

Non-database attributes are not stored in the database and have no equivalence -to category nor strvalue, attrtype or model.

-

In-memory Attributes (NAttributes)

NAttributes (short of Non-database Attributes) mimic Attributes in most things except they diff --git a/docs/1.0-dev/Components/Batch-Code-Processor.html b/docs/1.0-dev/Components/Batch-Code-Processor.html index fffa28c034..650e54b46c 100644 --- a/docs/1.0-dev/Components/Batch-Code-Processor.html +++ b/docs/1.0-dev/Components/Batch-Code-Processor.html @@ -17,8 +17,8 @@ - - + +

-

Line editor usage

+

Line editor usage

The editor mimics the VIM editor as best as possible. The below is an excerpt of the return from the in-editor help command (:h).

 <txt>  - any non-command is appended to the end of the buffer.
@@ -260,29 +261,21 @@ the in-editor help command (
 
-

The EvEditor to edit code

-

The EvEditor is also used to edit some Python code in Evennia. The @py command supports an -/edit switch that will open the EvEditor in code mode. This mode isn’t significantly different -from the standard one, except it handles automatic indentation of blocks and a few options to -control this behavior.

+

The EvEditor to edit code

+

The EvEditor is also used to edit some Python code in Evennia. The py command supports an /edit switch that will open the EvEditor in code mode. This mode isn’t significantly different from the standard one, except it handles automatic indentation of blocks and a few options to control this behavior.

  • :< to remove a level of indentation for the future lines.

  • :+ to add a level of indentation for the future lines.

  • := to disable automatic indentation altogether.

-

Automatic indentation is there to make code editing more simple. Python needs correct indentation, -not as an aesthetic addition, but as a requirement to determine beginning and ending of blocks. The -EvEditor will try to guess the next level of indentation. If you type a block “if”, for instance, -the EvEditor will propose you an additional level of indentation at the next line. This feature -cannot be perfect, however, and sometimes, you will have to use the above options to handle -indentation.

+

Automatic indentation is there to make code editing more simple. Python needs correct indentation, not as an aesthetic addition, but as a requirement to determine beginning and ending of blocks. The EvEditor will try to guess the next level of indentation. If you type a block “if”, for instance, the EvEditor will propose you an additional level of indentation at the next line. This feature cannot be perfect, however, and sometimes, you will have to use the above options to handle indentation.

:= can be used to turn automatic indentation off completely. This can be very useful when trying to paste several lines of code that are already correctly indented, for instance.

-

To see the EvEditor in code mode, you can use the @py/edit command. Type in your code (on one or -several lines). You can then use the :w option (save without quitting) and the code you have +

To see the EvEditor in code mode, you can use the @py/edit command. Type in your code (on one or several lines). You can then use the :w option (save without quitting) and the code you have typed will be executed. The :! will do the same thing. Executing code while not closing the editor can be useful if you want to test the code you have typed but add new lines after your test.

+ diff --git a/docs/1.0-dev/Components/EvMenu.html b/docs/1.0-dev/Components/EvMenu.html index dddf082cd8..83c2699be5 100644 --- a/docs/1.0-dev/Components/EvMenu.html +++ b/docs/1.0-dev/Components/EvMenu.html @@ -63,10 +63,6 @@

Table of Contents

+
  • EvMenu templating language
  • -
  • Examples:
  • @@ -151,37 +146,25 @@

    EvMenu

    -

    EvMenu is used for generate branching multi-choice menus. Each menu ‘node’ can +

    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.

    -
    -

    Introduction

    The EvMenu utility class is located in evennia/utils/evmenu.py. 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:

    -
    -

    Examples

    -

    This section gives some examples of how menus work in-game. A menu is a state -(it’s actually a custom cmdset) where menu-specific commands are made available -to you. An EvMenu is usually started from inside a command, but could also -just be put in a file and run with py.

    -

    This is how the example menu will look in-game:

    -
    Is your answer yes or no?
    -_________________________________________
    -[Y]es! - Answer yes.
    -[N]o! - Answer no.
    -[A]bort - Answer neither, and abort.
    -
    -
    -

    If you pick (for example) Y(es), you will see

    -
    You chose yes!
    -
    -Thanks for your answer. Goodbye!
    -
    -
    -

    After which the menu will end (in this example at least - it could also continue -on to other questions and choices or even repeat the same node over and over!)

    -

    Here’s the full EvMenu code for this example:

    +

    This is how the example menu at the top of this page will look in code:

    from evennia.utils import evmenu
     
     def _handle_answer(caller, raw_input, **kwargs):
    @@ -317,8 +300,6 @@ uses the template-string and a mapping of callables (we must add
     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 @@ -354,39 +335,14 @@ most common way to do so - from inside a -

  • caller (Object or Account): is a reference to the object using the menu. This object will get a -new CmdSet 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 +

  • caller (Object or Account): is a reference to the object using the menu. This object will get a new CmdSet 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. Nevertheless, 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.

  • +
  • 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. Nevertheless, 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 @@ -397,9 +353,7 @@ start a menu differently depending on the Command’s arguments in which it was

  • 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 <variable> 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.

  • +
  • 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 @@ -601,9 +555,8 @@ goto-callable. This functionality comes from a time before goto could be a calla

    -
    -

    Temporary storage

    +

    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 @@ -614,7 +567,7 @@ that this will remain after the menu closes though, so you need to handle any ne yourself.

    -

    Customizing Menu formatting

    +

    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:

    @@ -678,6 +631,7 @@ needed. Here is an example:

    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 @@ -827,8 +781,171 @@ myfunc(foo) # error!

    -
    -

    Examples:

    +
    +

    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:

    +
    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:

    +
    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 <value> in the same method. It has +to do with yield turning the method into a +generator. A return without an argument works, you +can just not do return <value>. 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:

    +
    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:

    +
    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 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.

    +
    +
    +

    Example: Yes/No prompt

    +

    Below is an example of a Yes/No prompt using the get_input function:

    +
    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:

    +
    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

    • Simple branching menu - choose from options

    • Dynamic goto - jumping to different nodes based on response

    • @@ -844,8 +961,7 @@ helper function accessed as

      Example: Simple branching menu

      -

      Below is an example of a simple branching menu node leading to different other nodes depending on -choice:

      +

      Below is an example of a simple branching menu node leading to different other nodes depending on choice:

      # in mygame/world/mychargen.py
       
       def define_character(caller):
      @@ -1064,9 +1180,7 @@ 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:

      +

      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:

      
       def _check_username(caller, raw_string, **kwargs):
           # we assume lookup_username() exists
      @@ -1177,178 +1291,6 @@ probably more work than it’s worth: You can create dynamic menus by instead ma
       function more clever. See the NPC shop tutorial for an example of this.

    -
    -

    Ask for simple 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:

    -
    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:

    -
    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 <value> in the same method. It has -to do with yield turning the method into a -generator. A return without an argument works, you -can just not do return <value>. 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:

    -
    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:

    -
    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 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.

    -
    -
    -

    Example: Yes/No prompt

    -

    Below is an example of a Yes/No prompt using the get_input function:

    -
    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:

    -
    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.

    -
    -
    -

    Assorted notes

    -

    The EvMenu is implemented using Commands. When you start a new EvMenu, the user of the -menu will be assigned a CmdSet with the commands they need to navigate the menu. -This means that if you were to, from inside the menu, assign a new command set to the caller, you -may override the Menu Cmdset and kill the menu. If you want to assign cmdsets to the caller as part -of the menu, you should store the cmdset on caller.ndb._evmenu and wait to actually assign it -until the exit node.

    -
    diff --git a/docs/1.0-dev/Components/EvMore.html b/docs/1.0-dev/Components/EvMore.html index 341e7a0620..c5f71a4b3e 100644 --- a/docs/1.0-dev/Components/EvMore.html +++ b/docs/1.0-dev/Components/EvMore.html @@ -60,14 +60,6 @@ -

    Table of Contents

    - -

    Previous topic

    EvMenu

    @@ -110,8 +102,6 @@ window. The evennia.utils.evmore.EvMore class gives the user the in-game ability to only view one page of text at a time. It is usually used via its access function, evmore.msg.

    The name comes from the famous unix pager utility more which performs just this function.

    -
    -

    Using EvMore

    To use the pager, just pass the long text through it:

    from evennia.utils import evmore
     
    @@ -136,7 +126,6 @@ commands to jump to previous pages, to the top or bottom of the document as well
     paging.

    The pager takes several more keyword arguments for controlling the message output. See the evmore-API for more info.

    -
    diff --git a/docs/1.0-dev/Components/EvTable.html b/docs/1.0-dev/Components/EvTable.html index c9ae6ae737..937589aa8c 100644 --- a/docs/1.0-dev/Components/EvTable.html +++ b/docs/1.0-dev/Components/EvTable.html @@ -17,7 +17,7 @@ - + -

    Note here that the access_type can be left to a dummy value since this method does not actually do -a Lock lookup.

    +

    Note here that the access_type can be left to a dummy value since this method does not actually do a Lock lookup.

    -

    Default locks

    +

    Default locks

    Evennia sets up a few basic locks on all new objects and accounts (if we didn’t, noone would have any access to anything from the start). This is all defined in the root Typeclasses of the respective entity, in the hook method basetype_setup() (which you usually don’t want to @@ -378,6 +367,7 @@ child object to change the default. Also creation commands like control lock_type so as to allow you, its creator, to control and delete the object.

    +

    More Lock definition examples

    examine: attr(eyesight, excellent) or perm(Builders)
    @@ -414,9 +404,8 @@ would be be added as a lock string to the create command sets up new objects. In sequence, this permission string sets the
     owner of this object be the creator (the one  running create). Builders may examine the object
     whereas only Admins and the creator may delete it. Everyone can pick it up.

    -
    -

    A complete example of setting locks on an object

    +

    A complete example of setting locks on an object

    Assume we have two objects - one is ourselves (not superuser) and the other is an Object called box.

     > create/drop box
    @@ -475,20 +464,11 @@ strength above 50 however and you’ll pick it up no problem. Done! A very heavy
     
    +

    On Django’s permission system

    -

    Django also implements a comprehensive permission/security system of its own. The reason we don’t -use that is because it is app-centric (app in the Django sense). Its permission strings are of the -form appname.permstring and it automatically adds three of them for each database model in the app

    -
      -
    • for the app evennia/object this would be for example ‘object.create’, ‘object.admin’ and -‘object.edit’. This makes a lot of sense for a web application, not so much for a MUD, especially -when we try to hide away as much of the underlying architecture as possible.

    • -
    -

    The django permissions are not completely gone however. We use it for validating passwords during -login. It is also used exclusively for managing Evennia’s web-based admin site, which is a graphical -front-end for the database of Evennia. You edit and assign such permissions directly from the web -interface. It’s stand-alone from the permissions described above.

    +

    Django also implements a comprehensive permission/security system of its own. The reason we don’t use that is because it is app-centric (app in the Django sense). Its permission strings are of the form appname.permstring and it automatically adds three of them for each database model in the app - for the app evennia/object this would be for example ‘object.create’, ‘object.admin’ and ‘object.edit’. This makes a lot of sense for a web application, not so much for a MUD, especially when we try to hide away as much of the underlying architecture as possible.

    +

    The django permissions are not completely gone however. We use it for validating passwords during login. It is also used exclusively for managing Evennia’s web-based admin site, which is a graphical front-end for the database of Evennia. You edit and assign such permissions directly from the web interface. It’s stand-alone from the permissions described above.

    @@ -508,10 +488,10 @@ interface. It’s stand-alone from the permissions described above.

    modules |
  • - next |
  • - previous |
  • diff --git a/docs/1.0-dev/Components/MonitorHandler.html b/docs/1.0-dev/Components/MonitorHandler.html index 9450cc8c14..6835d04ec6 100644 --- a/docs/1.0-dev/Components/MonitorHandler.html +++ b/docs/1.0-dev/Components/MonitorHandler.html @@ -18,7 +18,7 @@ - +
    -
    -

    Superusers

    -

    There is normally only one superuser account and that is the one first created -when starting Evennia (User #1). This is sometimes known as the “Owner” or “God” -user. A superuser has more than full access - it completely bypasses all -locks and will always pass the PermissionHandler.check() check. This allows -for the superuser to always have access to everything in an emergency. But it -could also hide any eventual errors you might have made in your lock definitions. So -when trying out game systems you should either use quelling (see below) or make -a second Developer-level character that does not bypass such checks.

    -

    Quelling

    The quell command can be used to enforce the perm() lockfunc to ignore @@ -308,7 +296,7 @@ affectable by locks.

    modules |
  • - next |
  • - - + +
  • - next |
  • - previous |
  • @@ -61,11 +61,11 @@

    Previous topic

    -

    Permissions

    +

    Core Components

    Next topic

    -

    Commands

    +

    Sessions

      @@ -98,11 +98,6 @@

      Portal And Server

      -

      Evennia consists of two processes, known as Portal and Server. They can be controlled from -inside the game or from the command line as described in the Running-Evennia doc.

      -

      In short, the Portal knows everything about internet protocols (telnet, websockets etc), but knows very little about the game.

      -

      In contrast, the Server knows everything about the game. It knows that a player has connected but now how they connected.

      -

      The effect of this is that you can fully reload the Server and have players still connected to the game. One the server comes back up, it will re-connect to the Portal and re-sync all players as if nothing happened.

      Internet│  ┌──────────┐ ┌─┐           ┌─┐ ┌─────────┐
               │  │Portal    │ │S│   ┌───┐   │S│ │Server   │
           P   │  │          │ │e│   │AMP│   │e│ │         │
      @@ -116,7 +111,14 @@ inside the game or from the command line as described twistd processes and can be controlled from inside the game or from the command line as described in the Running-Evennia doc.

      +
        +
      • The Portal knows everything about internet protocols (telnet, websockets etc), but knows very little about the game.

      • +
      • The Server knows everything about the game. It knows that a player has connected but now how they connected.

      • +
      +

      The effect of this is that you can fully reload the Server and have players still connected to the game. One the server comes back up, it will re-connect to the Portal and re-sync all players as if nothing happened.

      +

      The Portal and Server are intended to always run on the same machine. They are glued together via an AMP (Asynchronous Messaging Protocol) connection. This allows the two programs to communicate seamlessly.

      @@ -135,10 +137,10 @@ inside the game or from the command line as described modules |
    • - next |
    • - previous |
    • diff --git a/docs/1.0-dev/Components/Prototypes.html b/docs/1.0-dev/Components/Prototypes.html index 6aba3c54a6..3a733dac7a 100644 --- a/docs/1.0-dev/Components/Prototypes.html +++ b/docs/1.0-dev/Components/Prototypes.html @@ -63,8 +63,9 @@

      Table of Contents

      • Spawner and Prototypes
          +
        • Working with Prototypes @@ -124,47 +124,30 @@

          Spawner and Prototypes

          -

          The spawner is a system for defining and creating individual objects from a base template called a -prototype. It is only designed for use with in-game Objects, not any other type of -entity.

          -

          The normal way to create a custom object in Evennia is to make a Typeclass. If you -haven’t read up on Typeclasses yet, think of them as normal Python classes that save to the database -behind the scenes. Say you wanted to create a “Goblin” enemy. A common way to do this would be to -first create a Mobile typeclass that holds everything common to mobiles in the game, like generic -AI, combat code and various movement methods. A Goblin subclass is then made to inherit from -Mobile. The Goblin class adds stuff unique to goblins, like group-based AI (because goblins are -smarter in a group), the ability to panic, dig for gold etc.

          -

          But now it’s time to actually start to create some goblins and put them in the world. What if we -wanted those goblins to not all look the same? Maybe we want grey-skinned and green-skinned goblins -or some goblins that can cast spells or which wield different weapons? We could make subclasses of -Goblin, like GreySkinnedGoblin and GoblinWieldingClub. But that seems a bit excessive (and a -lot of Python code for every little thing). Using classes can also become impractical when wanting -to combine them - what if we want a grey-skinned goblin shaman wielding a spear - setting up a web -of classes inheriting each other with multiple inheritance can be tricky.

          -

          This is what the prototype is for. It is a Python dictionary that describes these per-instance -changes to an object. The prototype also has the advantage of allowing an in-game builder to -customize an object without access to the Python backend. Evennia also allows for saving and -searching prototypes so other builders can find and use (and tweak) them later. Having a library of -interesting prototypes is a good reasource for builders. The OLC system allows for creating, saving, -loading and manipulating prototypes using a menu system.

          +
          > spawn goblin
          +
          +Spawned Goblin Grunt(#45)
          +
          +
          +

          The spawner is a system for defining and creating individual objects from a base template called a prototype. It is only designed for use with in-game Objects, not any other type of entity.

          +

          The normal way to create a custom object in Evennia is to make a Typeclass. If you haven’t read up on Typeclasses yet, think of them as normal Python classes that save to the database behind the scenes. Say you wanted to create a “Goblin” enemy. A common way to do this would be to first create a Mobile typeclass that holds everything common to mobiles in the game, like generic AI, combat code and various movement methods. A Goblin subclass is then made to inherit from Mobile. The Goblin class adds stuff unique to goblins, like group-based AI (because goblins are smarter in a group), the ability to panic, dig for gold etc.

          +

          But now it’s time to actually start to create some goblins and put them in the world. What if we wanted those goblins to not all look the same? Maybe we want grey-skinned and green-skinned goblins or some goblins that can cast spells or which wield different weapons? We could make subclasses of Goblin, like GreySkinnedGoblin and GoblinWieldingClub. But that seems a bit excessive (and a lot of Python code for every little thing). Using classes can also become impractical when wanting to combine them - what if we want a grey-skinned goblin shaman wielding a spear - setting up a web of classes inheriting each other with multiple inheritance can be tricky.

          +

          This is what the prototype is for. It is a Python dictionary that describes these per-instance changes to an object. The prototype also has the advantage of allowing an in-game builder to customize an object without access to the Python backend. Evennia also allows for saving and searching prototypes so other builders can find and use (and tweak) them later. Having a library of interesting prototypes is a good reasource for builders. The OLC system allows for creating, saving, loading and manipulating prototypes using a menu system.

          The spawner takes a prototype and uses it to create (spawn) new, custom objects.

          +
          +

          Working with Prototypes

          -

          Using the OLC

          -

          Enter the olc command or @spawn/olc to enter the prototype wizard. This is a menu system for +

          Using the OLC

          +

          Enter the olc command or spawn/olc to enter the prototype wizard. This is a menu system for creating, loading, saving and manipulating prototypes. It’s intended to be used by in-game builders and will give a better understanding of prototypes in general. Use help on each node of the menu for more information. Below are further details about how prototypes work and how they are used.

          -

          The prototype

          -

          The prototype dictionary can either be created for you by the OLC (see above), be written manually -in a Python module (and then referenced by the @spawn command/OLC), or created on-the-fly and -manually loaded into the spawner function or @spawn command.

          -

          The dictionary defines all possible database-properties of an Object. It has a fixed set of allowed -keys. When preparing to store the prototype in the database (or when using the OLC), some -of these keys are mandatory. When just passing a one-time prototype-dict to the spawner the system -is -more lenient and will use defaults for keys not explicitly provided.

          +

          The prototype

          +

          The prototype dictionary can either be created for you by the OLC (see above), be written manually in a Python module (and then referenced by the spawn command/OLC), or created on-the-fly and +manually loaded into the spawner function or spawn command.

          +

          The dictionary defines all possible database-properties of an Object. It has a fixed set of allowed keys. When preparing to store the prototype in the database (or when using the OLC), some of these keys are mandatory. When just passing a one-time prototype-dict to the spawner the system is more lenient and will use defaults for keys not explicitly provided.

          In dictionary form, a prototype can look something like this:

          {
              "prototype_key": "house"
          @@ -174,18 +157,13 @@ more lenient and will use defaults for keys not explicitly provided.

          If you wanted to load it into the spawner in-game you could just put all on one line:

          -
          @spawn {"prototype_key="house", "key": "Large house", ...}
          +
          spawn {"prototype_key="house", "key": "Large house", ...}
           
          -

          Note that the prototype dict as given on the command line must be a valid Python structure - -so you need to put quotes around strings etc. For security reasons, a dict inserted from-in game -cannot have any -other advanced Python functionality, such as executable code, lambda etc. If builders are supposed -to be able to use such features, you need to offer them through [$protfuncs](Spawner-and- -Prototypes#protfuncs), embedded runnable functions that you have full control to check and vet -before running.

          +

          Note that the prototype dict as given on the command line must be a valid Python structure - so you need to put quotes around strings etc. For security reasons, a dict inserted from-in game cannot have any other advanced Python functionality, such as executable code, lambda etc. If builders are supposed to be able to use such features, you need to offer them through [$protfuncs](Spawner-and- Prototypes#protfuncs), embedded runnable functions that you have full control to check and vet before running.

          +

          Prototype keys

          All keys starting with prototype_ are for book keeping.

          @@ -200,53 +178,31 @@ left-right inheritance. If this is not given, a prototype_desc - this is optional and used when listing the prototype in in-game listings.

        • protototype_tags - this is optional and allows for tagging the prototype in order to find it easier later.

        • -
        • prototype_locks - two lock types are supported: edit and spawn. The first lock restricts -the copying and editing of the prototype when loaded through the OLC. The second determines who +

        • prototype_locks - two lock types are supported: edit and spawn. The first lock restricts the copying and editing of the prototype when loaded through the OLC. The second determines who may use the prototype to create new objects.

        The remaining keys determine actual aspects of the objects to spawn from this prototype:

          -
        • key - the main object identifier. Defaults to “Spawned Object X”, where X is a random -integer.

        • -
        • typeclass - A full python-path (from your gamedir) to the typeclass you want to use. If not -set, the prototype_parent should be -defined, with typeclass defined somewhere in the parent chain. When creating a one-time -prototype -dict just for spawning, one could omit this - settings.BASE_OBJECT_TYPECLASS will be used -instead.

        • +
        • key - the main object identifier. Defaults to “Spawned Object X”, where X is a random integer.

        • +
        • typeclass - A full python-path (from your gamedir) to the typeclass you want to use. If not set, the prototype_parent should be defined, with typeclass defined somewhere in the parent chain. When creating a one-time prototype dict just for spawning, one could omit this - settings.BASE_OBJECT_TYPECLASS will be used instead.

        • location - this should be a #dbref.

        • -
        • home - a valid #dbref. Defaults to location or settings.DEFAULT_HOME if location does not -exist.

        • +
        • home - a valid #dbref. Defaults to location or settings.DEFAULT_HOME if location does not exist.

        • destination - a valid #dbref. Only used by exits.

        • permissions - list of permission strings, like ["Accounts", "may_use_red_door"]

        • locks - a lock-string like "edit:all();control:perm(Builder)"

        • aliases - list of strings for use as aliases

        • tags - list Tags. These are given as tuples (tag, category, data).

        • attrs - list of Attributes. These are given as tuples (attrname, value, category, lockstring)

        • -
        • Any other keywords are interpreted as non-category Attributes and their values. -This is convenient for simple Attributes - use attrs for full control of Attributes.

        • +
        • Any other keywords are interpreted as non-category Attributes and their values. This is convenient for simple Attributes - use attrs for full control of Attributes.

        More on prototype inheritance

          -
        • A prototype can inherit by defining a prototype_parent pointing to the name -(prototype_key of another prototype). If a list of prototype_keys, this -will be stepped through from left to right, giving priority to the first in -the list over those appearing later. That is, if your inheritance is -prototype_parent = ('A', 'B,' 'C'), and all parents contain colliding keys, -then the one from A will apply.

        • -
        • The prototype keys that start with prototype_* are all unique to each -prototype. They are never inherited from parent to child.

        • -
        • The prototype fields 'attr': [(key, value, category, lockstring),...] -and 'tags': [(key, category, data), ...] are inherited in a complementary -fashion. That means that only colliding key+category matches will be replaced, not the entire list. -Remember that the category None is also considered a valid category!

        • -
        • Adding an Attribute as a simple key:value will under the hood be translated -into an Attribute tuple (key, value, None, '') and may replace an Attribute -in the parent if it the same key and a None category.

        • -
        • All other keys (permissions, destination, aliases etc) are completely -replaced by the child’s value if given. For the parent’s value to be -retained, the child must not define these keys at all.

        • +
        • A prototype can inherit by defining a prototype_parent pointing to the name (prototype_key of another prototype). If a list of prototype_keys, this will be stepped through from left to right, giving priority to the first in the list over those appearing later. That is, if your inheritance is prototype_parent = ('A', 'B,' 'C'), and all parents contain colliding keys, then the one from A will apply.

        • +
        • The prototype keys that start with prototype_* are all unique to each prototype. They are never inherited from parent to child.

        • +
        • The prototype fields 'attr': [(key, value, category, lockstring),...] and 'tags': [(key, category, data), ...] are inherited in a complementary fashion. That means that only colliding key+category matches will be replaced, not the entire list. Remember that the category None is also considered a valid category!

        • +
        • Adding an Attribute as a simple key:value will under the hood be translated into an Attribute tuple (key, value, None, '') and may replace an Attribute in the parent if it the same key and a None category.

        • +
        • All other keys (permissions, destination, aliases etc) are completely replaced by the child’s value if given. For the parent’s value to be retained, the child must not define these keys at all.

    @@ -258,32 +214,26 @@ retained, the child must not define these keys at all.

    -

    It can also be a callable. This callable is called without arguments whenever the prototype is -used to -spawn a new object:

    +

    It can also be a callable. This callable is called without arguments whenever the prototype is used to spawn a new object:

        {"key": _get_a_random_goblin_name, ...}
     
     
    -

    By use of Python lambda one can wrap the callable so as to make immediate settings in the -prototype:

    +

    By use of Python lambda one can wrap the callable so as to make immediate settings in the prototype:

        {"key": lambda: random.choice(("Urfgar", "Rick the smelly", "Blargh the foul", ...)), ...}
     
     

    Protfuncs

    -

    Finally, the value can be a prototype function (Protfunc). These look like simple function calls -that you embed in strings and that has a $ in front, like

    +

    Finally, the value can be a prototype function (Protfunc). These look like simple function calls that you embed in strings and that has a $ in front, like

        {"key": "$choice(Urfgar, Rick the smelly, Blargh the foul)",
          "attrs": {"desc": "This is a large $red(and very red) demon. "
                            "He has $randint(2,5) skulls in a chain around his neck."}
     
    -

    At execution time, the place of the protfunc will be replaced with the result of that protfunc being -called (this is always a string). A protfunc is a FuncParser function run -every time the prototype is used to spawn a new object.

    -

    Here is how a protfunc is defined (same as an inlinefunc).

    +

    At execution time, the place of the protfunc will be replaced with the result of that protfunc being called (this is always a string). A protfunc is a FuncParser function run every time the prototype is used to spawn a new object.

    +

    Here is how a protfunc is defined (same as other funcparser functions).

    # this is a silly example, you can just color the text red with |r directly!
     def red(*args, **kwargs):
        """
    @@ -296,23 +246,17 @@ every time the prototype is used to spawn a new object.

    -

    Note that we must make sure to validate input and raise ValueError if that fails. Also, it is -not possible to use keywords in the call to the protfunc (so something like $echo(text, align=left) is invalid). The kwargs requred is for internal evennia use and not used at all for -protfuncs (only by inlinefuncs).

    +

    Note that we must make sure to validate input and raise ValueError if that fails. Also, it is not possible to use keywords in the call to the protfunc (so something like $echo(text, align=left) is invalid). The kwargs requred is for internal evennia use and not used at all for protfuncs (only by inlinefuncs).

    -

    To make this protfunc available to builders in-game, add it to a new module and add the path to that -module to settings.PROT_FUNC_MODULES:

    +

    To make this protfunc available to builders in-game, add it to a new module and add the path to that module to settings.PROT_FUNC_MODULES:

    # in mygame/server/conf/settings.py
     
     PROT_FUNC_MODULES += ["world.myprotfuncs"]
     
     
    -

    All global callables in your added module will be considered a new protfunc. To avoid this (e.g. -to have helper functions that are not protfuncs on their own), name your function something starting -with _.

    -

    The default protfuncs available out of the box are defined in evennia/prototypes/profuncs.py. To -override the ones available, just add the same-named function in your own protfunc module.

    +

    All global callables in your added module will be considered a new protfunc. To avoid this (e.g. to have helper functions that are not protfuncs on their own), name your function something starting with _.

    +

    The default protfuncs available out of the box are defined in evennia/prototypes/profuncs.py. To override the ones available, just add the same-named function in your own protfunc module.

    @@ -370,31 +314,17 @@ override the ones available, just add the same-named function in your own protfu

    Protfunc

    -

    For developers with access to Python, using protfuncs in prototypes is generally not useful. Passing -real Python functions is a lot more powerful and flexible. Their main use is to allow in-game -builders to -do limited coding/scripting for their prototypes without giving them direct access to raw Python.

    +

    For developers with access to Python, using protfuncs in prototypes is generally not useful. Passing real Python functions is a lot more powerful and flexible. Their main use is to allow in-game builders to do limited coding/scripting for their prototypes without giving them direct access to raw Python.

    -
    -

    Storing prototypes

    -

    A prototype can be defined and stored in two ways, either in the database or as a dict in a module.

    -

    Database prototypes

    -

    Stored as Scripts in the database. These are sometimes referred to as database- -prototypes This is the only way for in-game builders to modify and add prototypes. They have the -advantage of being easily modifiable and sharable between builders but you need to work with them -using in-game tools.

    +

    Database prototypes

    +

    Stored as Scripts in the database. These are sometimes referred to as database- prototypes This is the only way for in-game builders to modify and add prototypes. They have the advantage of being easily modifiable and sharable between builders but you need to work with them using in-game tools.

    -

    Module-based prototypes

    -

    These prototypes are defined as dictionaries assigned to global variables in one of the modules -defined in settings.PROTOTYPE_MODULES. They can only be modified from outside the game so they are -are necessarily “read-only” from in-game and cannot be modified (but copies of them could be made -into database-prototypes). These were the only prototypes available before Evennia 0.8. Module based -prototypes can be useful in order for developers to provide read-only “starting” or “base” -prototypes to build from or if they just prefer to work offline in an external code editor.

    +

    Module-based prototypes

    +

    These prototypes are defined as dictionaries assigned to global variables in one of the modules defined in settings.PROTOTYPE_MODULES. They can only be modified from outside the game so they are are necessarily “read-only” from in-game and cannot be modified (but copies of them could be made into database-prototypes). These were the only prototypes available before Evennia 0.8. Module based prototypes can be useful in order for developers to provide read-only “starting” or “base” prototypes to build from or if they just prefer to work offline in an external code editor.

    By default mygame/world/prototypes.py is set up for you to add your own prototypes. All global dicts in this module will be considered by Evennia to be a prototype. You could also tell Evennia to look for prototypes in more modules if you want:

    @@ -423,17 +353,14 @@ was given explicitly, that would take precedence. This is a legacy behavior and that you always add prototype_key to be consistent.

    -
    -
    -

    Using @spawn

    -

    The spawner can be used from inside the game through the Builder-only @spawn command. Assuming the -“goblin” typeclass is available to the system (either as a database-prototype or read from module), -you can spawn a new goblin with

    -
    @spawn goblin
    +
    +

    Spawning

    +

    The spawner can be used from inside the game through the Builder-only @spawn command. Assuming the “goblin” typeclass is available to the system (either as a database-prototype or read from module), you can spawn a new goblin with

    +
    spawn goblin
     

    You can also specify the prototype directly as a valid Python dictionary:

    -
    @spawn {"prototype_key": "shaman", \
    +
    spawn {"prototype_key": "shaman", \
         "key":"Orc shaman", \
             "prototype_parent": "goblin", \
             "weapon": "wooden staff", \
    @@ -441,14 +368,10 @@ you can spawn a new goblin with

    -

    Note: The @spawn command is more lenient about the prototype dictionary than shown here. So you -can for example skip the prototype_key if you are just testing a throw-away prototype. A random -hash will be used to please the validation. You could also skip prototype_parent/typeclass - then -the typeclass given by settings.BASE_OBJECT_TYPECLASS will be used.

    +

    Note: The @spawncommand is more lenient about the prototype dictionary than shown here. So you can for example skip theprototype_keyif you are just testing a throw-away prototype. A random hash will be used to please the validation. You could also skip prototype_parent/typeclass- then the typeclass given bysettings.BASE_OBJECT_TYPECLASS` will be used.

    -
    -

    Using evennia.prototypes.spawner()

    +

    Using evennia.prototypes.spawner()

    In code you access the spawner mechanism directly via the call

        new_objects = evennia.prototypes.spawner.spawn(*prototypes)
     
    @@ -460,18 +383,10 @@ matching list of created objects. Example:

    -

    Hint: Same as when using @spawn, when spawning from a one-time prototype dict like this, you can -skip otherwise required keys, like prototype_key or typeclass/prototype_parent. Defaults will -be used.

    +

    Hint: Same as when using spawn, when spawning from a one-time prototype dict like this, you can skip otherwise required keys, like prototype_key or typeclass/prototype_parent. Defaults will be used.

    -

    Note that no location will be set automatically when using evennia.prototypes.spawner.spawn(), -you -have to specify location explicitly in the prototype dict.

    -

    If the prototypes you supply are using prototype_parent keywords, the spawner will read prototypes -from modules -in settings.PROTOTYPE_MODULES as well as those saved to the database to determine the body of -available parents. The spawn command takes many optional keywords, you can find its definition in -the api docs.

    +

    Note that no location will be set automatically when using evennia.prototypes.spawner.spawn(), you have to specify location explicitly in the prototype dict. If the prototypes you supply are using prototype_parent keywords, the spawner will read prototypes from modules in settings.PROTOTYPE_MODULES as well as those saved to the database to determine the body of available parents. The spawn command takes many optional keywords, you can find its definition in the api docs.

    +
    diff --git a/docs/1.0-dev/Components/Scripts.html b/docs/1.0-dev/Components/Scripts.html index 2640047741..1180282dec 100644 --- a/docs/1.0-dev/Components/Scripts.html +++ b/docs/1.0-dev/Components/Scripts.html @@ -63,20 +63,20 @@

    Table of Contents

    @@ -120,39 +120,20 @@

    Scripts

    Script API reference

    -

    Scripts are the out-of-character siblings to the in-character -Objects. Scripts are so flexible that the name “Script” is a bit limiting -in itself - but we had to pick something to name them. Other possible names -(depending on what you’d use them for) would be OOBObjects, StorageContainers or TimerObjects.

    -

    If you ever consider creating an Object with a None-location just to store some game data, -you should really be using a Script instead.

    +

    Scripts are the out-of-character siblings to the in-character Objects. Scripts are so flexible that the name “Script” is a bit limiting in itself - but we had to pick something to name them. Other possible names (depending on what you’d use them for) would be OOBObjects, StorageContainers or TimerObjects.

    +

    If you ever consider creating an Object with a None-location just to store some game data, you should really be using a Script instead.

      -
    • Scripts are full Typeclassed entities - they have Attributes and -can be modified in the same way. But they have no in-game existence, so no -location or command-execution like Objects and no connection to a particular -player/session like Accounts. This means they are perfectly suitable for acting -as database-storage backends for game systems: Storing the current state of the economy, -who is involved in the current fight, tracking an ongoing barter and so on. They are great as -persistent system handlers.

    • -
    • Scripts have an optional timer component. This means that you can set up the script -to tick the at_repeat hook on the Script at a certain interval. The timer can be controlled -independently of the rest of the script as needed. This component is optional -and complementary to other timing functions in Evennia, like -evennia.utils.delay and -evennia.utils.repeat.

    • -
    • Scripts can attach to Objects and Accounts via e.g. obj.scripts.add/remove. In the -script you can then access the object/account as self.obj or self.account. This can be used to -dynamically extend other typeclasses but also to use the timer component to affect the parent object -in various ways. For historical reasons, a Script not attached to an object is referred to as a -Global Script.

    • +
    • Scripts are full Typeclassed entities - they have Attributes and can be modified in the same way. But they have no in-game existence, so no location or command-execution like Objects and no connection to a particular player/session like Accounts. This means they are perfectly suitable for acting as database-storage backends for game systems: Storing the current state of the economy, who is involved in the current fight, tracking an ongoing barter and so on. They are great as persistent system handlers.

    • +
    • Scripts have an optional timer component. This means that you can set up the script to tick the at_repeat hook on the Script at a certain interval. The timer can be controlled independently of the rest of the script as needed. This component is optional and complementary to other timing functions in Evennia, like evennia.utils.delay and evennia.utils.repeat.

    • +
    • Scripts can attach to Objects and Accounts via e.g. obj.scripts.add/remove. In the script you can then access the object/account as self.obj or self.account. This can be used to dynamically extend other typeclasses but also to use the timer component to affect the parent object in various ways. For historical reasons, a Script not attached to an object is referred to as a Global Script.

    Changed in version 1.0: In previus Evennia versions, stopping the Script’s timer also meant deleting the Script object. Starting with this version, the timer can be start/stopped separately and .delete() must be called on the Script explicitly to delete it.

    -
    -

    In-game command examples

    +
    +

    Working with Scripts

    There are two main commands controlling scripts in the default cmdset:

    The addscript command is used for attaching scripts to existing objects:

    > addscript obj = bodyfunctions.BodyFunctions
    @@ -169,9 +150,8 @@ on the Script explicitly to delete it.

    Changed in version 1.0: The addscript command used to be only script which was easy to confuse with scripts.

    -
    -

    Code examples

    +

    Code examples

    Here are some examples of working with Scripts in-code (more details to follow in later sections).

    Create a new script:

    @@ -217,11 +197,11 @@ sections).

    -

    Defining new Scripts

    +

    Defining new Scripts

    A Script is defined as a class and is created in the same way as other typeclassed entities. The parent class is evennia.DefaultScript.

    -

    Simple storage script

    +

    Simple storage script

    In mygame/typeclasses/scripts.py is an empty Script class already set up. You can use this as a base for your own scripts.

    # in mygame/typeclasses/scripts.py
    @@ -259,13 +239,10 @@ you set in your at_
     
     
    -

    See the create_script and -search_script API documentation for more options -on creating and finding Scripts.

    +

    See the create_script and search_script API documentation for more options on creating and finding Scripts.

    -
    -
    -

    Timed Scripts

    +
    +

    Timed Script

    There are several properties one can set on the Script to control its timer component.

    # in mygame/typeclasses/scripts.py
     
    @@ -293,8 +270,7 @@ to help identifying what does what.

  • interval (int): The amount of time (in seconds) between every ‘tick’ of the timer. Note that it’s generally bad practice to use sub-second timers for anything in a text-game - the player will not be able to appreciate the precision (and if you print it, it will just spam the screen). For -calculations you can pretty much always do them on-demand, or at a much slower interval without the -player being the wiser.

  • +calculations you can pretty much always do them on-demand, or at a much slower interval without the player being the wiser.

  • start_delay (bool): If timer should start right away or wait interval seconds first.

  • repeats (int): If >0, the timer will only run this many times before stopping. Otherwise the number of repeats are infinite. If set to 1, the Script mimics a delay action.

  • @@ -320,30 +296,18 @@ at which time the m
  • .time_until_next_repeat() - get the time until next time the timer fires.

  • .remaining_repeats() - get the number of repeats remaining, or None if repeats are infinite.

  • .reset_callcount() - this resets the repeat counter to start over from 0. Only useful if repeats>0.

  • -
  • .force_repeat() - this prematurely forces at_repeat to be called right away. Doing so will reset the -countdown so that next call will again happen after interval seconds.

  • +
  • .force_repeat() - this prematurely forces at_repeat to be called right away. Doing so will reset the countdown so that next call will again happen after interval seconds.

  • +
    +

    Script timers vs delay/repeat

    -

    If the only goal is to get a repeat/delay effect, the -evennia.utils.delay and -evennia.utils.repeat functions -should generally be considered first. A Script is a lot ‘heavier’ to create/delete on the fly. -In fact, for making a single delayed call (script.repeats==1), the utils.delay call is -probably always the better choice.

    -

    For repeating tasks, the utils.repeat is optimized for quick repeating of a large number of objects. It -uses the TickerHandler under the hood. Its subscription-based model makes it very efficient to -start/stop the repeating action for an object. The side effect is however that all objects set to tick -at a given interval will all do so at the same time. This may or may not look strange in-game depending -on the situation. By contrast the Script uses its own ticker that will operate independently from the -tickers of all other Scripts.

    -

    It’s also worth noting that once the script object has already been created, -starting/stopping/pausing/unpausing the timer has very little overhead. The pause/unpause and update -methods of the script also offers a bit more fine-control than using utils.delays/repeat.

    -
    +

    If the only goal is to get a repeat/delay effect, the evennia.utils.delay and evennia.utils.repeat functions should generally be considered first. A Script is a lot ‘heavier’ to create/delete on the fly. In fact, for making a single delayed call (script.repeats==1), the utils.delay call is probably always the better choice.

    +

    For repeating tasks, the utils.repeat is optimized for quick repeating of a large number of objects. It uses the TickerHandler under the hood. Its subscription-based model makes it very efficient to start/stop the repeating action for an object. The side effect is however that all objects set to tick at a given interval will all do so at the same time. This may or may not look strange in-game depending on the situation. By contrast the Script uses its own ticker that will operate independently from the tickers of all other Scripts.

    +

    It’s also worth noting that once the script object has already been created, starting/stopping/pausing/unpausing the timer has very little overhead. The pause/unpause and update methods of the script also offers a bit more fine-control than using utils.delays/repeat.

    -

    Script attached to another object

    +

    Script attached to another object

    Scripts can be attached to an Account or (more commonly) an Object. If so, the ‘parent object’ will be available to the script as either .obj or .account.

        # mygame/typeclasses/scripts.py
    @@ -392,20 +356,16 @@ but only scripts.We
     
    -

    Other Script methods

    +

    Other Script methods

    A Script has all the properties of a typeclassed object, such as db and ndb(see Typeclasses). Setting key is useful in order to manage scripts (delete them by name etc). These are usually set up in the Script’s typeclass, but can also be assigned on the fly as keyword arguments to evennia.create_script.

    • at_script_creation() - this is only called once - when the script is first created.

    • -
    • at_server_reload() - this is called whenever the server is warm-rebooted (e.g. with the -reload command). It’s a good place to save non-persistent data you might want to survive a -reload.

    • +
    • at_server_reload() - this is called whenever the server is warm-rebooted (e.g. with the reload command). It’s a good place to save non-persistent data you might want to survive a reload.

    • at_server_shutdown() - this is called when a system reset or systems shutdown is invoked.

    • -
    • at_server_start() - this is called when the server comes back (from reload/shutdown/reboot). It -can be usuful for initializations and caching of non-persistent data when starting up a script’s -functionality.

    • +
    • at_server_start() - this is called when the server comes back (from reload/shutdown/reboot). It can be usuful for initializations and caching of non-persistent data when starting up a script’s functionality.

    • at_repeat()

    • at_start()

    • at_pause()

    • @@ -413,12 +373,33 @@ functionality.

    • delete() - same as for other typeclassed entities, this will delete the Script. Of note is that it will also stop the timer (if it runs), leading to the at_stop hook being called.

    -

    In addition, Scripts support Attributes, Tags and Locks etc like other -Typeclassed entities.

    -

    See also the methods involved in controlling a Timed Script above.

    +

    In addition, Scripts support Attributes, Tags and Locks etc like other Typeclassed entities.

    +

    See also the methods involved in controlling a Timed Script above.

    -
    -

    The GLOBAL_SCRIPTS container

    +
    +

    Dealing with Script Errors

    +

    Errors inside a timed, executing script can sometimes be rather terse or point to parts of the execution mechanism that is hard to interpret. One way to make it easier to debug scripts is to import Evennia’s native logger and wrap your functions in a try/catch block. Evennia’s logger can show you where the traceback occurred in your script.

    +
    
    +from evennia.utils import logger
    +
    +class Weather(Script):
    +
    +    # [...]
    +
    +    def at_repeat(self):
    +
    +        try:
    +            # [...]
    +        except Exception:
    +            logger.log_trace()
    +https://github.com/evennia/evennia/blob/master/evennia/contrib/tutorial_examples/example_batch_cmds.ev
    +
    +
    +
    +
    +
    +
    +

    Using GLOBAL_SCRIPTS

    A Script not attached to another entity is commonly referred to as a Global script since it’t available to access from anywhere. This means they need to be searched for in order to be used.

    Evennia supplies a convenient “container” evennia.GLOBAL_SCRIPTS to help organize your global @@ -438,13 +419,7 @@ scripts. All you need is the Script’s

    Warning

    -
    Note that global scripts appear as properties on `GLOBAL_SCRIPTS` based on their `key`.
    -If you were to create two global scripts with the same `key` (even with different typeclasses),
    -the `GLOBAL_SCRIPTS` container will only return one of them (which one depends on order in
    -the database). Best is to organize your scripts so that this does not happen. Otherwise, use
    -`evennia.search_scripts` to get exactly the script you want.
    -
    -
    +

    Note that global scripts appear as properties on GLOBAL_SCRIPTS based on their key. If you were to create two global scripts with the same key (even with different typeclasses), the GLOBAL_SCRIPTS container will only return one of them (which one depends on order in the database). Best is to organize your scripts so that this does not happen. Otherwise, use evennia.search_scripts to get exactly the script you want.

    There are two ways to make a script appear as a property on GLOBAL_SCRIPTS:

      @@ -469,18 +444,10 @@ script keys are globally unique.

      } -

      Above we add two scripts with keys myscript and storagescriptrespectively. The following dict -can be empty - the settings.BASE_SCRIPT_TYPECLASS will then be used. Under the hood, the provided -dict (along with the key) will be passed into create_script automatically, so -all the same keyword arguments as for create_script are -supported here.

      +

      Above we add two scripts with keys myscript and storagescriptrespectively. The following dict can be empty - the settings.BASE_SCRIPT_TYPECLASS will then be used. Under the hood, the provided dict (along with the key) will be passed into create_script automatically, so all the same keyword arguments as for create_script are supported here.

      Warning

      -
      Before setting up Evennia to manage your script like this, make sure that your Script typeclass
      -does not have any critical errors (test it separately). If there are, you'll see errors in your log
      -and your Script will temporarily fall back to being a `DefaultScript` type.
      -
      -
      +

      Before setting up Evennia to manage your script like this, make sure that your Script typeclass does not have any critical errors (test it separately). If there are, you’ll see errors in your log and your Script will temporarily fall back to being a DefaultScript type.

      Moreover, a script defined this way is guaranteed to exist when you try to access it:

      from evennia import GLOBAL_SCRIPTS
      @@ -494,29 +461,6 @@ and your Script will temporarily fall back to being a `DefaultScript` type.
       

      That is, if the script is deleted, next time you get it from GLOBAL_SCRIPTS, Evennia will use the information in settings to recreate it for you on the fly.

    -
    -

    Hints: Dealing with Script Errors

    -

    Errors inside a timed, executing script can sometimes be rather terse or point to -parts of the execution mechanism that is hard to interpret. One way to make it -easier to debug scripts is to import Evennia’s native logger and wrap your -functions in a try/catch block. Evennia’s logger can show you where the -traceback occurred in your script.

    -
    
    -from evennia.utils import logger
    -
    -class Weather(Script):
    -
    -    # [...]
    -
    -    def at_repeat(self):
    -
    -        try:
    -            # [...]
    -        except Exception:
    -            logger.log_trace()
    -
    -
    -
    diff --git a/docs/1.0-dev/Components/Sessions.html b/docs/1.0-dev/Components/Sessions.html index 7bca2ebe16..472b9ee0ae 100644 --- a/docs/1.0-dev/Components/Sessions.html +++ b/docs/1.0-dev/Components/Sessions.html @@ -17,8 +17,8 @@ - - + +