mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Cleaned up Components page
This commit is contained in:
parent
bc092b8b2b
commit
3b6f16f529
29 changed files with 969 additions and 1735 deletions
|
|
@ -12,7 +12,7 @@ An _Account_ represents a unique game account - one player playing the game. Whe
|
|||
The Account object has no in-game representation. In order to actually get on the game the Account must *puppet* an [Object](./Objects.md) (normally a [Character](./Objects.md#characters)).
|
||||
|
||||
Exactly how many Sessions can interact with an Account and its Puppets at once is determined by
|
||||
Evennia's [MULTISESSION_MODE](./Sessions.md#multisession-mode) setting.
|
||||
Evennia's [MULTISESSION_MODE](../Concepts/Connection-Styles.md#multisession-mode-and-multiplaying)
|
||||
|
||||
Apart from storing login information and other account-specific data, the Account object is what is chatting on Evennia's default [Channels](./Channels.md). It is also a good place to store [Permissions](./Locks.md) to be consistent between different in-game characters. It can also hold player-level configuration options.
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ This re-puppets the latest character.
|
|||
Note that the Account object can have, and often does have, a different set of [Permissions](./Permissions.md) from the Character they control. Normally you should put your permissions on the Account level - this will overrule permissions set on the Character level. For the permissions of the Character to come into play the default `quell` command can be used. This allows for exploring the game using a different permission set (but you can't escalate your permissions this way - for hierarchical permissions like `Builder`, `Admin` etc, the *lower* of the permissions on the Character/Account will always be used).
|
||||
|
||||
|
||||
## How to customize your own Account types
|
||||
## Working with Accounts
|
||||
|
||||
You will usually not want more than one Account typeclass for all new accounts.
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ You should now see the Attributes on yourself.
|
|||
> If you wanted Evennia to default to a completely *different* Account class located elsewhere, you > must point Evennia to it. Add `BASE_ACCOUNT_TYPECLASS` to your settings file, and give the python path to your custom class as its value. By default this points to `typeclasses.accounts.Account`, the empty template we used above.
|
||||
|
||||
|
||||
## Properties on Accounts
|
||||
### Properties on Accounts
|
||||
|
||||
Beyond those properties assigned to all typeclassed objects (see [Typeclasses](./Typeclasses.md)), the Account also has the following custom properties:
|
||||
|
||||
|
|
|
|||
|
|
@ -25,27 +25,13 @@ class MyObject(DefaultObject):
|
|||
|
||||
```
|
||||
|
||||
_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.
|
||||
_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.
|
||||
|
||||
- [What can be stored in an Attribute](#what-types-of-data-can-i-save-in-an-attribute) is a must-read to avoid being surprised, also for experienced developers. Attributes can store _almost_ everything
|
||||
but you need to know the quirks.
|
||||
- [NAttributes](#in-memory-attributes-nattributes) are the in-memory, non-persistent
|
||||
siblings of Attributes.
|
||||
- [Managing Attributes In-game](#managing-attributes-in-game) for in-game builder commands.
|
||||
## Working with Attributes
|
||||
|
||||
## Managing Attributes in Code
|
||||
|
||||
Attributes are usually handled in code. All [Typeclassed](./Typeclasses.md) entities
|
||||
([Accounts](./Accounts.md), [Objects](./Objects.md), [Scripts](./Scripts.md) and
|
||||
[Channels](./Channels.md)) can (and usually do) have Attributes associated with them. There
|
||||
Attributes are usually handled in code. All [Typeclassed](./Typeclasses.md) entities ([Accounts](./Accounts.md), [Objects](./Objects.md), [Scripts](./Scripts.md) and [Channels](./Channels.md)) can (and usually do) have Attributes associated with them. There
|
||||
are three ways to manage Attributes, all of which can be mixed.
|
||||
|
||||
- [Using the `.db` property shortcut](#using-db)
|
||||
- [Using the `.attributes` manager (`AttributeManager`)](#using-attributes)
|
||||
- [Using `AttributeProperty` for assigning Attributes in a way similar to Django fields](#using-attributeproperty)
|
||||
|
||||
### 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`)
|
||||
|
|
@ -115,8 +101,7 @@ neck_armor = obj.attributes.get("neck", category="armor")
|
|||
|
||||
If you don't specify a category, the Attribute's `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](evennia.typeclasses.attributes.AttributeHandler) for more details.
|
||||
Here are the methods of the `AttributeHandler`. See the [AttributeHandler API](evennia.typeclasses.attributes.AttributeHandler) 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.
|
||||
|
|
@ -214,16 +199,46 @@ char.db.sleepy # now returns True!
|
|||
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](evennia.typeclasses.attributes.AttributeProperty) for more details on how to create it with special options, like giving access-restrictions.
|
||||
|
||||
### Properties of Attributes
|
||||
|
||||
## Managing Attributes in-game
|
||||
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](./Attributes.md#what-types-of-data-can-i-save-in-an-attribute) 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](./Nicks.md) is an example of using
|
||||
Attributes in this way). To modify this property you need to use the [Attribute Handler](#attributes)
|
||||
- `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](./Nicks.md) use it). It is only
|
||||
accessible via the [Attribute Handler](#attributes).
|
||||
|
||||
There are also two special properties:
|
||||
|
||||
- `attrtype` - this is used internally by Evennia to separate [Nicks](./Nicks.md), 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
|
||||
|
||||
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
|
||||
|
|
@ -261,7 +276,7 @@ string.
|
|||
|
||||
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
|
||||
|
|
@ -313,6 +328,7 @@ To check the `lockstring` you provided, make sure you include `accessing_obj` an
|
|||
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
|
||||
|
|
@ -519,37 +535,6 @@ The result of this operation will be a structure only consisting of normal Pytho
|
|||
instead of `_SaverList`, `dict` instead of `_SaverDict` and so on). If you update it, you need to
|
||||
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](./Attributes.md#what-types-of-data-can-i-save-in-an-attribute) 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](./Nicks.md) is an example of using
|
||||
Attributes in this way). To modify this property you need to use the [Attribute Handler](#attributes)
|
||||
- `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](./Nicks.md) use it). It is only
|
||||
accessible via the [Attribute Handler](#attributes).
|
||||
|
||||
There are also two special properties:
|
||||
|
||||
- `attrtype` - this is used internally by Evennia to separate [Nicks](./Nicks.md), 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)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,61 +1,31 @@
|
|||
# Batch Code Processor
|
||||
|
||||
|
||||
For an introduction and motivation to using batch processors, see [here](./Batch-Processors.md). This
|
||||
page describes the Batch-*code* processor. The Batch-*command* one is covered [here](Batch-Command-
|
||||
Processor).
|
||||
|
||||
## Basic Usage
|
||||
For an introduction and motivation to using batch processors, see [here](./Batch-Processors.md). This page describes the Batch-*code* processor. The Batch-*command* one is covered [here](Batch-Command- Processor).
|
||||
|
||||
The batch-code processor is a superuser-only function, invoked by
|
||||
|
||||
> @batchcode path.to.batchcodefile
|
||||
> batchcode path.to.batchcodefile
|
||||
|
||||
Where `path.to.batchcodefile` is the path to a *batch-code file*. Such a file should have a name
|
||||
ending in "`.py`" (but you shouldn't include that in the path). The path is given like a python path
|
||||
relative to a folder you define to hold your batch files, set by `BATCH_IMPORT_PATH` in your
|
||||
settings. Default folder is (assuming your game is called "mygame") `mygame/world/`. So if you want
|
||||
to run the example batch file in `mygame/world/batch_code.py`, you could simply use
|
||||
Where `path.to.batchcodefile` is the path to a *batch-code file*. Such a file should have a name ending in "`.py`" (but you shouldn't include that in the path). The path is given like a python path relative to a folder you define to hold your batch files, set by `BATCH_IMPORT_PATH` in your settings. Default folder is (assuming your game is called "mygame") `mygame/world/`. So if you want to run the example batch file in `mygame/world/batch_code.py`, you could simply use
|
||||
|
||||
> @batchcode batch_code
|
||||
> batchcode batch_code
|
||||
|
||||
This will try to run through the entire batch file in one go. For more gradual, *interactive*
|
||||
control you can use the `/interactive` switch. The switch `/debug` will put the processor in
|
||||
*debug* mode. Read below for more info.
|
||||
This will try to run through the entire batch file in one go. For more gradual, *interactive* control you can use the `/interactive` switch. The switch `/debug` will put the processor in *debug* mode. Read below for more info.
|
||||
|
||||
## The batch file
|
||||
|
||||
A batch-code file is a normal Python file. The difference is that since the batch processor loads
|
||||
and executes the file rather than importing it, you can reliably update the file, then call it
|
||||
again, over and over and see your changes without needing to `@reload` the server. This makes for
|
||||
easy testing. In the batch-code file you have also access to the following global variables:
|
||||
A batch-code file is a normal Python file. The difference is that since the batch processor loads and executes the file rather than importing it, you can reliably update the file, then call it again, over and over and see your changes without needing to `reload` the server. This makes for easy testing. In the batch-code file you have also access to the following global variables:
|
||||
|
||||
- `caller` - This is a reference to the object running the batchprocessor.
|
||||
- `DEBUG` - This is a boolean that lets you determine if this file is currently being run in debug-
|
||||
mode or not. See below how this can be useful.
|
||||
- `DEBUG` - This is a boolean that lets you determine if this file is currently being run in debug-mode or not. See below how this can be useful.
|
||||
|
||||
Running a plain Python file through the processor will just execute the file from beginning to end.
|
||||
If you want to get more control over the execution you can use the processor's *interactive* mode.
|
||||
This runs certain code blocks on their own, rerunning only that part until you are happy with it. In
|
||||
order to do this you need to add special markers to your file to divide it up into smaller chunks.
|
||||
These take the form of comments, so the file remains valid Python.
|
||||
Running a plain Python file through the processor will just execute the file from beginning to end. If you want to get more control over the execution you can use the processor's *interactive* mode. This runs certain code blocks on their own, rerunning only that part until you are happy with it. In order to do this you need to add special markers to your file to divide it up into smaller chunks. These take the form of comments, so the file remains valid Python.
|
||||
|
||||
Here are the rules of syntax of the batch-code `*.py` file.
|
||||
|
||||
- `#CODE` as the first on a line marks the start of a *code* block. It will last until the beginning
|
||||
of another marker or the end of the file. Code blocks contain functional python code. Each `#CODE`
|
||||
block will be run in complete isolation from other parts of the file, so make sure it's self-
|
||||
contained.
|
||||
- `#HEADER` as the first on a line marks the start of a *header* block. It lasts until the next
|
||||
marker or the end of the file. This is intended to hold imports and variables you will need for all
|
||||
other blocks .All python code defined in a header block will always be inserted at the top of every
|
||||
`#CODE` blocks in the file. You may have more than one `#HEADER` block, but that is equivalent to
|
||||
having one big one. Note that you can't exchange data between code blocks, so editing a header-
|
||||
variable in one code block won't affect that variable in any other code block!
|
||||
- `#INSERT path.to.file` will insert another batchcode (Python) file at that position.
|
||||
- A `#` that is not starting a `#HEADER`, `#CODE` or `#INSERT` instruction is considered a comment.
|
||||
- Inside a block, normal Python syntax rules apply. For the sake of indentation, each block acts as
|
||||
a separate python module.
|
||||
- `#CODE` as the first on a line marks the start of a *code* block. It will last until the beginning of another marker or the end of the file. Code blocks contain functional python code. Each `#CODE` block will be run in complete isolation from other parts of the file, so make sure it's self- contained.
|
||||
- `#HEADER` as the first on a line marks the start of a *header* block. It lasts until the next marker or the end of the file. This is intended to hold imports and variables you will need for all other blocks .All python code defined in a header block will always be inserted at the top of every `#CODE` blocks in the file. You may have more than one `#HEADER` block, but that is equivalent to having one big one. Note that you can't exchange data between code blocks, so editing a header- variable in one code block won't affect that variable in any other code block!
|
||||
- `#INSERT path.to.file` will insert another batchcode (Python) file at that position. - A `#` that is not starting a `#HEADER`, `#CODE` or `#INSERT` instruction is considered a comment.
|
||||
- Inside a block, normal Python syntax rules apply. For the sake of indentation, each block acts as a separate python module.
|
||||
|
||||
Below is a version of the example file found in `evennia/contrib/tutorial_examples/`.
|
||||
|
||||
|
|
@ -105,37 +75,25 @@ This uses Evennia's Python API to create three objects in sequence.
|
|||
|
||||
Try to run the example script with
|
||||
|
||||
> @batchcode/debug tutorial_examples.example_batch_code
|
||||
> batchcode/debug tutorial_examples.example_batch_code
|
||||
|
||||
The batch script will run to the end and tell you it completed. You will also get messages that the
|
||||
button and the two pieces of furniture were created. Look around and you should see the button
|
||||
there. But you won't see any chair nor a table! This is because we ran this with the `/debug`
|
||||
switch, which is directly visible as `DEBUG==True` inside the script. In the above example we
|
||||
handled this state by deleting the chair and table again.
|
||||
The batch script will run to the end and tell you it completed. You will also get messages that the button and the two pieces of furniture were created. Look around and you should see the button there. But you won't see any chair nor a table! This is because we ran this with the `/debug` switch, which is directly visible as `DEBUG==True` inside the script. In the above example we handled this state by deleting the chair and table again.
|
||||
|
||||
The debug mode is intended to be used when you test out a batchscript. Maybe you are looking for
|
||||
bugs in your code or try to see if things behave as they should. Running the script over and over
|
||||
would then create an ever-growing stack of chairs and tables, all with the same name. You would have
|
||||
to go back and painstakingly delete them later.
|
||||
The debug mode is intended to be used when you test out a batchscript. Maybe you are looking for bugs in your code or try to see if things behave as they should. Running the script over and over would then create an ever-growing stack of chairs and tables, all with the same name. You would have to go back and painstakingly delete them later.
|
||||
|
||||
## Interactive mode
|
||||
|
||||
Interactive mode works very similar to the [batch-command processor counterpart](Batch-Command-
|
||||
Processor). It allows you more step-wise control over how the batch file is executed. This is useful
|
||||
for debugging or for picking and choosing only particular blocks to run. Use `@batchcode` with the
|
||||
`/interactive` flag to enter interactive mode.
|
||||
Interactive mode works very similar to the [batch-command processor counterpart](Batch-Command- Processor). It allows you more step-wise control over how the batch file is executed. This is useful for debugging or for picking and choosing only particular blocks to run. Use `batchcode` with the `/interactive` flag to enter interactive mode.
|
||||
|
||||
> @batchcode/interactive tutorial_examples.batch_code
|
||||
> batchcode/interactive tutorial_examples.batch_code
|
||||
|
||||
You should see the following:
|
||||
|
||||
01/02: red_button = create_object(red_button.RedButton, [...] (hh for help)
|
||||
|
||||
This shows that you are on the first `#CODE` block, the first of only two commands in this batch
|
||||
file. Observe that the block has *not* actually been executed at this point!
|
||||
This shows that you are on the first `#CODE` block, the first of only two commands in this batch file. Observe that the block has *not* actually been executed at this point!
|
||||
|
||||
To take a look at the full code snippet you are about to run, use `ll` (a batch-processor version of
|
||||
`look`).
|
||||
To take a look at the full code snippet you are about to run, use `ll` (a batch-processor version of `look`).
|
||||
|
||||
```python
|
||||
from evennia.utils import create, search
|
||||
|
|
@ -151,23 +109,16 @@ To take a look at the full code snippet you are about to run, use `ll` (a batch-
|
|||
caller.msg("A red button was created.")
|
||||
```
|
||||
|
||||
Compare with the example code given earlier. Notice how the content of `#HEADER` has been pasted at
|
||||
the top of the `#CODE` block. Use `pp` to actually execute this block (this will create the button
|
||||
Compare with the example code given earlier. Notice how the content of `#HEADER` has been pasted at the top of the `#CODE` block. Use `pp` to actually execute this block (this will create the button
|
||||
and give you a message). Use `nn` (next) to go to the next command. Use `hh` for a list of commands.
|
||||
|
||||
If there are tracebacks, fix them in the batch file, then use `rr` to reload the file. You will
|
||||
still be at the same code block and can rerun it easily with `pp` as needed. This makes for a simple
|
||||
debug cycle. It also allows you to rerun individual troublesome blocks - as mentioned, in a large
|
||||
batch file this can be very useful (don't forget the `/debug` mode either).
|
||||
If there are tracebacks, fix them in the batch file, then use `rr` to reload the file. You will still be at the same code block and can rerun it easily with `pp` as needed. This makes for a simple debug cycle. It also allows you to rerun individual troublesome blocks - as mentioned, in a large batch file this can be very useful (don't forget the `/debug` mode either).
|
||||
|
||||
Use `nn` and `bb` (next and back) to step through the file; e.g. `nn 12` will jump 12 steps forward
|
||||
(without processing any blocks in between). All normal commands of Evennia should work too while
|
||||
working in interactive mode.
|
||||
Use `nn` and `bb` (next and back) to step through the file; e.g. `nn 12` will jump 12 steps forward (without processing any blocks in between). All normal commands of Evennia should work too while working in interactive mode.
|
||||
|
||||
## Limitations and Caveats
|
||||
|
||||
The batch-code processor is by far the most flexible way to build a world in Evennia. There are
|
||||
however some caveats you need to keep in mind.
|
||||
The batch-code processor is by far the most flexible way to build a world in Evennia. There are however some caveats you need to keep in mind.
|
||||
|
||||
### Safety
|
||||
Or rather the lack of it. There is a reason only *superusers* are allowed to run the batch-code
|
||||
|
|
@ -180,23 +131,12 @@ command processor is much safer since the user running it is still 'inside' the
|
|||
really do anything outside what the game commands allow them to.
|
||||
|
||||
### No communication between code blocks
|
||||
Global variables won't work in code batch files, each block is executed as stand-alone environments.
|
||||
`#HEADER` blocks are literally pasted on top of each `#CODE` block so updating some header-variable
|
||||
in your block will not make that change available in another block. Whereas a python execution
|
||||
limitation, allowing this would also lead to very hard-to-debug code when using the interactive mode
|
||||
- this would be a classical example of "spaghetti code".
|
||||
Global variables won't work in code batch files, each block is executed as stand-alone environments. `#HEADER` blocks are literally pasted on top of each `#CODE` block so updating some header-variable in your block will not make that change available in another block. Whereas a python execution limitation, allowing this would also lead to very hard-to-debug code when using the interactive mode - this would be a classical example of "spaghetti code".
|
||||
|
||||
The main practical issue with this is when building e.g. a room in one code block and later want to
|
||||
connect that room with a room you built in the current block. There are two ways to do this:
|
||||
The main practical issue with this is when building e.g. a room in one code block and later want to connect that room with a room you built in the current block. There are two ways to do this:
|
||||
|
||||
- Perform a database search for the name of the room you created (since you cannot know in advance
|
||||
which dbref it got assigned). The problem is that a name may not be unique (you may have a lot of "A
|
||||
dark forest" rooms). There is an easy way to handle this though - use [Tags](./Tags.md) or *Aliases*. You
|
||||
can assign any number of tags and/or aliases to any object. Make sure that one of those tags or
|
||||
aliases is unique to the room (like "room56") and you will henceforth be able to always uniquely
|
||||
search and find it later.
|
||||
- Use the `caller` global property as an inter-block storage. For example, you could have a
|
||||
dictionary of room references in an `ndb`:
|
||||
- Perform a database search for the name of the room you created (since you cannot know in advance which dbref it got assigned). The problem is that a name may not be unique (you may have a lot of "A dark forest" rooms). There is an easy way to handle this though - use [Tags](./Tags.md) or *Aliases*. You can assign any number of tags and/or aliases to any object. Make sure that one of those tags or aliases is unique to the room (like "room56") and you will henceforth be able to always uniquely search and find it later.
|
||||
- Use the `caller` global property as an inter-block storage. For example, you could have a dictionary of room references in an `ndb`:
|
||||
```python
|
||||
#HEADER
|
||||
if caller.ndb.all_rooms is None:
|
||||
|
|
@ -211,18 +151,13 @@ dictionary of room references in an `ndb`:
|
|||
# in another node we want to access the castle
|
||||
castle = caller.ndb.all_rooms.get("castle")
|
||||
```
|
||||
Note how we check in `#HEADER` if `caller.ndb.all_rooms` doesn't already exist before creating the
|
||||
dict. Remember that `#HEADER` is copied in front of every `#CODE` block. Without that `if` statement
|
||||
Note how we check in `#HEADER` if `caller.ndb.all_rooms` doesn't already exist before creating the dict. Remember that `#HEADER` is copied in front of every `#CODE` block. Without that `if` statement
|
||||
we'd be wiping the dict every block!
|
||||
|
||||
### Don't treat a batchcode file like any Python file
|
||||
Despite being a valid Python file, a batchcode file should *only* be run by the batchcode processor.
|
||||
You should not do things like define Typeclasses or Commands in them, or import them into other
|
||||
code. Importing a module in Python will execute base level of the module, which in the case of your
|
||||
average batchcode file could mean creating a lot of new objects every time.
|
||||
|
||||
Despite being a valid Python file, a batchcode file should *only* be run by the batchcode processor. You should not do things like define Typeclasses or Commands in them, or import them into other code. Importing a module in Python will execute base level of the module, which in the case of your average batchcode file could mean creating a lot of new objects every time.
|
||||
|
||||
### Don't let code rely on the batch-file's real file path
|
||||
|
||||
When you import things into your batchcode file, don't use relative imports but always import with
|
||||
paths starting from the root of your game directory or evennia library. Code that relies on the
|
||||
batch file's "actual" location *will fail*. Batch code files are read as text and the strings
|
||||
executed. When the code runs it has no knowledge of what file those strings where once a part of.
|
||||
When you import things into your batchcode file, don't use relative imports but always import with paths starting from the root of your game directory or evennia library. Code that relies on the batch file's "actual" location *will fail*. Batch code files are read as text and the strings executed. When the code runs it has no knowledge of what file those strings where once a part of.
|
||||
|
|
@ -5,11 +5,9 @@ For an introduction and motivation to using batch processors, see [here](./Batch
|
|||
page describes the Batch-*command* processor. The Batch-*code* one is covered [here](Batch-Code-
|
||||
Processor).
|
||||
|
||||
## Basic Usage
|
||||
|
||||
The batch-command processor is a superuser-only function, invoked by
|
||||
|
||||
> @batchcommand path.to.batchcmdfile
|
||||
> batchcommand path.to.batchcmdfile
|
||||
|
||||
Where `path.to.batchcmdfile` is the path to a *batch-command file* with the "`.ev`" file ending.
|
||||
This path is given like a python path relative to a folder you define to hold your batch files, set
|
||||
|
|
@ -17,7 +15,7 @@ with `BATCH_IMPORT_PATH` in your settings. Default folder is (assuming your game
|
|||
folder) `mygame/world`. So if you want to run the example batch file in
|
||||
`mygame/world/batch_cmds.ev`, you could use
|
||||
|
||||
> @batchcommand batch_cmds
|
||||
> batchcommand batch_cmds
|
||||
|
||||
A batch-command file contains a list of Evennia in-game commands separated by comments. The
|
||||
processor will run the batch file from beginning to end. Note that *it will not stop if commands in
|
||||
|
|
@ -32,23 +30,12 @@ them in-game, except you have more freedom with line breaks.
|
|||
|
||||
Here are the rules of syntax of an `*.ev` file. You'll find it's really, really simple:
|
||||
|
||||
- All lines having the `#` (hash)-symbol *as the first one on the line* are considered *comments*.
|
||||
All non-comment lines are treated as a command and/or their arguments.
|
||||
- Comment lines have an actual function -- they mark the *end of the previous command definition*.
|
||||
So never put two commands directly after one another in the file - separate them with a comment, or
|
||||
the second of the two will be considered an argument to the first one. Besides, using plenty of
|
||||
comments is good practice anyway.
|
||||
- A line that starts with the word `#INSERT` is a comment line but also signifies a special
|
||||
instruction. The syntax is `#INSERT <path.batchfile>` and tries to import a given batch-cmd file
|
||||
into this one. The inserted batch file (file ending `.ev`) will run normally from the point of the
|
||||
`#INSERT` instruction.
|
||||
- Extra whitespace in a command definition is *ignored*. - A completely empty line translates in to
|
||||
a line break in texts. Two empty lines thus means a new paragraph (this is obviously only relevant
|
||||
for commands accepting such formatting, such as the `@desc` command).
|
||||
- All lines having the `#` (hash)-symbol *as the first one on the line* are considered *comments*. All non-comment lines are treated as a command and/or their arguments.
|
||||
- Comment lines have an actual function -- they mark the *end of the previous command definition*. So never put two commands directly after one another in the file - separate them with a comment, or the second of the two will be considered an argument to the first one. Besides, using plenty of comments is good practice anyway.
|
||||
- A line that starts with the word `#INSERT` is a comment line but also signifies a special instruction. The syntax is `#INSERT <path.batchfile>` and tries to import a given batch-cmd file into this one. The inserted batch file (file ending `.ev`) will run normally from the point of the `#INSERT` instruction.
|
||||
- Extra whitespace in a command definition is *ignored*. - A completely empty line translates in to a line break in texts. Two empty lines thus means a new paragraph (this is obviously only relevant for commands accepting such formatting, such as the `@desc` command).
|
||||
- The very last command in the file is not required to end with a comment.
|
||||
- You *cannot* nest another `@batchcommand` statement into your batch file. If you want to link many
|
||||
batch-files together, use the `#INSERT` batch instruction instead. You also cannot launch the
|
||||
`@batchcode` command from your batch file, the two batch processors are not compatible.
|
||||
- You *cannot* nest another `batchcommand` statement into your batch file. If you want to link many batch-files together, use the `#INSERT` batch instruction instead. You also cannot launch the `batchcode` command from your batch file, the two batch processors are not compatible.
|
||||
|
||||
Below is a version of the example file found in `evennia/contrib/tutorial_examples/batch_cmds.ev`.
|
||||
|
||||
|
|
@ -97,21 +84,15 @@ Below is a version of the example file found in `evennia/contrib/tutorial_exampl
|
|||
|
||||
To test this, run `@batchcommand` on the file:
|
||||
|
||||
> @batchcommand contrib.tutorial_examples.batch_cmds
|
||||
> batchcommand contrib.tutorial_examples.batch_cmds
|
||||
|
||||
A button will be created, described and dropped in Limbo. All commands will be executed by the user
|
||||
calling the command.
|
||||
A button will be created, described and dropped in Limbo. All commands will be executed by the user calling the command.
|
||||
|
||||
> Note that if you interact with the button, you might find that its description changes, loosing
|
||||
your custom-set description above. This is just the way this particular object works.
|
||||
> Note that if you interact with the button, you might find that its description changes, loosing your custom-set description above. This is just the way this particular object works.
|
||||
|
||||
## Interactive mode
|
||||
|
||||
Interactive mode allows you to more step-wise control over how the batch file is executed. This is
|
||||
useful for debugging and also if you have a large batch file and is only updating a small part of it
|
||||
-- running the entire file again would be a waste of time (and in the case of `@create`-ing objects
|
||||
you would to end up with multiple copies of same-named objects, for example). Use `@batchcommand`
|
||||
with the `/interactive` flag to enter interactive mode.
|
||||
Interactive mode allows you to more step-wise control over how the batch file is executed. This is useful for debugging and also if you have a large batch file and is only updating a small part of it -- running the entire file again would be a waste of time (and in the case of `create`-ing objects you would to end up with multiple copies of same-named objects, for example). Use `batchcommand` with the `/interactive` flag to enter interactive mode.
|
||||
|
||||
> @batchcommand/interactive tutorial_examples.batch_cmds
|
||||
|
||||
|
|
@ -119,64 +100,31 @@ You will see this:
|
|||
|
||||
01/04: @create button:tutorial_examples.red_button.RedButton (hh for help)
|
||||
|
||||
This shows that you are on the `@create` command, the first out of only four commands in this batch
|
||||
file. Observe that the command `@create` has *not* been actually processed at this point!
|
||||
This shows that you are on the `@create` command, the first out of only four commands in this batch file. Observe that the command `@create` has *not* been actually processed at this point!
|
||||
|
||||
To take a look at the full command you are about to run, use `ll` (a batch-processor version of
|
||||
`look`). Use `pp` to actually process the current command (this will actually `@create` the button)
|
||||
-- and make sure it worked as planned. Use `nn` (next) to go to the next command. Use `hh` for a
|
||||
list of commands.
|
||||
`look`). Use `pp` to actually process the current command (this will actually `@create` the button) -- and make sure it worked as planned. Use `nn` (next) to go to the next command. Use `hh` for a list of commands.
|
||||
|
||||
If there are errors, fix them in the batch file, then use `rr` to reload the file. You will still be
|
||||
at the same command and can rerun it easily with `pp` as needed. This makes for a simple debug
|
||||
cycle. It also allows you to rerun individual troublesome commands - as mentioned, in a large batch
|
||||
file this can be very useful. Do note that in many cases, commands depend on the previous ones (e.g.
|
||||
if `@create` in the example above had failed, the following commands would have had nothing to
|
||||
operate on).
|
||||
If there are errors, fix them in the batch file, then use `rr` to reload the file. You will still be at the same command and can rerun it easily with `pp` as needed. This makes for a simple debug cycle. It also allows you to rerun individual troublesome commands - as mentioned, in a large batch file this can be very useful. Do note that in many cases, commands depend on the previous ones (e.g. if `create` in the example above had failed, the following commands would have had nothing to operate on).
|
||||
|
||||
Use `nn` and `bb` (next and back) to step through the file; e.g. `nn 12` will jump 12 steps forward
|
||||
(without processing any command in between). All normal commands of Evennia should work too while
|
||||
working in interactive mode.
|
||||
Use `nn` and `bb` (next and back) to step through the file; e.g. `nn 12` will jump 12 steps forward (without processing any command in between). All normal commands of Evennia should work too while working in interactive mode.
|
||||
|
||||
## Limitations and Caveats
|
||||
|
||||
The batch-command processor is great for automating smaller builds or for testing new commands and
|
||||
objects repeatedly without having to write so much. There are several caveats you have to be aware
|
||||
of when using the batch-command processor for building larger, complex worlds though.
|
||||
The batch-command processor is great for automating smaller builds or for testing new commands and objects repeatedly without having to write so much. There are several caveats you have to be aware of when using the batch-command processor for building larger, complex worlds though.
|
||||
|
||||
The main issue is that when you run a batch-command script you (*you*, as in your superuser
|
||||
character) are actually moving around in the game creating and building rooms in sequence, just as
|
||||
if you had been entering those commands manually, one by one. You have to take this into account
|
||||
when creating the file, so that you can 'walk' (or teleport) to the right places in order.
|
||||
character) are actually moving around in the game creating and building rooms in sequence, just as if you had been entering those commands manually, one by one. You have to take this into account when creating the file, so that you can 'walk' (or teleport) to the right places in order.
|
||||
|
||||
This also means there are several pitfalls when designing and adding certain types of objects. Here
|
||||
are some examples:
|
||||
This also means there are several pitfalls when designing and adding certain types of objects. Here are some examples:
|
||||
|
||||
- *Rooms that change your [Command Set](./Command-Sets.md)*: Imagine that you build a 'dark' room, which
|
||||
severely limits the cmdsets of those entering it (maybe you have to find the light switch to
|
||||
proceed). In your batch script you would create this room, then teleport to it - and promptly be
|
||||
shifted into the dark state where none of your normal build commands work ...
|
||||
- *Auto-teleportation*: Rooms that automatically teleport those that enter them to another place
|
||||
(like a trap room, for example). You would be teleported away too.
|
||||
- *Mobiles*: If you add aggressive mobs, they might attack you, drawing you into combat. If they
|
||||
have AI they might even follow you around when building - or they might move away from you before
|
||||
you've had time to finish describing and equipping them!
|
||||
- *Rooms that change your [Command Set](./Command-Sets.md)*: Imagine that you build a 'dark' room, which severely limits the cmdsets of those entering it (maybe you have to find the light switch to proceed). In your batch script you would create this room, then teleport to it - and promptly be shifted into the dark state where none of your normal build commands work ...
|
||||
- *Auto-teleportation*: Rooms that automatically teleport those that enter them to another place (like a trap room, for example). You would be teleported away too.
|
||||
- *Mobiles*: If you add aggressive mobs, they might attack you, drawing you into combat. If they have AI they might even follow you around when building - or they might move away from you before you've had time to finish describing and equipping them!
|
||||
|
||||
The solution to all these is to plan ahead. Make sure that superusers are never affected by whatever
|
||||
effects are in play. Add an on/off switch to objects and make sure it's always set to *off* upon
|
||||
creation. It's all doable, one just needs to keep it in mind.
|
||||
The solution to all these is to plan ahead. Make sure that superusers are never affected by whatever effects are in play. Add an on/off switch to objects and make sure it's always set to *off* upon creation. It's all doable, one just needs to keep it in mind.
|
||||
|
||||
## Assorted notes
|
||||
## Editor highlighting for .ev files
|
||||
|
||||
The fact that you build as 'yourself' can also be considered an advantage however, should you ever
|
||||
decide to change the default command to allow others than superusers to call the processor. Since
|
||||
normal access-checks are still performed, a malevolent builder with access to the processor should
|
||||
not be able to do all that much damage (this is the main drawback of the [Batch Code
|
||||
Processor](./Batch-Code-Processor.md))
|
||||
|
||||
- [GNU Emacs](https://www.gnu.org/software/emacs/) users might find it interesting to use emacs'
|
||||
*evennia mode*. This is an Emacs major mode found in `evennia/utils/evennia-mode.el`. It offers
|
||||
correct syntax highlighting and indentation with `<tab>` when editing `.ev` files in Emacs. See the
|
||||
header of that file for installation instructions.
|
||||
- [VIM](https://www.vim.org/) users can use amfl's [vim-evennia](https://github.com/amfl/vim-evennia)
|
||||
mode instead, see its readme for install instructions.
|
||||
- [GNU Emacs](https://www.gnu.org/software/emacs/) users might find it interesting to use emacs' *evennia mode*. This is an Emacs major mode found in `evennia/utils/evennia-mode.el`. It offers correct syntax highlighting and indentation with `<tab>` when editing `.ev` files in Emacs. See the header of that file for installation instructions.
|
||||
- [VIM](https://www.vim.org/) users can use amfl's [vim-evennia](https://github.com/amfl/vim-evennia) mode instead, see its readme for install instructions.
|
||||
|
|
@ -1,33 +1,23 @@
|
|||
# Batch Processors
|
||||
|
||||
```{toctree}
|
||||
:maxdepth: 2
|
||||
|
||||
Building a game world is a lot of work, especially when starting out. Rooms should be created,
|
||||
descriptions have to be written, objects must be detailed and placed in their proper places. In many
|
||||
Batch-Command-Processor.md
|
||||
Batch-Code-Processor.md
|
||||
```
|
||||
|
||||
Building a game world is a lot of work, especially when starting out. Rooms should be created, descriptions have to be written, objects must be detailed and placed in their proper places. In many
|
||||
traditional MUD setups you had to do all this online, line by line, over a telnet session.
|
||||
|
||||
Evennia already moves away from much of this by shifting the main coding work to external Python
|
||||
modules. But also building would be helped if one could do some or all of it externally. Enter
|
||||
Evennia's *batch processors* (there are two of them). The processors allows you, as a game admin, to
|
||||
build your game completely offline in normal text files (*batch files*) that the processors
|
||||
understands. Then, when you are ready, you use the processors to read it all into Evennia (and into
|
||||
the database) in one go.
|
||||
|
||||
You can of course still build completely online should you want to - this is certainly the easiest
|
||||
way to go when learning and for small build projects. But for major building work, the advantages of
|
||||
using the batch-processors are many:
|
||||
- It's hard to compete with the comfort of a modern desktop text editor; Compared to a traditional
|
||||
MUD line input, you can get much better overview and many more features. Also, accidentally pressing
|
||||
Return won't immediately commit things to the database.
|
||||
- You might run external spell checkers on your batch files. In the case of one of the batch-
|
||||
processors (the one that deals with Python code), you could also run external debuggers and code
|
||||
analyzers on your file to catch problems before feeding it to Evennia.
|
||||
- The batch files (as long as you keep them) are records of your work. They make a natural starting
|
||||
point for quickly re-building your world should you ever decide to start over.
|
||||
- If you are an Evennia developer, using a batch file is a fast way to setup a test-game after
|
||||
having reset the database.
|
||||
- The batch files might come in useful should you ever decide to distribute all or part of your
|
||||
world to others.
|
||||
Evennia already moves away from much of this by shifting the main coding work to external Python modules. But also building would be helped if one could do some or all of it externally. Enter Evennia's *batch processors* (there are two of them). The processors allows you, as a game admin, to build your game completely offline in normal text files (*batch files*) that the processors understands. Then, when you are ready, you use the processors to read it all into Evennia (and into the database) in one go.
|
||||
|
||||
You can of course still build completely online should you want to - this is certainly the easiest way to go when learning and for small build projects. But for major building work, the advantages of using the batch-processors are many:
|
||||
- It's hard to compete with the comfort of a modern desktop text editor; Compared to a traditional MUD line input, you can get much better overview and many more features. Also, accidentally pressing Return won't immediately commit things to the database.
|
||||
- You might run external spell checkers on your batch files. In the case of one of the batch- processors (the one that deals with Python code), you could also run external debuggers and code analyzers on your file to catch problems before feeding it to Evennia.
|
||||
- The batch files (as long as you keep them) are records of your work. They make a natural starting point for quickly re-building your world should you ever decide to start over.
|
||||
- If you are an Evennia developer, using a batch file is a fast way to setup a test-game after having reset the database.
|
||||
- The batch files might come in useful should you ever decide to distribute all or part of your world to others.
|
||||
|
||||
There are two batch processors, the Batch-*command* processor and the Batch-*code* processor. The
|
||||
first one is the simpler of the two. It doesn't require any programming knowledge - you basically
|
||||
|
|
@ -35,48 +25,23 @@ just list in-game commands in a text file. The code-processor on the other hand
|
|||
powerful but also more complex - it lets you use Evennia's API to code your world in full-fledged
|
||||
Python code.
|
||||
|
||||
- The [Batch Command Processor](./Batch-Command-Processor.md)
|
||||
- The [Batch Code Processor](./Batch-Code-Processor.md)
|
||||
|
||||
If you plan to use international characters in your batchfiles you are wise to read about *file
|
||||
encodings* below.
|
||||
|
||||
## A note on File Encodings
|
||||
|
||||
As mentioned, both the processors take text files as input and then proceed to process them. As long
|
||||
as you stick to the standard [ASCII](https://en.wikipedia.org/wiki/Ascii) character set (which means
|
||||
the normal English characters, basically) you should not have to worry much about this section.
|
||||
As mentioned, both the processors take text files as input and then proceed to process them. As long as you stick to the standard [ASCII](https://en.wikipedia.org/wiki/Ascii) character set (which means the normal English characters, basically) you should not have to worry much about this section.
|
||||
|
||||
Many languages however use characters outside the simple `ASCII` table. Common examples are various
|
||||
apostrophes and umlauts but also completely different symbols like those of the greek or cyrillic
|
||||
alphabets.
|
||||
Many languages however use characters outside the simple `ASCII` table. Common examples are various apostrophes and umlauts but also completely different symbols like those of the greek or cyrillic alphabets.
|
||||
|
||||
First, we should make it clear that Evennia itself handles international characters just fine. It
|
||||
(and Django) uses [unicode](https://en.wikipedia.org/wiki/Unicode) strings internally.
|
||||
First, we should make it clear that Evennia itself handles international characters just fine. It (and Django) uses [unicode](https://en.wikipedia.org/wiki/Unicode) strings internally.
|
||||
|
||||
The problem is that when reading a text file like the batchfile, we need to know how to decode the
|
||||
byte-data stored therein to universal unicode. That means we need an *encoding* (a mapping) for how
|
||||
the file stores its data. There are many, many byte-encodings used around the world, with opaque
|
||||
names such as `Latin-1`, `ISO-8859-3` or `ARMSCII-8` to pick just a few examples. Problem is that
|
||||
it's practially impossible to determine which encoding was used to save a file just by looking at it
|
||||
(it's just a bunch of bytes!). You have to *know*.
|
||||
The problem is that when reading a text file like the batchfile, we need to know how to decode the byte-data stored therein to universal unicode. That means we need an *encoding* (a mapping) for how the file stores its data. There are many, many byte-encodings used around the world, with opaque names such as `Latin-1`, `ISO-8859-3` or `ARMSCII-8` to pick just a few examples. Problem is that it's practially impossible to determine which encoding was used to save a file just by looking at it (it's just a bunch of bytes!). You have to *know*.
|
||||
|
||||
With this little introduction it should be clear that Evennia can't guess but has to *assume* an
|
||||
encoding when trying to load a batchfile. The text editor and Evennia must speak the same "language"
|
||||
so to speak. Evennia will by default first try the international `UTF-8` encoding, but you can have
|
||||
Evennia try any sequence of different encodings by customizing the `ENCODINGS` list in your settings
|
||||
file. Evennia will use the first encoding in the list that do not raise any errors. Only if none
|
||||
work will the server give up and return an error message.
|
||||
With this little introduction it should be clear that Evennia can't guess but has to *assume* an encoding when trying to load a batchfile. The text editor and Evennia must speak the same "language" so to speak. Evennia will by default first try the international `UTF-8` encoding, but you can have Evennia try any sequence of different encodings by customizing the `ENCODINGS` list in your settings file. Evennia will use the first encoding in the list that do not raise any errors. Only if none work will the server give up and return an error message.
|
||||
|
||||
You can often change the text editor encoding (this depends on your editor though), otherwise you
|
||||
need to add the editor's encoding to Evennia's `ENCODINGS` list. If you are unsure, write a test
|
||||
file with lots of non-ASCII letters in the editor of your choice, then import to make sure it works
|
||||
as it should.
|
||||
You can often change the text editor encoding (this depends on your editor though), otherwise you need to add the editor's encoding to Evennia's `ENCODINGS` list. If you are unsure, write a test file with lots of non-ASCII letters in the editor of your choice, then import to make sure it works as it should.
|
||||
|
||||
More help with encodings can be found in the entry [Text Encodings](../Concepts/Text-Encodings.md) and also in the
|
||||
Wikipedia article [here](https://en.wikipedia.org/wiki/Text_encodings).
|
||||
More help with encodings can be found in the entry [Text Encodings](../Concepts/Text-Encodings.md) and also in the Wikipedia article [here](https://en.wikipedia.org/wiki/Text_encodings).
|
||||
|
||||
**A footnote for the batch-code processor**: Just because *Evennia* can parse your file and your
|
||||
fancy special characters, doesn't mean that *Python* allows their use. Python syntax only allows
|
||||
international characters inside *strings*. In all other source code only `ASCII` set characters are
|
||||
fancy special characters, doesn't mean that *Python* allows their use. Python syntax only allows international characters inside *strings*. In all other source code only `ASCII` set characters are
|
||||
allowed.
|
||||
|
|
|
|||
|
|
@ -27,16 +27,14 @@ Channels can be used both for chats between [Accounts](./Accounts.md) and betwee
|
|||
|
||||
```
|
||||
|
||||
## Using channels in-game
|
||||
|
||||
In the default command set, channels are all handled via the mighty
|
||||
[channel
|
||||
command](evennia.commands.default.comms.CmdChannel), `channel` (or
|
||||
`chan`). By default, this command will assume all entities dealing with
|
||||
channels are `Accounts`.
|
||||
## Working with channels
|
||||
|
||||
### Viewing and joining channels
|
||||
|
||||
In the default command set, channels are all handled via the mighty [channel command](evennia.commands.default.comms.CmdChannel), `channel` (or `chan`). By default, this command will assume all entities dealing with channels are `Accounts`.
|
||||
|
||||
Viewing channels
|
||||
|
||||
channel - shows your subscriptions
|
||||
channel/all - shows all subs available to you
|
||||
channel/who - shows who subscribes to this channel
|
||||
|
|
@ -52,7 +50,7 @@ unsubscribing), you can mute it:
|
|||
channel/mute channelname
|
||||
channel/unmute channelname
|
||||
|
||||
### Chat on channels
|
||||
### Talk on channels
|
||||
|
||||
To speak on a channel, do
|
||||
|
||||
|
|
@ -141,8 +139,7 @@ Banning adds the user to the channels blacklist. This means they will not be
|
|||
able to _rejoin_ if you boot them. You will need to run `channel/boot` to
|
||||
actually kick them out.
|
||||
|
||||
See the [Channel command](evennia.commands.default.comms.CmdChannel) api
|
||||
docs (and in-game help) for more details.
|
||||
See the [Channel command](evennia.commands.default.comms.CmdChannel) api docs (and in-game help) for more details.
|
||||
|
||||
Admin-level users can also modify channel's [locks](./Locks.md):
|
||||
|
||||
|
|
@ -159,11 +156,7 @@ Channels use three lock-types by default:
|
|||
|
||||
#### Restricting channel administration
|
||||
|
||||
By default everyone can use the channel command ([evennia.commands.default.comms.CmdChannel](evennia.commands.default.comms.CmdChannel))
|
||||
to create channels and will then control the channels they created (to boot/ban
|
||||
people etc). If you as a developer does not want regular players to do this
|
||||
(perhaps you want only staff to be able to spawn new channels), you can
|
||||
override the `channel` command and change its `locks` property.
|
||||
By default everyone can use the channel command ([evennia.commands.default.comms.CmdChannel](evennia.commands.default.comms.CmdChannel)) to create channels and will then control the channels they created (to boot/ban people etc). If you as a developer does not want regular players to do this (perhaps you want only staff to be able to spawn new channels), you can override the `channel` command and change its `locks` property.
|
||||
|
||||
The default `help` command has the following `locks` property:
|
||||
|
||||
|
|
@ -203,19 +196,19 @@ channels you could override the `help` command and change the lockstring to:
|
|||
Add this custom command to your default cmdset and regular users wil now get an
|
||||
access-denied error when trying to use use these switches.
|
||||
|
||||
## Allowing Characters to use Channels
|
||||
## Using channels in code
|
||||
|
||||
The default `channel` command ([evennia.commands.default.comms.CmdChannel](evennia.commands.default.comms.CmdChannel))
|
||||
sits in the `Account` [command set](./Command-Sets.md). It is set up such that it will
|
||||
always operate on `Accounts`, even if you were to add it to the
|
||||
`CharacterCmdSet`.
|
||||
For most common changes, the default channel, the recipient hooks and possibly
|
||||
overriding the `channel` command will get you very far. But you can also tweak
|
||||
channels themselves.
|
||||
|
||||
It's a one-line change to make this command accept non-account callers. But for
|
||||
convenience we provide a version for Characters/Objects. Just import
|
||||
[evennia.commands.default.comms.CmdObjectChannel](evennia.commands.default.comms.CmdObjectChannel)
|
||||
and inherit from that instead.
|
||||
### Allowing Characters to use Channels
|
||||
|
||||
## Customizing channel output and behavior
|
||||
The default `channel` command ([evennia.commands.default.comms.CmdChannel](evennia.commands.default.comms.CmdChannel)) sits in the `Account` [command set](./Command-Sets.md). It is set up such that it will always operate on `Accounts`, even if you were to add it to the `CharacterCmdSet`.
|
||||
|
||||
It's a one-line change to make this command accept non-account callers. But for convenience we provide a version for Characters/Objects. Just import [evennia.commands.default.comms.CmdObjectChannel](evennia.commands.default.comms.CmdObjectChannel) and inherit from that instead.
|
||||
|
||||
### Customizing channel output and behavior
|
||||
|
||||
When distributing a message, the channel will call a series of hooks on itself
|
||||
and (more importantly) on each recipient. So you can customize things a lot by
|
||||
|
|
@ -243,21 +236,11 @@ Note that `Accounts` and `Objects` both have their have separate sets of hooks.
|
|||
So make sure you modify the set actually used by your subcribers (or both).
|
||||
Default channels all use `Account` subscribers.
|
||||
|
||||
## Channels in code
|
||||
### Channel class
|
||||
|
||||
For most common changes, the default channel, the recipient hooks and possibly
|
||||
overriding the `channel` command will get you very far. But you can also tweak
|
||||
channels themselves.
|
||||
Channels are [Typeclassed](./Typeclasses.md) entities. This means they are persistent in the database, can have [attributes](./Attributes.md) and [Tags](./Tags.md) and can be easily extended.
|
||||
|
||||
Channels are [Typeclassed](./Typeclasses.md) entities. This means they are
|
||||
persistent in the database, can have [attributes](./Attributes.md) and [Tags](./Tags.md)
|
||||
and can be easily extended.
|
||||
|
||||
To change which channel typeclass Evennia uses for default commands, change
|
||||
`settings.BASE_CHANNEL_TYPECLASS`. The base command class is
|
||||
[`evennia.comms.comms.DefaultChannel`](evennia.comms.comms.DefaultChannel).
|
||||
There is an empty child class in `mygame/typeclasses/channels.py`, same
|
||||
as for other typelass-bases.
|
||||
To change which channel typeclass Evennia uses for default commands, change `settings.BASE_CHANNEL_TYPECLASS`. The base command class is [`evennia.comms.comms.DefaultChannel`](evennia.comms.comms.DefaultChannel). There is an empty child class in `mygame/typeclasses/channels.py`, same as for other typelass-bases.
|
||||
|
||||
In code you create a new channel with `evennia.create_channel` or
|
||||
`Channel.create`:
|
||||
|
|
@ -294,11 +277,10 @@ In code you create a new channel with `evennia.create_channel` or
|
|||
The Channel's `.connect` method will accept both `Account` and `Object` subscribers
|
||||
and will handle them transparently.
|
||||
|
||||
The channel has many more hooks, both hooks shared with all typeclasses as well
|
||||
as special ones related to muting/banning etc. See the channel class for
|
||||
The channel has many more hooks, both hooks shared with all typeclasses as well as special ones related to muting/banning etc. See the channel class for
|
||||
details.
|
||||
|
||||
## Channel logging
|
||||
### Channel logging
|
||||
|
||||
```{versionchanged} 0.7
|
||||
|
||||
|
|
@ -309,11 +291,7 @@ details.
|
|||
Channels stopped supporting Msg and TmpMsg, using only log files.
|
||||
```
|
||||
|
||||
The channel messages are not stored in the database. A channel is instead
|
||||
always logged to a regular text log-file
|
||||
`mygame/server/logs/channel_<channelname>.log`. This is where `channels/history channelname`
|
||||
gets its data from. A channel's log will rotate when it grows too big, which
|
||||
thus also automatically limits the max amount of history a user can view with
|
||||
The channel messages are not stored in the database. A channel is instead always logged to a regular text log-file `mygame/server/logs/channel_<channelname>.log`. This is where `channels/history channelname` gets its data from. A channel's log will rotate when it grows too big, which thus also automatically limits the max amount of history a user can view with
|
||||
`/history`.
|
||||
|
||||
The log file name is set on the channel class as the `log_file` property. This
|
||||
|
|
@ -321,7 +299,6 @@ is a string that takes the formatting token `{channelname}` to be replaced with
|
|||
the (lower-case) name of the channel. By default the log is written to in the
|
||||
channel's `at_post_channel_msg` method.
|
||||
|
||||
|
||||
### Properties on Channels
|
||||
|
||||
Channels have all the standard properties of a Typeclassed entity (`key`,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
Evennia comes with many utilities to help with common coding tasks. Most are accessible directly
|
||||
from the flat API, otherwise you can find them in the `evennia/utils/` folder.
|
||||
|
||||
> This is just a small selection of the tools in `evennia/utils`. It's worth to browse [the directory](evennia.utils) and in particular the content of [evennia/utils/utils.py](evennia.utils.utils) directly to find more useful stuff.
|
||||
|
||||
## Searching
|
||||
|
||||
A common thing to do is to search for objects. There it's easiest to use the `search` method defined
|
||||
|
|
@ -13,13 +15,9 @@ on all objects. This will search for objects in the same location and inside the
|
|||
obj = self.search(objname)
|
||||
```
|
||||
|
||||
The most common time one needs to do this is inside a command body. `obj =
|
||||
self.caller.search(objname)` will search inside the caller's (typically, the character that typed
|
||||
the command) `.contents` (their "inventory") and `.location` (their "room").
|
||||
The most common time one needs to do this is inside a command body. `obj = self.caller.search(objname)` will search inside the caller's (typically, the character that typed the command) `.contents` (their "inventory") and `.location` (their "room").
|
||||
|
||||
Give the keyword `global_search=True` to extend search to encompass entire database. Aliases will
|
||||
also be matched by this search. You will find multiple examples of this functionality in the default
|
||||
command set.
|
||||
Give the keyword `global_search=True` to extend search to encompass entire database. Aliases will also be matched by this search. You will find multiple examples of this functionality in the default command set.
|
||||
|
||||
If you need to search for objects in a code module you can use the functions in
|
||||
`evennia.utils.search`. You can access these as shortcuts `evennia.search_*`.
|
||||
|
|
@ -29,42 +27,39 @@ If you need to search for objects in a code module you can use the functions in
|
|||
obj = search_object(objname)
|
||||
```
|
||||
|
||||
- [`evennia.search_account`](evennia.accounts.manager.AccountDBManager.search_account)
|
||||
- [`evennia.search_object`](evennia.objects.manager.ObjectDBManager.search_object)
|
||||
- [`evennia.search(object)_by_tag`](evennia.utils.search.search_tag)
|
||||
- [`evennia.search_script`](evennia.scripts.manager.ScriptDBManager.search_script)
|
||||
- [`evennia.search_channel`](evennia.comms.managers.ChannelDBManager.search_channel)
|
||||
- [`evennia.search_message`](evennia.comms.managers.MsgManager.search_message)
|
||||
- [`evennia.search_help`](evennia.help.manager.HelpEntryManager.search_help)
|
||||
- [evennia.search_account](evennia.accounts.manager.AccountDBManager.search_account)
|
||||
- [evennia.search_object](evennia.objects.manager.ObjectDBManager.search_object)
|
||||
- [evennia.search(object)_by_tag](evennia.utils.search.search_tag)
|
||||
- [evennia.search_script](evennia.scripts.manager.ScriptDBManager.search_script)
|
||||
- [evennia.search_channel](evennia.comms.managers.ChannelDBManager.search_channel)
|
||||
- [evennia.search_message](evennia.comms.managers.MsgManager.search_message)
|
||||
- [evennia.search_help](evennia.help.manager.HelpEntryManager.search_help)
|
||||
|
||||
Note that these latter methods will always return a `list` of results, even if the list has one or
|
||||
zero entries.
|
||||
Note that these latter methods will always return a `list` of results, even if the list has one or zero entries.
|
||||
|
||||
## Create
|
||||
|
||||
Apart from the in-game build commands (`@create` etc), you can also build all of Evennia's game
|
||||
entities directly in code (for example when defining new create commands).
|
||||
Apart from the in-game build commands (`@create` etc), you can also build all of Evennia's game entities directly in code (for example when defining new create commands).
|
||||
|
||||
```python
|
||||
import evennia
|
||||
|
||||
myobj = evennia.create_objects("game.gamesrc.objects.myobj.MyObj", key="MyObj")
|
||||
```
|
||||
|
||||
- [`evennia.create_account`](evennia.utils.create.create_account)
|
||||
- [`evennia.create_object`](evennia.utils.create.create_object)
|
||||
- [`evennia.create_script`](evennia.utils.create.create_script)
|
||||
- [`evennia.create_channel`](evennia.utils.create.create_channel)
|
||||
- [`evennia.create_help_entry`](evennia.utils.create.create_help_entry)
|
||||
- [`evennia.create_message`](evennia.utils.create.create_message)
|
||||
- [evennia.create_account](evennia.utils.create.create_account)
|
||||
- [evennia.create_object](evennia.utils.create.create_object)
|
||||
- [evennia.create_script](evennia.utils.create.create_script)
|
||||
- [evennia.create_channel](evennia.utils.create.create_channel)
|
||||
- [evennia.create_help_entry](evennia.utils.create.create_help_entry)
|
||||
- [evennia.create_message](evennia.utils.create.create_message)
|
||||
|
||||
Each of these create-functions have a host of arguments to further customize the created entity. See
|
||||
`evennia/utils/create.py` for more information.
|
||||
Each of these create-functions have a host of arguments to further customize the created entity. See `evennia/utils/create.py` for more information.
|
||||
|
||||
## Logging
|
||||
|
||||
Normally you can use Python `print` statements to see output to the terminal/log. The `print`
|
||||
statement should only be used for debugging though. For producion output, use the `logger` which
|
||||
will create proper logs either to terminal or to file.
|
||||
statement should only be used for debugging though. For producion output, use the `logger` which will create proper logs either to terminal or to file.
|
||||
|
||||
```python
|
||||
from evennia import logger
|
||||
|
|
@ -75,8 +70,7 @@ will create proper logs either to terminal or to file.
|
|||
logger.log_dep("This feature is deprecated")
|
||||
```
|
||||
|
||||
There is a special log-message type, `log_trace()` that is intended to be called from inside a
|
||||
traceback - this can be very useful for relaying the traceback message back to log without having it
|
||||
There is a special log-message type, `log_trace()` that is intended to be called from inside a traceback - this can be very useful for relaying the traceback message back to log without having it
|
||||
kill the server.
|
||||
|
||||
```python
|
||||
|
|
@ -86,25 +80,21 @@ kill the server.
|
|||
logger.log_trace("This text will show beneath the traceback itself.")
|
||||
```
|
||||
|
||||
The `log_file` logger, finally, is a very useful logger for outputting arbitrary log messages. This
|
||||
is a heavily optimized asynchronous log mechanism using
|
||||
[threads](https://en.wikipedia.org/wiki/Thread_%28computing%29) to avoid overhead. You should be
|
||||
able to use it for very heavy custom logging without fearing disk-write delays.
|
||||
The `log_file` logger, finally, is a very useful logger for outputting arbitrary log messages. This is a heavily optimized asynchronous log mechanism using [threads](https://en.wikipedia.org/wiki/Thread_%28computing%29) to avoid overhead. You should be able to use it for very heavy custom logging without fearing disk-write delays.
|
||||
|
||||
```python
|
||||
logger.log_file(message, filename="mylog.log")
|
||||
```
|
||||
|
||||
If not an absolute path is given, the log file will appear in the `mygame/server/logs/` directory.
|
||||
If the file already exists, it will be appended to. Timestamps on the same format as the normal
|
||||
Evennia logs will be automatically added to each entry. If a filename is not specified, output will
|
||||
be written to a file `game/logs/game.log`.
|
||||
If not an absolute path is given, the log file will appear in the `mygame/server/logs/` directory. If the file already exists, it will be appended to. Timestamps on the same format as the normal Evennia logs will be automatically added to each entry. If a filename is not specified, output will be written to a file `game/logs/game.log`.
|
||||
|
||||
See also the [Debugging](../Coding/Debugging.md) documentation for help with finding elusive bugs.
|
||||
|
||||
## Time Utilities
|
||||
|
||||
### Game time
|
||||
|
||||
Evennia tracks the current server time. You can access this time via the `evennia.gametime`
|
||||
shortcut:
|
||||
Evennia tracks the current server time. You can access this time via the `evennia.gametime` shortcut:
|
||||
|
||||
```python
|
||||
from evennia import gametime
|
||||
|
|
@ -131,13 +121,8 @@ gametime.reset_gametime()
|
|||
|
||||
```
|
||||
|
||||
The setting `TIME_FACTOR` determines how fast/slow in-game time runs compared to the real world. The
|
||||
setting `TIME_GAME_EPOCH` sets the starting game epoch (in seconds). The functions from the
|
||||
`gametime` module all return their times in seconds. You can convert this to whatever units of time
|
||||
you desire for your game. You can use the `@time` command to view the server time info.
|
||||
|
||||
You can also *schedule* things to happen at specific in-game times using the
|
||||
[gametime.schedule](evennia.utils.gametime.schedule) function:
|
||||
The setting `TIME_FACTOR` determines how fast/slow in-game time runs compared to the real world. The setting `TIME_GAME_EPOCH` sets the starting game epoch (in seconds). The functions from the `gametime` module all return their times in seconds. You can convert this to whatever units of time you desire for your game. You can use the `@time` command to view the server time info.
|
||||
You can also *schedule* things to happen at specific in-game times using the [gametime.schedule](evennia.utils.gametime.schedule) function:
|
||||
|
||||
```python
|
||||
import evennia
|
||||
|
|
@ -151,9 +136,7 @@ gametime.schedule(church_clock, hour=2)
|
|||
|
||||
### utils.time_format()
|
||||
|
||||
This function takes a number of seconds as input (e.g. from the `gametime` module above) and
|
||||
converts it to a nice text output in days, hours etc. It's useful when you want to show how old
|
||||
something is. It converts to four different styles of output using the *style* keyword:
|
||||
This function takes a number of seconds as input (e.g. from the `gametime` module above) and converts it to a nice text output in days, hours etc. It's useful when you want to show how old something is. It converts to four different styles of output using the *style* keyword:
|
||||
|
||||
- style 0 - `5d:45m:12s` (standard colon output)
|
||||
- style 1 - `5d` (shows only the longest time unit)
|
||||
|
|
@ -162,6 +145,8 @@ something is. It converts to four different styles of output using the *style* k
|
|||
|
||||
### utils.delay()
|
||||
|
||||
This allows for making a delayed call.
|
||||
|
||||
```python
|
||||
from evennia import utils
|
||||
|
||||
|
|
@ -169,44 +154,23 @@ def _callback(obj, text):
|
|||
obj.msg(text)
|
||||
|
||||
# wait 10 seconds before sending "Echo!" to obj (which we assume is defined)
|
||||
deferred = utils.delay(10, _callback, obj, "Echo!", persistent=False)
|
||||
utils.delay(10, _callback, obj, "Echo!", persistent=False)
|
||||
|
||||
# code here will run immediately, not waiting for the delay to fire!
|
||||
|
||||
```
|
||||
|
||||
This creates an asynchronous delayed call. It will fire the given callback function after the given
|
||||
number of seconds. This is a very light wrapper over a Twisted
|
||||
[Deferred](https://twistedmatrix.com/documents/current/core/howto/defer.html). Normally this is run
|
||||
non-persistently, which means that if the server is `@reload`ed before the delay is over, the
|
||||
callback will never run (the server forgets it). If setting `persistent` to True, the delay will be
|
||||
stored in the database and survive a `@reload` - but for this to work it is susceptible to the same
|
||||
limitations incurred when saving to an [Attribute](./Attributes.md).
|
||||
See [The Asynchronous process](../Concepts/Async-Process.md#delay) for more information.
|
||||
|
||||
The `deferred` return object can usually be ignored, but calling its `.cancel()` method will abort
|
||||
the delay prematurely.
|
||||
## Finding Classes
|
||||
|
||||
`utils.delay` is the lightest form of delayed call in Evennia. For other way to create time-bound
|
||||
tasks, see the [TickerHandler](./TickerHandler.md) and [Scripts](./Scripts.md).
|
||||
|
||||
> Note that many delayed effects can be achieved without any need for an active timer. For example
|
||||
if you have a trait that should recover a point every 5 seconds you might just need its value when
|
||||
it's needed, but checking the current time and calculating on the fly what value it should have.
|
||||
|
||||
## Object Classes
|
||||
### utils.inherits_from()
|
||||
|
||||
This useful function takes two arguments - an object to check and a parent. It returns `True` if
|
||||
object inherits from parent *at any distance* (as opposed to Python's in-built `is_instance()` that
|
||||
This useful function takes two arguments - an object to check and a parent. It returns `True` if object inherits from parent *at any distance* (as opposed to Python's in-built `is_instance()` that
|
||||
will only catch immediate dependence). This function also accepts as input any combination of
|
||||
classes, instances or python-paths-to-classes.
|
||||
|
||||
Note that Python code should usually work with [duck
|
||||
typing](https://en.wikipedia.org/wiki/Duck_typing). But in Evennia's case it can sometimes be useful
|
||||
to check if an object inherits from a given [Typeclass](./Typeclasses.md) as a way of identification. Say
|
||||
for example that we have a typeclass *Animal*. This has a subclass *Felines* which in turn has a
|
||||
subclass *HouseCat*. Maybe there are a bunch of other animal types too, like horses and dogs. Using
|
||||
`inherits_from` will allow you to check for all animals in one go:
|
||||
Note that Python code should usually work with [duck typing](https://en.wikipedia.org/wiki/Duck_typing). But in Evennia's case it can sometimes be useful to check if an object inherits from a given [Typeclass](./Typeclasses.md) as a way of identification. Say for example that we have a typeclass *Animal*. This has a subclass *Felines* which in turn has a subclass *HouseCat*. Maybe there are a bunch of other animal types too, like horses and dogs. Using `inherits_from` will allow you to check for all animals in one go:
|
||||
|
||||
```python
|
||||
from evennia import utils
|
||||
|
|
@ -214,8 +178,6 @@ subclass *HouseCat*. Maybe there are a bunch of other animal types too, like hor
|
|||
obj.msg("The bouncer stops you in the door. He says: 'No talking animals allowed.'")
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Text utilities
|
||||
|
||||
In a text game, you are naturally doing a lot of work shuffling text back and forth. Here is a *non-
|
||||
|
|
@ -224,8 +186,7 @@ If nothing else it can be good to look here before starting to develop a solutio
|
|||
|
||||
### utils.fill()
|
||||
|
||||
This flood-fills a text to a given width (shuffles the words to make each line evenly wide). It also
|
||||
indents as needed.
|
||||
This flood-fills a text to a given width (shuffles the words to make each line evenly wide). It also indents as needed.
|
||||
|
||||
```python
|
||||
outtxt = fill(intxt, width=78, indent=4)
|
||||
|
|
@ -244,11 +205,7 @@ can be useful in listings when showing multiple lines would mess up things.
|
|||
|
||||
### utils.dedent()
|
||||
|
||||
This solves what may at first glance appear to be a trivial problem with text - removing
|
||||
indentations. It is used to shift entire paragraphs to the left, without disturbing any further
|
||||
formatting they may have. A common case for this is when using Python triple-quoted strings in code
|
||||
- they will retain whichever indentation they have in the code, and to make easily-readable source
|
||||
code one usually don't want to shift the string to the left edge.
|
||||
This solves what may at first glance appear to be a trivial problem with text - removing indentations. It is used to shift entire paragraphs to the left, without disturbing any further formatting they may have. A common case for this is when using Python triple-quoted strings in code - they will retain whichever indentation they have in the code, and to make easily-readable source code one usually don't want to shift the string to the left edge.
|
||||
|
||||
```python
|
||||
#python code is entered at a given indentation
|
||||
|
|
@ -267,31 +224,6 @@ help entries).
|
|||
|
||||
### to_str() and to_bytes()
|
||||
|
||||
Evennia supplies two utility functions for converting text to the correct
|
||||
encodings. `to_str()` and `to_bytes()`. Unless you are adding a custom protocol and
|
||||
need to send byte-data over the wire, `to_str` is the only one you'll need.
|
||||
Evennia supplies two utility functions for converting text to the correct encodings. `to_str()` and `to_bytes()`. Unless you are adding a custom protocol and need to send byte-data over the wire, `to_str` is the only one you'll need.
|
||||
|
||||
The difference from Python's in-built `str()` and `bytes()` operators are that
|
||||
the Evennia ones makes use of the `ENCODINGS` setting and will try very hard to
|
||||
never raise a traceback but instead echo errors through logging. See
|
||||
[here](../Concepts/Text-Encodings.md) for more info.
|
||||
|
||||
### Ansi Coloring Tools
|
||||
- [evennia.utils.ansi](evennia.utils.ansi)
|
||||
|
||||
## Display utilities
|
||||
### Making ascii tables
|
||||
|
||||
The [EvTable](evennia.utils.evtable.EvTable) class (`evennia/utils/evtable.py`) can be used
|
||||
to create correctly formatted text tables. There is also
|
||||
[EvForm](evennia.utils.evform.EvForm) (`evennia/utils/evform.py`). This reads a fixed-format
|
||||
text template from a file in order to create any level of sophisticated ascii layout. Both evtable
|
||||
and evform have lots of options and inputs so see the header of each module for help.
|
||||
|
||||
The third-party [PrettyTable](https://code.google.com/p/prettytable/) module is also included in
|
||||
Evennia. PrettyTable is considered deprecated in favor of EvTable since PrettyTable cannot handle
|
||||
ANSI colour. PrettyTable can be found in `evennia/utils/prettytable/`. See its homepage above for
|
||||
instructions.
|
||||
|
||||
### Menus
|
||||
- [evennia.EvMenu](evennia.utils.evmenu.EvMenu)
|
||||
The difference from Python's in-built `str()` and `bytes()` operators are that the Evennia ones makes use of the `ENCODINGS` setting and will try very hard to never raise a traceback but instead echo errors through logging. See [here](../Concepts/Text-Encodings.md) for more info.
|
||||
|
|
@ -13,8 +13,7 @@ There are two components to having a command running - the *Command* class and t
|
|||
1. A *Command* is a python class containing all the functioning code for what a command does - for example, a *get* command would contain code for picking up objects.
|
||||
1. A *Command Set* (often referred to as a CmdSet or cmdset) is like a container for one or more Commands. A given Command can go into any number of different command sets. Only by putting the command set on a character object you will make all the commands therein available to use by that character. You can also store command sets on normal objects if you want users to be able to use the object in various ways. Consider a "Tree" object with a cmdset defining the commands *climb* and *chop down*. Or a "Clock" with a cmdset containing the single command *check time*.
|
||||
|
||||
This page goes into full detail about how to use Commands. To fully use them you must also read the page detailing [Command Sets](./Command-Sets.md). There is also a step-by-step [Adding Command Tutorial](../Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Adding-Commands.md) that will get you started quickly without the
|
||||
extra explanations.
|
||||
This page goes into full detail about how to use Commands. To fully use them you must also read the page detailing [Command Sets](./Command-Sets.md). There is also a step-by-step [Adding Command Tutorial](../Howtos/Beginner-Tutorial/Part1/Beginner-Tutorial-Adding-Commands.md) that will get you started quickly without the extra explanations.
|
||||
|
||||
## Defining Commands
|
||||
|
||||
|
|
|
|||
|
|
@ -2,15 +2,15 @@
|
|||
|
||||
These are the 'building blocks' out of which Evennia is built. This documentation is complementary to, and often goes deeper than, the doc-strings of each component in the [API](../Evennia-API.md).
|
||||
|
||||
## Basic entites
|
||||
## Base components
|
||||
|
||||
These are base pieces used to make an Evennia game. Most are long-lived and are persisted in the database.
|
||||
|
||||
```{toctree}
|
||||
:maxdepth: 2
|
||||
|
||||
Typeclasses.md
|
||||
Portal-And-Server.md
|
||||
Sessions.md
|
||||
Typeclasses.md
|
||||
Accounts.md
|
||||
Objects.md
|
||||
Scripts.md
|
||||
|
|
@ -22,7 +22,7 @@ Tags.md
|
|||
Prototypes.md
|
||||
Help-System.md
|
||||
Permissions.md
|
||||
Portal-And-Server.md
|
||||
Locks.md
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
|
@ -35,10 +35,7 @@ Evennia's Command system handle everything sent to the server by the user.
|
|||
Commands.md
|
||||
Command-Sets.md
|
||||
Default-Commands.md
|
||||
Connection-Screen.md
|
||||
Batch-Processors.md
|
||||
Batch-Code-Processor.md
|
||||
Batch-Command-Processor.md
|
||||
Inputfuncs.md
|
||||
```
|
||||
|
||||
|
|
@ -59,7 +56,6 @@ EvTable.md
|
|||
FuncParser.md
|
||||
MonitorHandler.md
|
||||
TickerHandler.md
|
||||
Locks.md
|
||||
Signals.md
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
# Connection Screen
|
||||
|
||||
|
||||
When you first connect to your game you are greeted by Evennia's default connection screen.
|
||||
|
||||
|
||||
==============================================================
|
||||
Welcome to Evennia, version Beta-ra4d24e8a3cab+!
|
||||
|
||||
If you have an existing account, connect to it by typing:
|
||||
connect <username> <password>
|
||||
If you need to create an account, type (without the <>'s):
|
||||
create <username> <password>
|
||||
|
||||
If you have spaces in your username, enclose it in quotes.
|
||||
Enter help for more info. look will re-show this screen.
|
||||
==============================================================
|
||||
|
||||
Effective, but not very exciting. You will most likely want to change this to be more unique for
|
||||
your game. This is simple:
|
||||
|
||||
1. Edit `mygame/server/conf/connection_screens.py`.
|
||||
1. [Reload](../Setup/Running-Evennia.md) Evennia.
|
||||
|
||||
Evennia will look into this module and locate all *globally defined strings* in it. These strings
|
||||
are used as the text in your connection screen and are shown to the user at startup. If more than
|
||||
one such string/screen is defined in the module, a *random* screen will be picked from among those
|
||||
available.
|
||||
|
||||
## Commands available at the Connection Screen
|
||||
|
||||
You can also customize the [Commands](./Commands.md) available to use while the connection screen is
|
||||
shown (`connect`, `create` etc). These commands are a bit special since when the screen is running
|
||||
the account is not yet logged in. A command is made available at the login screen by adding them to
|
||||
`UnloggedinCmdSet` in `mygame/commands/default_cmdset.py`. See [Commands](./Commands.md) and the
|
||||
tutorial section on how to add new commands to a default command set.
|
||||
|
|
@ -29,7 +29,7 @@ cleanup and exit messages to the user must be handled by this function.
|
|||
It has no other mechanical function.
|
||||
- `persistent` (default `False`): if set to `True`, the editor will survive a reboot.
|
||||
|
||||
## Example of usage
|
||||
## Working with EvEditor
|
||||
|
||||
This is an example command for setting a specific Attribute using the editor.
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ class CmdSetTestAttr(Command):
|
|||
key=key)
|
||||
```
|
||||
|
||||
## Persistent editor
|
||||
### Persistent editor
|
||||
|
||||
If you set the `persistent` keyword to `True` when creating the editor, it will remain open even
|
||||
when reloading the game. In order to be persistent, an editor needs to have its callback functions
|
||||
|
|
@ -107,7 +107,7 @@ class CmdSetTestAttr(Command):
|
|||
key=key, persistent=True)
|
||||
```
|
||||
|
||||
## 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`).
|
||||
|
|
@ -154,28 +154,19 @@ the in-editor help command (`:h`).
|
|||
<txt> - longer string, usually not needed to be enclosed in quotes.
|
||||
```
|
||||
|
||||
## The EvEditor to edit code
|
||||
### 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` 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.
|
||||
|
|
|
|||
|
|
@ -1,44 +1,27 @@
|
|||
# EvMenu
|
||||
|
||||
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](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:
|
||||
|
||||
### 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:
|
||||
|
||||
```
|
||||
```shell
|
||||
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
|
||||
|
||||
```
|
||||
> Y
|
||||
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!)
|
||||
_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.
|
||||
|
||||
Here's the full EvMenu code for this example:
|
||||
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
|
||||
|
|
@ -229,39 +212,14 @@ EvMenu(caller, menu_data,
|
|||
|
||||
```
|
||||
|
||||
- `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
|
||||
- `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. 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
|
||||
|
|
@ -272,9 +230,7 @@ after
|
|||
- `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
|
||||
|
|
@ -282,7 +238,6 @@ is exited and you can also use it to store your own temporary variables for acce
|
|||
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.
|
||||
|
|
@ -506,7 +461,7 @@ manipulated for every iteration.
|
|||
> *deprecated* as of Evennia 0.8. Use `goto` for all functionality where you'd before use `exec`.
|
||||
|
||||
|
||||
## 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.
|
||||
|
|
@ -519,7 +474,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
|
||||
|
|
@ -737,7 +692,189 @@ evmenu.template2menu(caller, template_string, goto_callables)
|
|||
|
||||
```
|
||||
|
||||
## 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:
|
||||
|
||||
```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 <value>` 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 <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:
|
||||
|
||||
```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
|
||||
|
||||
- **[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
|
||||
|
|
@ -754,8 +891,7 @@ 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:
|
||||
Below is an example of a simple branching menu node leading to different other nodes depending on choice:
|
||||
|
||||
```python
|
||||
# in mygame/world/mychargen.py
|
||||
|
|
@ -993,9 +1129,7 @@ use
|
|||
|
||||
### 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:
|
||||
|
||||
|
||||
```python
|
||||
|
|
@ -1116,196 +1250,3 @@ function - for example you can't use other Python keywords like `if` inside the
|
|||
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.
|
||||
|
||||
|
||||
## 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:
|
||||
|
||||
```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 <value>` 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 <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:
|
||||
|
||||
```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.
|
||||
|
||||
|
||||
## Assorted notes
|
||||
|
||||
The EvMenu is implemented using [Commands](./Commands.md). When you start a new EvMenu, the user of the
|
||||
menu will be assigned a [CmdSet](./Command-Sets.md) 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.
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ 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:
|
||||
|
||||
```python
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# The Inline Function Parser
|
||||
# FuncParser inline text parsing
|
||||
|
||||
The [FuncParser](evennia.utils.funcparser.FuncParser) extracts and executes 'inline functions' embedded in a string on the form `$funcname(args, kwargs)`. Under the hood, this will lead to a call to a Python function you control. The inline function call will be replaced by the return from the function.
|
||||
|
||||
|
|
@ -36,30 +36,20 @@ parser.parse("This is an escaped $$pow(4) and so is this \$pow(3)")
|
|||
"This is an escaped $pow(4) and so is this $pow(3)"
|
||||
```
|
||||
|
||||
## Uses in default Evennia
|
||||
## Working with FuncParser
|
||||
|
||||
The FuncParser can be applied to any string. Out of the box it's applied in a few situations:
|
||||
|
||||
- _Outgoing messages_. All messages sent from the server is processed through FuncParser and every
|
||||
callable is provided the [Session](./Sessions.md) of the object receiving the message. This potentially
|
||||
allows a message to be modified on the fly to look different for different recipients.
|
||||
- _Prototype values_. A [Prototype](./Prototypes.md) dict's values are run through the parser such that every
|
||||
callable gets a reference to the rest of the prototype. In the Prototype ORM, this would allow builders
|
||||
to safely call functions to set non-string values to prototype values, get random values, reference
|
||||
- _Outgoing messages_. All messages sent from the server is processed through FuncParser and every callable is provided the [Session](./Sessions.md) of the object receiving the message. This potentially allows a message to be modified on the fly to look different for different recipients.
|
||||
- _Prototype values_. A [Prototype](./Prototypes.md) dict's values are run through the parser such that every callable gets a reference to the rest of the prototype. In the Prototype ORM, this would allow builders to safely call functions to set non-string values to prototype values, get random values, reference
|
||||
other fields of the prototype, and more.
|
||||
- _Actor-stance in messages to others_. In the
|
||||
[Object.msg_contents](evennia.objects.objects.DefaultObject.msg_contents) method,
|
||||
the outgoing string is parsed for special `$You()` and `$conj()` callables to decide if a given recipient
|
||||
- _Actor-stance in messages to others_. In the [Object.msg_contents](evennia.objects.objects.DefaultObject.msg_contents) method, the outgoing string is parsed for special `$You()` and `$conj()` callables to decide if a given recipient
|
||||
should see "You" or the character's name.
|
||||
|
||||
```{important}
|
||||
The inline-function parser is not intended as a 'softcode' programming language. It does not
|
||||
have things like loops and conditionals, for example. While you could in principle extend it to
|
||||
do very advanced things and allow builders a lot of power, all-out coding is something
|
||||
Evennia expects you to do in a proper text editor, outside of the game, not from inside it.
|
||||
```
|
||||
|
||||
## Using the FuncParser
|
||||
The inline-function parser is not intended as a 'softcode' programming language. It does not have things like loops and conditionals, for example. While you could in principle extend it to do very advanced things and allow builders a lot of power, all-out coding is something Evennia expects you to do in a proper text editor, outside of the game, not from inside it.
|
||||
```
|
||||
|
||||
You can apply inline function parsing to any string. The
|
||||
[FuncParser](evennia.utils.funcparser.FuncParser) is imported as `evennia.utils.funcparser`.
|
||||
|
|
@ -129,8 +119,7 @@ _test('foo', bar='4', mydefault=2, myreserved=[1, 2, 3],
|
|||
funcparser=<FuncParser>, raise_errors=False)
|
||||
```
|
||||
|
||||
The `mydefault=2` kwarg could be overwritten if we made the call as `$test(mydefault=...)`
|
||||
but `myreserved=[1, 2, 3]` will _always_ be sent as-is and will override a call `$test(myreserved=...)`.
|
||||
The `mydefault=2` kwarg could be overwritten if we made the call as `$test(mydefault=...)` but `myreserved=[1, 2, 3]` will _always_ be sent as-is and will override a call `$test(myreserved=...)`.
|
||||
The `funcparser`/`raise_errors` kwargs are also always included as reserved kwargs.
|
||||
|
||||
## Defining custom callables
|
||||
|
|
@ -143,21 +132,16 @@ def funcname(*args, **kwargs):
|
|||
return something
|
||||
```
|
||||
|
||||
> The `*args` and `**kwargs` must always be included. If you are unsure how `*args` and `**kwargs` work in Python,
|
||||
> [read about them here](https://www.digitalocean.com/community/tutorials/how-to-use-args-and-kwargs-in-python-3).
|
||||
> The `*args` and `**kwargs` must always be included. If you are unsure how `*args` and `**kwargs` work in Python, [read about them here](https://www.digitalocean.com/community/tutorials/how-to-use-args-and-kwargs-in-python-3).
|
||||
|
||||
The input from the innermost `$funcname(...)` call in your callable will always be a `str`. Here's
|
||||
an example of an `$toint` function; it converts numbers to integers.
|
||||
|
||||
"There's a $toint(22.0)% chance of survival."
|
||||
|
||||
What will enter the `$toint` callable (as `args[0]`) is the _string_ `"22.0"`. The function is responsible
|
||||
for converting this to a number so that we can convert it to an integer. We must also properly handle invalid
|
||||
inputs (like non-numbers).
|
||||
What will enter the `$toint` callable (as `args[0]`) is the _string_ `"22.0"`. The function is responsible for converting this to a number so that we can convert it to an integer. We must also properly handle invalid inputs (like non-numbers).
|
||||
|
||||
If you want to mark an error, raise `evennia.utils.funcparser.ParsingError`. This stops the entire parsing
|
||||
of the string and may or may not raise the exception depending on what you set `raise_errors` to when you
|
||||
created the parser.
|
||||
If you want to mark an error, raise `evennia.utils.funcparser.ParsingError`. This stops the entire parsing of the string and may or may not raise the exception depending on what you set `raise_errors` to when you created the parser.
|
||||
|
||||
However, if you _nest_ functions, the return of the innermost function may be something other than
|
||||
a string. Let's introduce the `$eval` function, which evaluates simple expressions using
|
||||
|
|
@ -170,20 +154,16 @@ Since the `$eval` is the innermost call, it will get a string as input - the str
|
|||
It evaluates this and returns the `float` `22.0`. This time the outermost `$toint` will be called with
|
||||
this `float` instead of with a string.
|
||||
|
||||
> It's important to safely validate your inputs since users may end up nesting your callables in any order.
|
||||
> See the next section for useful tools to help with this.
|
||||
> It's important to safely validate your inputs since users may end up nesting your callables in any order. See the next section for useful tools to help with this.
|
||||
|
||||
In these examples, the result will be embedded in the larger string, so the result of the entire parsing
|
||||
will be a string:
|
||||
In these examples, the result will be embedded in the larger string, so the result of the entire parsing will be a string:
|
||||
|
||||
```python
|
||||
parser.parse(above_string)
|
||||
"There's a 22% chance of survival."
|
||||
```
|
||||
|
||||
However, if you use the `parse_to_any` (or `parse(..., return_str=False)`) and
|
||||
_don't add any extra string around the outermost function call_,
|
||||
you'll get the return type of the outermost callable back:
|
||||
However, if you use the `parse_to_any` (or `parse(..., return_str=False)`) and _don't add any extra string around the outermost function call_, you'll get the return type of the outermost callable back:
|
||||
|
||||
```python
|
||||
parser.parse_to_any("$toint($eval(10 * 2.2)")
|
||||
|
|
@ -245,9 +225,7 @@ non-developer players/builders and some things (such as complex
|
|||
classes/callables etc) are just not safe/possible to convert from string
|
||||
representation.
|
||||
|
||||
In `evennia.utils.utils` is a helper called
|
||||
[safe_convert_to_types](evennia.utils.utils.safe_convert_to_types). This function
|
||||
automates the conversion of simple data types in a safe way:
|
||||
In `evennia.utils.utils` is a helper called [safe_convert_to_types](evennia.utils.utils.safe_convert_to_types). This function automates the conversion of simple data types in a safe way:
|
||||
|
||||
```python
|
||||
from evennia.utils.utils import safe_convert_to_types
|
||||
|
|
@ -265,44 +243,23 @@ def _process_callable(*args, **kwargs):
|
|||
|
||||
```
|
||||
|
||||
In other words, in the callable `$process(expression, local, extra1=..,
|
||||
extra2=...)`, the first argument will be handled by the 'py' converter
|
||||
(described below), the second will passed through regular Python `str`,
|
||||
kwargs will be handled by `int` and `str` respectively. You can supply
|
||||
your own converter function as long as it takes one argument and returns
|
||||
the converted result.
|
||||
|
||||
In other words,
|
||||
In other words, in the callable `$process(expression, local, extra1=.., extra2=...)`, the first argument will be handled by the 'py' converter (described below), the second will passed through regular Python `str`, kwargs will be handled by `int` and `str` respectively. You can supply your own converter function as long as it takes one argument and returns the converted result.
|
||||
|
||||
```python
|
||||
args, kwargs = safe_convert_to_type(
|
||||
(tuple_of_arg_converters, dict_of_kwarg_converters), *args, **kwargs)
|
||||
```
|
||||
|
||||
The special converter `"py"` will try to convert a string argument to a Python structure with the help of the
|
||||
following tools (which you may also find useful to experiment with on your own):
|
||||
The special converter `"py"` will try to convert a string argument to a Python structure with the help of the following tools (which you may also find useful to experiment with on your own):
|
||||
|
||||
- [ast.literal_eval](https://docs.python.org/3.8/library/ast.html#ast.literal_eval) is an in-built Python
|
||||
function. It
|
||||
_only_ supports strings, bytes, numbers, tuples, lists, dicts, sets, booleans and `None`. That's
|
||||
it - no arithmetic or modifications of data is allowed. This is good for converting individual values and
|
||||
lists/dicts from the input line to real Python objects.
|
||||
- [simpleeval](https://pypi.org/project/simpleeval/) is a third-party tool included with Evennia. This
|
||||
allows for evaluation of simple (and thus safe) expressions. One can operate on numbers and strings
|
||||
with `+-/*` as well as do simple comparisons like `4 > 3` and more. It does _not_ accept more complex
|
||||
containers like lists/dicts etc, so this and `literal_eval` are complementary to each other.
|
||||
- [ast.literal_eval](https://docs.python.org/3.8/library/ast.html#ast.literal_eval) is an in-built Python function. It _only_ supports strings, bytes, numbers, tuples, lists, dicts, sets, booleans and `None`. That's it - no arithmetic or modifications of data is allowed. This is good for converting individual values and lists/dicts from the input line to real Python objects.
|
||||
- [simpleeval](https://pypi.org/project/simpleeval/) is a third-party tool included with Evennia. This allows for evaluation of simple (and thus safe) expressions. One can operate on numbers and strings with `+-/*` as well as do simple comparisons like `4 > 3` and more. It does _not_ accept more complex containers like lists/dicts etc, so this and `literal_eval` are complementary to each other.
|
||||
|
||||
```{warning}
|
||||
It may be tempting to run use Python's in-built ``eval()`` or ``exec()`` functions as converters since
|
||||
these are able to convert any valid Python source code to Python. NEVER DO THIS unless you really, really
|
||||
know that ONLY developers will ever modify the string going into the callable. The parser is intended
|
||||
for untrusted users (if you were trusted you'd have access to Python already). Letting untrusted users
|
||||
pass strings to ``eval``/``exec`` is a MAJOR security risk. It allows the caller to run arbitrary
|
||||
Python code on your server. This is the path to maliciously deleted hard drives. Just don't do it and
|
||||
sleep better at night.
|
||||
It may be tempting to run use Python's in-built ``eval()`` or ``exec()`` functions as converters since these are able to convert any valid Python source code to Python. NEVER DO THIS unless you really, really know that ONLY developers will ever modify the string going into the callable. The parser is intended for untrusted users (if you were trusted you'd have access to Python already). Letting untrusted users pass strings to ``eval``/``exec`` is a MAJOR security risk. It allows the caller to run arbitrary Python code on your server. This is the path to maliciously deleted hard drives. Just don't do it and sleep better at night.
|
||||
```
|
||||
|
||||
## Default callables
|
||||
## Default funcparser callables
|
||||
|
||||
These are some example callables you can import and add your parser. They are divided into
|
||||
global-level dicts in `evennia.utils.funcparser`. Just import the dict(s) and merge/add one or
|
||||
|
|
@ -312,47 +269,27 @@ more to them when you create your `FuncParser` instance to have those callables
|
|||
|
||||
These are the 'base' callables.
|
||||
|
||||
- `$eval(expression)` ([code](evennia.utils.funcparser.funcparser_callable_eval)) -
|
||||
this uses `literal_eval` and `simple_eval` (see previous section) attemt to convert a string expression
|
||||
to a python object. This handles e.g. lists of literals `[1, 2, 3]` and simple expressions like `"1 + 2"`.
|
||||
- `$toint(number)` ([code](evennia.utils.funcparser.funcparser_callable_toint)) -
|
||||
always converts an output to an integer, if possible.
|
||||
- `$eval(expression)` ([code](evennia.utils.funcparser.funcparser_callable_eval)) - this uses `literal_eval` and `simple_eval` (see previous section) attemt to convert a string expression to a python object. This handles e.g. lists of literals `[1, 2, 3]` and simple expressions like `"1 + 2"`.
|
||||
- `$toint(number)` ([code](evennia.utils.funcparser.funcparser_callable_toint)) - always converts an output to an integer, if possible.
|
||||
- `$add/sub/mult/div(obj1, obj2)` ([code](evennia.utils.funcparser.funcparser_callable_add)) -
|
||||
this adds/subtracts/multiplies and divides to elements together. While simple addition could be done with
|
||||
`$eval`, this could for example be used also to add two lists together, which is not possible with `eval`;
|
||||
for example `$add($eval([1,2,3]), $eval([4,5,6])) -> [1, 2, 3, 4, 5, 6]`.
|
||||
- `$round(float, significant)` ([code](evennia.utils.funcparser.funcparser_callable_round)) -
|
||||
rounds an input float into the number of provided significant digits. For example `$round(3.54343, 3) -> 3.543`.
|
||||
- `$random([start, [end]])` ([code](evennia.utils.funcparser.funcparser_callable_random)) -
|
||||
this works like the Python `random()` function, but will randomize to an integer value if both start/end are
|
||||
this adds/subtracts/multiplies and divides to elements together. While simple addition could be done with `$eval`, this could for example be used also to add two lists together, which is not possible with `eval`; for example `$add($eval([1,2,3]), $eval([4,5,6])) -> [1, 2, 3, 4, 5, 6]`.
|
||||
- `$round(float, significant)` ([code](evennia.utils.funcparser.funcparser_callable_round)) - rounds an input float into the number of provided significant digits. For example `$round(3.54343, 3) -> 3.543`.
|
||||
- `$random([start, [end]])` ([code](evennia.utils.funcparser.funcparser_callable_random)) - this works like the Python `random()` function, but will randomize to an integer value if both start/end are
|
||||
integers. Without argument, will return a float between 0 and 1.
|
||||
- `$randint([start, [end]])` ([code](evennia.utils.funcparser.funcparser_callable_randint)) -
|
||||
works like the `randint()` python function and always returns an integer.
|
||||
- `$choice(list)` ([code](evennia.utils.funcparser.funcparser_callable_choice)) -
|
||||
the input will automatically be parsed the same way as `$eval` and is expected to be an iterable. A random
|
||||
element of this list will be returned.
|
||||
- `$pad(text[, width, align, fillchar])` ([code](evennia.utils.funcparser.funcparser_callable_pad)) -
|
||||
this will pad content. `$pad("Hello", 30, c, -)` will lead to a text centered in a 30-wide block surrounded by `-`
|
||||
characters.
|
||||
- `$crop(text, width=78, suffix='[...]')` ([code](evennia.utils.funcparser.funcparser_callable_crop)) -
|
||||
this will crop a text longer than the width, by default ending it with a `[...]`-suffix that also fits within
|
||||
the width. If no width is given, the client width or `settings.DEFAULT_CLIENT_WIDTH` will be used.
|
||||
- `$space(num)` ([code](evennia.utils.funcparser.funcparser_callable_space)) -
|
||||
this will insert `num` spaces.
|
||||
- `$just(string, width=40, align=c, indent=2)` ([code](evennia.utils.funcparser.funcparser_callable_justify)) -
|
||||
justifies the text to a given width, aligning it left/right/center or 'f' for full (spread text across width).
|
||||
- `$randint([start, [end]])` ([code](evennia.utils.funcparser.funcparser_callable_randint)) - works like the `randint()` python function and always returns an integer.
|
||||
- `$choice(list)` ([code](evennia.utils.funcparser.funcparser_callable_choice)) - the input will automatically be parsed the same way as `$eval` and is expected to be an iterable. A random element of this list will be returned.
|
||||
- `$pad(text[, width, align, fillchar])` ([code](evennia.utils.funcparser.funcparser_callable_pad)) - this will pad content. `$pad("Hello", 30, c, -)` will lead to a text centered in a 30-wide block surrounded by `-` characters.
|
||||
- `$crop(text, width=78, suffix='[...]')` ([code](evennia.utils.funcparser.funcparser_callable_crop)) - this will crop a text longer than the width, by default ending it with a `[...]`-suffix that also fits within the width. If no width is given, the client width or `settings.DEFAULT_CLIENT_WIDTH` will be used.
|
||||
- `$space(num)` ([code](evennia.utils.funcparser.funcparser_callable_space)) - this will insert `num` spaces.
|
||||
- `$just(string, width=40, align=c, indent=2)` ([code](evennia.utils.funcparser.funcparser_callable_justify)) - justifies the text to a given width, aligning it left/right/center or 'f' for full (spread text across width).
|
||||
- `$ljust` - shortcut to justify-left. Takes all other kwarg of `$just`.
|
||||
- `$rjust` - shortcut to right justify.
|
||||
- `$cjust` - shortcut to center justify.
|
||||
- `$clr(startcolor, text[, endcolor])` ([code](evennia.utils.funcparser.funcparser_callable_clr)) -
|
||||
color text. The color is given with one or two characters without the preceeding `|`. If no endcolor is
|
||||
given, the string will go back to neutral, so `$clr(r, Hello)` is equivalent to `|rHello|n`.
|
||||
- `$clr(startcolor, text[, endcolor])` ([code](evennia.utils.funcparser.funcparser_callable_clr)) - color text. The color is given with one or two characters without the preceeding `|`. If no endcolor is given, the string will go back to neutral, so `$clr(r, Hello)` is equivalent to `|rHello|n`.
|
||||
|
||||
### `evennia.utils.funcparser.SEARCHING_CALLABLES`
|
||||
|
||||
These are callables that requires access-checks in order to search for objects. So they require some
|
||||
extra reserved kwargs to be passed when running the parser:
|
||||
|
||||
These are callables that requires access-checks in order to search for objects. So they require some extra reserved kwargs to be passed when running the parser:
|
||||
```python
|
||||
|
||||
parser.parse_to_any(string, caller=<object or account>, access="control", ...)
|
||||
|
|
@ -361,30 +298,23 @@ parser.parse_to_any(string, caller=<object or account>, access="control", ...)
|
|||
The `caller` is required, it's the the object to do the access-check for. The `access` kwarg is the
|
||||
[lock type](./Locks.md) to check, default being `"control"`.
|
||||
|
||||
- `$search(query,type=account|script,return_list=False)` ([code](evennia.utils.funcparser.funcparser_callable_search)) -
|
||||
this will look up and try to match an object by key or alias. Use the `type` kwarg to
|
||||
search for `account` or `script` instead. By default this will return nothing if there are more than one
|
||||
match; if `return_list` is `True` a list of 0, 1 or more matches will be returned instead.
|
||||
- `$search(query,type=account|script,return_list=False)` ([code](evennia.utils.funcparser.funcparser_callable_search)) - this will look up and try to match an object by key or alias. Use the `type` kwarg to search for `account` or `script` instead. By default this will return nothing if there are more than one match; if `return_list` is `True` a list of 0, 1 or more matches will be returned instead.
|
||||
- `$obj(query)`, `$dbref(query)` - legacy aliases for `$search`.
|
||||
- `$objlist(query)` - legacy alias for `$search`, always returning a list.
|
||||
|
||||
|
||||
### `evennia.utils.funcparser.ACTOR_STANCE_CALLABLES`
|
||||
|
||||
These are used to implement actor-stance emoting. They are used by the
|
||||
[DefaultObject.msg_contents](evennia.objects.objects.DefaultObject.msg_contents) method
|
||||
by default. You can read a lot more about this on the page
|
||||
These are used to implement actor-stance emoting. They are used by the [DefaultObject.msg_contents](evennia.objects.objects.DefaultObject.msg_contents) method by default. You can read a lot more about this on the page
|
||||
[Change messages per receiver](../Concepts/Change-Message-Per-Receiver.md).
|
||||
|
||||
On the parser side, all these inline functions require extra kwargs be passed into the parser
|
||||
(done by `msg_contents` by default):
|
||||
On the parser side, all these inline functions require extra kwargs be passed into the parser (done by `msg_contents` by default):
|
||||
|
||||
```python
|
||||
parser.parse(string, caller=<obj>, receiver=<obj>, mapping={'key': <obj>, ...})
|
||||
```
|
||||
|
||||
Here the `caller` is the one sending the message and `receiver` the one to see it. The `mapping` contains
|
||||
references to other objects accessible via these callables.
|
||||
Here the `caller` is the one sending the message and `receiver` the one to see it. The `mapping` contains references to other objects accessible via these callables.
|
||||
|
||||
- `$you([key])` ([code](evennia.utils.funcparser.funcparser_callable_you)) -
|
||||
if no `key` is given, this represents the `caller`, otherwise an object from `mapping`
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
# Help System
|
||||
|
||||
Evennia has an extensive help system covering both command-help and regular
|
||||
free-form help documentation. It supports subtopics and if failing to find a
|
||||
match it will provide suggestsions, first from alternative topics and then by
|
||||
finding mentions of the search term in help entries.
|
||||
|
||||
|
||||
> help theatre
|
||||
|
||||
```shell
|
||||
> help theatre
|
||||
```
|
||||
|
||||
```shell
|
||||
------------------------------------------------------------------------------
|
||||
Help for The theatre (aliases: the hub, curtains)
|
||||
|
||||
|
|
@ -22,9 +19,11 @@ Subtopics:
|
|||
------------------------------------------------------------------------------
|
||||
```
|
||||
|
||||
> help evennia
|
||||
|
||||
```shell
|
||||
> help evennia
|
||||
```
|
||||
|
||||
```shell
|
||||
------------------------------------------------------------------------------
|
||||
No help found
|
||||
|
||||
|
|
@ -36,7 +35,7 @@ Suggestions:
|
|||
-----------------------------------------------------------------------------
|
||||
```
|
||||
|
||||
## Using the help system from in-game
|
||||
Evennia has an extensive help system covering both command-help and regular free-form help documentation. It supports subtopics and if failing to find a match it will provide suggestsions, first from alternative topics and then by finding mentions of the search term in help entries.
|
||||
|
||||
The help system is accessed in-game by use of the `help` command:
|
||||
|
||||
|
|
@ -44,6 +43,16 @@ The help system is accessed in-game by use of the `help` command:
|
|||
|
||||
Sub-topics are accessed as `help <topic>/<subtopic>/...`.
|
||||
|
||||
## Working with three types of help entries
|
||||
|
||||
There are three ways to generate help entries:
|
||||
|
||||
- In the database
|
||||
- As Python modules
|
||||
- From Command doc strings
|
||||
|
||||
### Database-stored help entries
|
||||
|
||||
Creating a new help entry from in-game is done with
|
||||
|
||||
sethelp <topic>[;aliases] [,category] [,lockstring] = <text>
|
||||
|
|
@ -52,205 +61,24 @@ For example
|
|||
|
||||
sethelp The Gods;pantheon, Lore = In the beginning all was dark ...
|
||||
|
||||
Use the `/edit` switch to open the EvEditor for more convenient in-game writing
|
||||
(but note that devs can also create help entries outside the game using their
|
||||
regular code editor, see below).
|
||||
This will create a new help entry in the database. Use the `/edit` switch to open the EvEditor for more convenient in-game writing (but note that devs can also create help entries outside the game using their regular code editor, see below).
|
||||
|
||||
> You can also create help entries as Python modules, outside of the game. See
|
||||
> _FileHelp_ entries below.
|
||||
|
||||
## Sources of help entries
|
||||
|
||||
Evennia collects help entries from three sources:
|
||||
|
||||
- _Auto-generated command help_ - this is literally the doc-strings of
|
||||
the [Command classes](./Commands.md). The idea is that the command docs are
|
||||
easier to maintain and keep up-to-date if the developer can change them at the
|
||||
same time as they do the code.
|
||||
- _Database-stored help entries_ - These are created in-game (using the
|
||||
default `sethelp` command as exemplified in the previous section).
|
||||
- _File-stored help entries_ - These are created outside the game, as dicts in
|
||||
normal Python modules. They allows developers to write and maintain their help
|
||||
files using a proper text editor.
|
||||
|
||||
### The Help Entry
|
||||
|
||||
All help entries (no matter the source) have the following properties:
|
||||
|
||||
- `key` - This is the main topic-name. For Commands, this is literally the
|
||||
command's `key`.
|
||||
- `aliases` - Alternate names for the help entry. This can be useful if the main
|
||||
name is hard to remember.
|
||||
- `help_category` - The general grouping of the entry. This is optional. If not
|
||||
given it will use the default category given by
|
||||
`settings.COMMAND_DEFAULT_HELP_CATEGORY` for Commands and
|
||||
`settings.DEFAULT_HELP_CATEGORY` for file+db help entries.
|
||||
- `locks` - Lock string (for commands) or LockHandler (all help entries).
|
||||
This defines who may read this entry. See the next section.
|
||||
- `tags` - This is not used by default, but could be used to further organize
|
||||
help entries.
|
||||
- `text` - The actual help entry text. This will be dedented and stripped of
|
||||
extra space at beginning and end.
|
||||
|
||||
A `text` that scrolls off the screen will automatically be paginated by
|
||||
the [EvMore](./EvMore.md) pager (you can control this with
|
||||
`settings.HELP_MORE_ENABLED=False`). If you use EvMore and want to control
|
||||
exactly where the pager should break the page, mark the break with the control
|
||||
character `\f`.
|
||||
|
||||
#### Subtopics
|
||||
|
||||
```{versionadded} 1.0
|
||||
```
|
||||
|
||||
Rather than making a very long help entry, the `text` may also be broken up
|
||||
into _subtopics_. A list of the next level of subtopics are shown below the
|
||||
main help text and allows the user to read more about some particular detail
|
||||
that wouldn't fit in the main text.
|
||||
|
||||
Subtopics use a markup slightly similar to markdown headings. The top level
|
||||
heading must be named `# subtopics` (non case-sensitive) and the following
|
||||
headers must be sub-headings to this (so `## subtopic name` etc). All headings
|
||||
are non-case sensitive (the help command will format them). The topics can be
|
||||
nested at most to a depth of 5 (which is probably too many levels already). The
|
||||
parser uses fuzzy matching to find the subtopic, so one does not have to type
|
||||
it all out exactly.
|
||||
|
||||
Below is an example of a `text` with sub topics.
|
||||
|
||||
```
|
||||
The theatre is the heart of the city, here you can find ...
|
||||
(This is the main help text, what you get with `help theatre`)
|
||||
|
||||
# subtopics
|
||||
|
||||
## lore
|
||||
|
||||
The theatre holds many mysterious things...
|
||||
(`help theatre/lore`)
|
||||
|
||||
### the grand opening
|
||||
|
||||
The grand opening is the name for a mysterious event where ghosts appeared ...
|
||||
(`this is a subsub-topic to lore, accessible as `help theatre/lore/grand` or
|
||||
any other partial match).
|
||||
|
||||
### the Phantom
|
||||
|
||||
Deep under the theatre, rumors has it a monster hides ...
|
||||
(another subsubtopic, accessible as `help theatre/lore/phantom`)
|
||||
|
||||
## layout
|
||||
|
||||
The theatre is a two-story building situated at ...
|
||||
(`help theatre/layout`)
|
||||
|
||||
## dramatis personae
|
||||
|
||||
There are many interesting people prowling the halls of the theatre ...
|
||||
(`help theatre/dramatis` or `help theathre/drama` or `help theatre/personae` would work)
|
||||
|
||||
### Primadonna Ada
|
||||
|
||||
Everyone knows the primadonna! She is ...
|
||||
(A subtopic under dramatis personae, accessible as `help theatre/drama/ada` etc)
|
||||
|
||||
### The gatekeeper
|
||||
|
||||
He always keeps an eye on the door and ...
|
||||
(`help theatre/drama/gate`)
|
||||
|
||||
```
|
||||
### Command Auto-help system
|
||||
|
||||
The auto-help system uses the `__doc__` strings of your command classes and
|
||||
formats this to a nice- looking help entry. This makes for a very easy way to
|
||||
keep the help updated - just document your commands well and updating the help
|
||||
file is just a `reload` away.
|
||||
|
||||
Example (from a module with command definitions):
|
||||
The [HelpEntry](evennia.help.models.HelpEntry) stores database help. It is _not_ a Typeclassed entity and can't be extended using the typeclass mechanism.
|
||||
|
||||
Here's how to create a database-help entry in code:
|
||||
```python
|
||||
class CmdMyCmd(Command):
|
||||
"""
|
||||
mycmd - my very own command
|
||||
|
||||
Usage:
|
||||
mycmd[/switches] <args>
|
||||
|
||||
Switches:
|
||||
test - test the command
|
||||
run - do something else
|
||||
|
||||
This is my own command that does this and that.
|
||||
|
||||
"""
|
||||
# [...]
|
||||
|
||||
locks = "cmd:all();read:all()" # default
|
||||
help_category = "General" # default
|
||||
auto_help = True # default
|
||||
|
||||
# [...]
|
||||
```
|
||||
|
||||
The text at the very top of the command class definition is the class'
|
||||
`__doc__`-string and will be shown to users looking for help. Try to use a
|
||||
consistent format - all default commands are using the structure shown above.
|
||||
|
||||
You can limit access to the help entry by the `view` and/or `read` locks on the
|
||||
Command. See [the section below](./Help-System.md#locking-help-entries) for details.
|
||||
|
||||
You should also supply the `help_category` class property if you can; this helps
|
||||
to group help entries together for people to more easily find them. See the
|
||||
`help` command in-game to see the default categories. If you don't specify the
|
||||
category, `settings.COMMAND_DEFAULT_HELP_CATEGORY` (default is "General") is
|
||||
used.
|
||||
|
||||
If you don't want your command to be picked up by the auto-help system at all
|
||||
(like if you want to write its docs manually using the info in the next section
|
||||
or you use a [cmdset](./Command-Sets.md) that has its own help functionality) you
|
||||
can explicitly set `auto_help` class property to `False` in your command
|
||||
definition.
|
||||
|
||||
Alternatively, you can keep the advantages of *auto-help* in commands, but
|
||||
control the display of command helps. You can do so by overriding the command's
|
||||
`get_help(caller, cmdset)` method. By default, this method will return the
|
||||
class docstring. You could modify it to add custom behavior: the text returned
|
||||
by this method will be displayed to the character asking for help in this
|
||||
command.
|
||||
|
||||
### Database-help entries
|
||||
|
||||
These are most commonly created in-game using the `sethelp` command. If you need to create one
|
||||
manually, you can do so with `evennia.create_help_entry()`:
|
||||
|
||||
```python
|
||||
|
||||
from evennia import create_help_entry
|
||||
entry = create_help_entry("emote",
|
||||
"Emoting is important because ...",
|
||||
category="Roleplaying", locks="view:all()")
|
||||
```
|
||||
|
||||
The entity being created is a [evennia.help.models.HelpEntry](evennia.help.models.HelpEntry)
|
||||
object. This is _not_ a [Typeclassed](./Typeclasses.md) entity and is not meant to
|
||||
be modified to any great degree. It holds the properties listed earlier. The
|
||||
text is stored in a field `entrytext`. It does not provide a `get_help` method
|
||||
like commands, stores and returns the `entrytext` directly.
|
||||
|
||||
You can search for (db-)-`HelpEntry` objects using `evennia.search_help` but note that
|
||||
this will not return the two other types of help entries.
|
||||
|
||||
### File-help entries
|
||||
### File-stored help entries
|
||||
|
||||
```{versionadded} 1.0
|
||||
```
|
||||
|
||||
File-help entries are created by the game development team outside of the game. The
|
||||
help entries are defined in normal Python modules (`.py` file ending) containing
|
||||
a `dict` to represent each entry. They require a server `reload` before any changes
|
||||
apply.
|
||||
File-help entries are created by the game development team outside of the game. The help entries are defined in normal Python modules (`.py` file ending) containing a `dict` to represent each entry. They require a server `reload` before any changes apply.
|
||||
|
||||
- Evennia will look through all modules given by
|
||||
`settings.FILE_HELP_ENTRY_MODULES`. This should be a list of python-paths for
|
||||
|
|
@ -308,26 +136,37 @@ The help entry text will be dedented and will retain paragraphs. You should try
|
|||
to keep your strings a reasonable width (it will look better). Just reload the
|
||||
server and the file-based help entries will be available to view.
|
||||
|
||||
## Entry priority
|
||||
### Command-help entries
|
||||
|
||||
Should you have clashing help-entries between the three types of available
|
||||
entries, the priority is
|
||||
The `__docstring__` of [Command classes](./Commands.md) are automatically extracted into a help entry. You set `help_category` directly on the class.
|
||||
|
||||
Command-auto-help > Db-help > File-help
|
||||
```python
|
||||
from evennia import Command
|
||||
|
||||
So if you create a db-help entry 'foo', it will replace any file-based help
|
||||
entry 'foo'. But if there is a Command 'foo', that's the help you'll get when
|
||||
you enter `help foo`.
|
||||
class MyCommand(Command):
|
||||
"""
|
||||
This command is great!
|
||||
|
||||
The reasoning for this is that commands must always be understood in order to
|
||||
play the game. Meanwhile db-based help can be kept up-to-date from in-game
|
||||
builders and may be less 'static' than the file-based ones.
|
||||
Usage:
|
||||
mycommand [argument]
|
||||
|
||||
The `sethelp` command (which only deals with creating db-based help entries)
|
||||
will warn you if a new help entry might shadow/be shadowed by a
|
||||
same/similar-named command or file-based help entry.
|
||||
When this command is called, great things happen. If you
|
||||
pass an argument, even GREATER things HAPPEN!
|
||||
|
||||
## Locking help entries
|
||||
"""
|
||||
|
||||
key = "mycommand"
|
||||
|
||||
locks: "cmd:all();read:all()" # default
|
||||
help_category = "General" # default
|
||||
auto_help = True # default
|
||||
|
||||
# ...
|
||||
```
|
||||
|
||||
When you update your code, the command's help will follow. The idea is that the command docs are easier to maintain and keep up-to-date if the developer can change them at the same time as they do the code.
|
||||
|
||||
### Locking help entries
|
||||
|
||||
The default `help` command gather all available commands and help entries
|
||||
together so they can be searched or listed. By setting locks on the command/help
|
||||
|
|
@ -374,32 +213,101 @@ help_entry = {
|
|||
the new 'read' lock-type to control access to the entry itself.
|
||||
```
|
||||
|
||||
## Customizing the look of the help system
|
||||
### Customizing the look of the help system
|
||||
|
||||
This is done almost exclusively by overriding the `help` command
|
||||
[evennia.commands.default.help.CmdHelp](evennia.commands.default.help.CmdHelp).
|
||||
This is done almost exclusively by overriding the `help` command [evennia.commands.default.help.CmdHelp](evennia.commands.default.help.CmdHelp).
|
||||
|
||||
Since the available commands may vary from moment to moment, `help` is
|
||||
responsible for collating the three sources of help-entries (commands/db/file)
|
||||
together and search through them on the fly. It also does all the formatting of
|
||||
the output.
|
||||
Since the available commands may vary from moment to moment, `help` is responsible for collating the three sources of help-entries (commands/db/file) together and search through them on the fly. It also does all the formatting of the output.
|
||||
|
||||
To make it easier to tweak the look, the parts of the code that changes the visual presentation and entity searching has been broken out into separate methods on the command class. Override these in your version of `help` to change the display or tweak as you please. See the api link above for details.
|
||||
|
||||
## Subtopics
|
||||
|
||||
```{versionadded} 1.0
|
||||
```
|
||||
|
||||
Rather than making a very long help entry, the `text` may also be broken up into _subtopics_. A list of the next level of subtopics are shown below the main help text and allows the user to read more about some particular detail that wouldn't fit in the main text.
|
||||
|
||||
Subtopics use a markup slightly similar to markdown headings. The top level heading must be named `# subtopics` (non case-sensitive) and the following headers must be sub-headings to this (so `## subtopic name` etc). All headings are non-case sensitive (the help command will format them). The topics can be nested at most to a depth of 5 (which is probably too many levels already). The parser uses fuzzy matching to find the subtopic, so one does not have to type it all out exactly.
|
||||
|
||||
Below is an example of a `text` with sub topics.
|
||||
|
||||
```
|
||||
The theatre is the heart of the city, here you can find ...
|
||||
(This is the main help text, what you get with `help theatre`)
|
||||
|
||||
# subtopics
|
||||
|
||||
## lore
|
||||
|
||||
The theatre holds many mysterious things...
|
||||
(`help theatre/lore`)
|
||||
|
||||
### the grand opening
|
||||
|
||||
The grand opening is the name for a mysterious event where ghosts appeared ...
|
||||
(`this is a subsub-topic to lore, accessible as `help theatre/lore/grand` or
|
||||
any other partial match).
|
||||
|
||||
### the Phantom
|
||||
|
||||
Deep under the theatre, rumors has it a monster hides ...
|
||||
(another subsubtopic, accessible as `help theatre/lore/phantom`)
|
||||
|
||||
## layout
|
||||
|
||||
The theatre is a two-story building situated at ...
|
||||
(`help theatre/layout`)
|
||||
|
||||
## dramatis personae
|
||||
|
||||
There are many interesting people prowling the halls of the theatre ...
|
||||
(`help theatre/dramatis` or `help theathre/drama` or `help theatre/personae` would work)
|
||||
|
||||
### Primadonna Ada
|
||||
|
||||
Everyone knows the primadonna! She is ...
|
||||
(A subtopic under dramatis personae, accessible as `help theatre/drama/ada` etc)
|
||||
|
||||
### The gatekeeper
|
||||
|
||||
He always keeps an eye on the door and ...
|
||||
(`help theatre/drama/gate`)
|
||||
|
||||
```
|
||||
|
||||
To make it easier to tweak the look, the parts of the code that changes the
|
||||
visual presentation and entity searching has been broken out into separate
|
||||
methods on the command class. Override these in your version of `help` to change
|
||||
the display or tweak as you please. See the api link above for details.
|
||||
|
||||
## Technical notes
|
||||
|
||||
Since it needs to search so different types of data, the help system has to
|
||||
collect all possibilities in memory before searching through the entire set. It
|
||||
uses the [Lunr](https://github.com/yeraydiazdiaz/lunr.py) search engine to
|
||||
search through the main bulk of help entries. Lunr is a mature engine used for
|
||||
web-pages and produces much more sensible results than previous solutions.
|
||||
#### Help-entry clashes
|
||||
|
||||
Once the main entry has been found, subtopics are then searched with
|
||||
simple `==`, `startswith` and `in` matching (there are so relatively few of them
|
||||
at that point).
|
||||
Should you have clashing help-entries (of the same name) between the three types of available entries, the priority is
|
||||
|
||||
Command-auto-help > Db-help > File-help
|
||||
|
||||
The `sethelp` command (which only deals with creating db-based help entries) will warn you if a new help entry might shadow/be shadowed by a same/similar-named command or file-based help entry.
|
||||
|
||||
#### The Help Entry container
|
||||
|
||||
All help entries (no matter the source) are parsed into an object with the following properties:
|
||||
|
||||
- `key` - This is the main topic-name. For Commands, this is literally the command's `key`.
|
||||
- `aliases` - Alternate names for the help entry. This can be useful if the main name is hard to remember.
|
||||
- `help_category` - The general grouping of the entry. This is optional. If not given it will use the default category given by `settings.COMMAND_DEFAULT_HELP_CATEGORY` for Commands and
|
||||
`settings.DEFAULT_HELP_CATEGORY` for file+db help entries.
|
||||
- `locks` - Lock string (for commands) or LockHandler (all help entries). This defines who may read this entry. See the next section.
|
||||
- `tags` - This is not used by default, but could be used to further organize help entries.
|
||||
- `text` - The actual help entry text. This will be dedented and stripped of extra space at beginning and end.
|
||||
|
||||
#### Help pagination
|
||||
|
||||
A `text` that scrolls off the screen will automatically be paginated by the [EvMore](./EvMore.md) pager (you can control this with `settings.HELP_MORE_ENABLED=False`). If you use EvMore and want to control exactly where the pager should break the page, mark the break with the control character `\f`.
|
||||
|
||||
#### Search engine
|
||||
|
||||
Since it needs to search so different types of data, the help system has to collect all possibilities in memory before searching through the entire set. It uses the [Lunr](https://github.com/yeraydiazdiaz/lunr.py) search engine to search through the main bulk of help entries. Lunr is a mature engine used for web-pages and produces much more sensible results than previous solutions.
|
||||
|
||||
Once the main entry has been found, subtopics are then searched with simple `==`, `startswith` and `in` matching (there are so relatively few of them at that point).
|
||||
|
||||
```{versionchanged} 1.0
|
||||
Replaced the old bag-of-words algorithm with lunr package.
|
||||
|
|
|
|||
|
|
@ -1,34 +1,49 @@
|
|||
# Inputfuncs
|
||||
|
||||
```
|
||||
Internet│
|
||||
┌─────┐ │ ┌────────┐
|
||||
┌──────┐ │Text │ │ ┌────────────┐ ┌─────────┐ │Command │
|
||||
│Client├────┤JSON ├─┼──►commandtuple├────►Inputfunc├────►DB query│
|
||||
└──────┘ │etc │ │ └────────────┘ └─────────┘ │etc │
|
||||
└─────┘ │ └────────┘
|
||||
│Evennia
|
||||
|
||||
An *inputfunc* is an Evennia function that handles a particular input (an [inputcommand](../Concepts/OOB.md)) from
|
||||
the client. The inputfunc is the last destination for the inputcommand along the [ingoing message
|
||||
path](../Concepts/Messagepath.md#the-ingoing-message-path). The inputcommand always has the form `(commandname,
|
||||
(args), {kwargs})` and Evennia will use this to try to find and call an inputfunc on the form
|
||||
```
|
||||
|
||||
The Inputfunc is the last fixed step on the [Ingoing message path](../Concepts/Messagepath.md#ingoing-message-path). The available Inputfuncs are looked up and called using `commandtuple` structures sent from the client. The job of the Inputfunc is to perform whatever action is requested, by firing a Command, performing a database query or whatever is needed.
|
||||
|
||||
Given a `commandtuple` on the form
|
||||
|
||||
(commandname, (args), {kwargs})
|
||||
|
||||
Evennia will try to find and call an Inputfunc on the form
|
||||
|
||||
```python
|
||||
def commandname(session, *args, **kwargs):
|
||||
# ...
|
||||
def commandname(session, *args, **kwargs):
|
||||
# ...
|
||||
|
||||
```
|
||||
Or, if no match was found, it will call an inputfunc named "default" on this form
|
||||
|
||||
```python
|
||||
def default(session, cmdname, *args, **kwargs):
|
||||
# cmdname is the name of the mismatched inputcommand
|
||||
def default(session, cmdname, *args, **kwargs):
|
||||
# cmdname is the name of the mismatched inputcommand
|
||||
|
||||
```
|
||||
|
||||
The default inputfuncs are found in [evennia/server/inputfuncs.py](evennia.server.inputfuncs).
|
||||
|
||||
## Adding your own inputfuncs
|
||||
|
||||
This is simple. Add a function on the above form to `mygame/server/conf/inputfuncs.py`. Your
|
||||
function must be in the global, outermost scope of that module and not start with an underscore
|
||||
(`_`) to be recognized as an inputfunc. Reload the server. That's it. To overload a default
|
||||
inputfunc (see below), just add a function with the same name.
|
||||
1. Add a function on the above form to `mygame/server/conf/inputfuncs.py`. Your function must be in the global, outermost scope of that module and not start with an underscore (`_`) to be recognized as an inputfunc. i
|
||||
2. `reload` the server.
|
||||
|
||||
The modules Evennia looks into for inputfuncs are defined in the list `settings.INPUT_FUNC_MODULES`.
|
||||
This list will be imported from left to right and later imported functions will replace earlier
|
||||
ones.
|
||||
To overload a default inputfunc (see below), just add a function with the same name. You can also extend the settings-list `INPUT_FUNC_MODULES`.
|
||||
|
||||
INPUT_FUNC_MODULES += ["path.to.my.inputfunc.module"]
|
||||
|
||||
All global-level functions with a name not starting with `_` in these module(s) will be used by Evennia as an inputfunc. The list is imported from left to right, so latter imported functions will replace earlier ones.
|
||||
|
||||
## Default inputfuncs
|
||||
|
||||
|
|
@ -40,23 +55,19 @@ Evennia defines a few default inputfuncs to handle the common cases. These are d
|
|||
- Input: `("text", (textstring,), {})`
|
||||
- Output: Depends on Command triggered
|
||||
|
||||
This is the most common of inputcommands, and the only one supported by every traditional mud. The
|
||||
argument is usually what the user sent from their command line. Since all text input from the user
|
||||
like this is considered a [Command](./Commands.md), this inputfunc will do things like nick-replacement
|
||||
and then pass on the input to the central Commandhandler.
|
||||
This is the most common of inputs, and the only one supported by every traditional mud. The argument is usually what the user sent from their command line. Since all text input from the user
|
||||
like this is considered a [Command](./Commands.md), this inputfunc will do things like nick-replacement and then pass on the input to the central Commandhandler.
|
||||
|
||||
### echo
|
||||
|
||||
- Input: `("echo", (args), {})`
|
||||
- Output: `("text", ("Echo returns: %s" % args), {})`
|
||||
|
||||
This is a test input, which just echoes the argument back to the session as text. Can be used for
|
||||
testing custom client input.
|
||||
This is a test input, which just echoes the argument back to the session as text. Can be used for testing custom client input.
|
||||
|
||||
### default
|
||||
|
||||
The default function, as mentioned above, absorbs all non-recognized inputcommands. The default one
|
||||
will just log an error.
|
||||
The default function, as mentioned above, absorbs all non-recognized inputcommands. The default one will just log an error.
|
||||
|
||||
### client_options
|
||||
|
||||
|
|
@ -85,24 +96,21 @@ as an outputcommand `("client_options", (), {key=value, ...})`-
|
|||
- nomarkup (bool): Strip all text tags
|
||||
- raw (bool): Leave text tags unparsed
|
||||
|
||||
> Note that there are two GMCP aliases to this inputfunc - `hello` and `supports_set`, which means
|
||||
it will be accessed via the GMCP `Hello` and `Supports.Set` instructions assumed by some clients.
|
||||
> Note that there are two GMCP aliases to this inputfunc - `hello` and `supports_set`, which means it will be accessed via the GMCP `Hello` and `Supports.Set` instructions assumed by some clients.
|
||||
|
||||
### get_client_options
|
||||
|
||||
- Input: `("get_client_options, (), {key:value, ...})`
|
||||
- Output: `("client_options, (), {key:value, ...})`
|
||||
|
||||
This is a convenience wrapper that retrieves the current options by sending "get" to
|
||||
`client_options` above.
|
||||
This is a convenience wrapper that retrieves the current options by sending "get" to `client_options` above.
|
||||
|
||||
### get_inputfuncs
|
||||
|
||||
- Input: `("get_inputfuncs", (), {})`
|
||||
- Output: `("get_inputfuncs", (), {funcname:docstring, ...})`
|
||||
|
||||
Returns an outputcommand on the form `("get_inputfuncs", (), {funcname:docstring, ...})` - a list of
|
||||
all the available inputfunctions along with their docstrings.
|
||||
Returns an outputcommand on the form `("get_inputfuncs", (), {funcname:docstring, ...})` - a list of all the available inputfunctions along with their docstrings.
|
||||
|
||||
### login
|
||||
|
||||
|
|
@ -111,16 +119,14 @@ all the available inputfunctions along with their docstrings.
|
|||
- Input: `("login", (username, password), {})`
|
||||
- Output: Depends on login hooks
|
||||
|
||||
This performs the inputfunc version of a login operation on the current Session.
|
||||
This performs the inputfunc version of a login operation on the current Session. It's meant to be used by custom client setups.
|
||||
|
||||
### get_value
|
||||
|
||||
Input: `("get_value", (name, ), {})`
|
||||
Output: `("get_value", (value, ), {})`
|
||||
|
||||
Retrieves a value from the Character or Account currently controlled by this Session. Takes one
|
||||
argument, This will only accept particular white-listed names, you'll need to overload the function
|
||||
to expand. By default the following values can be retrieved:
|
||||
Retrieves a value from the Character or Account currently controlled by this Session. Takes one argument, This will only accept particular white-listed names, you'll need to overload the function to expand. By default the following values can be retrieved:
|
||||
|
||||
- "name" or "key": The key of the Account or puppeted Character.
|
||||
- "location": Name of the current location, or "None".
|
||||
|
|
@ -128,17 +134,11 @@ to expand. By default the following values can be retrieved:
|
|||
|
||||
### repeat
|
||||
|
||||
- Input: `("repeat", (), {"callback":funcname,
|
||||
"interval": secs, "stop": False})`
|
||||
- Input: `("repeat", (), {"callback":funcname, "interval": secs, "stop": False})`
|
||||
- Output: Depends on the repeated function. Will return `("text", (repeatlist),{}` with a list of
|
||||
accepted names if given an unfamiliar callback name.
|
||||
|
||||
This will tell evennia to repeatedly call a named function at a given interval. Behind the scenes
|
||||
this will set up a [Ticker](./TickerHandler.md). Only previously acceptable functions are possible to
|
||||
repeat-call in this way, you'll need to overload this inputfunc to add the ones you want to offer.
|
||||
By default only two example functions are allowed, "test1" and "test2", which will just echo a text
|
||||
back at the given interval. Stop the repeat by sending `"stop": True` (note that you must include
|
||||
both the callback name and interval for Evennia to know what to stop).
|
||||
This will tell evennia to repeatedly call a named function at a given interval. Behind the scenes this will set up a [Ticker](./TickerHandler.md). Only previously acceptable functions are possible to repeat-call in this way, you'll need to overload this inputfunc to add the ones you want to offer. By default only two example functions are allowed, "test1" and "test2", which will just echo a text back at the given interval. Stop the repeat by sending `"stop": True` (note that you must include both the callback name and interval for Evennia to know what to stop).
|
||||
|
||||
### unrepeat
|
||||
|
||||
|
|
@ -153,20 +153,15 @@ This is a convenience wrapper for sending "stop" to the `repeat` inputfunc.
|
|||
- Input: `("monitor", (), ("name":field_or_argname, stop=False)`
|
||||
- Output (on change): `("monitor", (), {"name":name, "value":value})`
|
||||
|
||||
This sets up on-object monitoring of Attributes or database fields. Whenever the field or Attribute
|
||||
changes in any way, the outputcommand will be sent. This is using the
|
||||
[MonitorHandler](./MonitorHandler.md) behind the scenes. Pass the "stop" key to stop monitoring. Note
|
||||
that you must supply the name also when stopping to let the system know which monitor should be
|
||||
cancelled.
|
||||
This sets up on-object monitoring of Attributes or database fields. Whenever the field or Attribute changes in any way, the outputcommand will be sent. This is using the [MonitorHandler](./MonitorHandler.md) behind the scenes. Pass the "stop" key to stop monitoring. Note that you must supply the name also when stopping to let the system know which monitor should be cancelled.
|
||||
|
||||
Only fields/attributes in a whitelist are allowed to be used, you have to overload this function to
|
||||
add more. By default the following fields/attributes can be monitored:
|
||||
Only fields/attributes in a whitelist are allowed to be used, you have to overload this function to add more. By default the following fields/attributes can be monitored:
|
||||
|
||||
- "name": The current character name
|
||||
- "location": The current location
|
||||
- "desc": The description Argument
|
||||
|
||||
## unmonitor
|
||||
### unmonitor
|
||||
|
||||
- Input: `("unmonitor", (), {"name":name})`
|
||||
- Output: None
|
||||
|
|
|
|||
|
|
@ -2,14 +2,11 @@
|
|||
|
||||
|
||||
For most games it is a good idea to restrict what people can do. In Evennia such restrictions are
|
||||
applied and checked by something called *locks*. All Evennia entities ([Commands](./Commands.md),
|
||||
[Objects](./Objects.md), [Scripts](./Scripts.md), [Accounts](./Accounts.md), [Help System](./Help-System.md),
|
||||
[messages](./Msg.md) and [channels](./Channels.md)) are accessed through locks.
|
||||
applied and checked by something called *locks*. All Evennia entities ([Commands](./Commands.md), [Objects](./Objects.md), [Scripts](./Scripts.md), [Accounts](./Accounts.md), [Help System](./Help-System.md), [messages](./Msg.md) and [channels](./Channels.md)) are accessed through locks.
|
||||
|
||||
A lock can be thought of as an "access rule" restricting a particular use of an Evennia entity.
|
||||
Whenever another entity wants that kind of access the lock will analyze that entity in different
|
||||
ways to determine if access should be granted or not. Evennia implements a "lockdown" philosophy -
|
||||
all entities are inaccessible unless you explicitly define a lock that allows some or full access.
|
||||
ways to determine if access should be granted or not. Evennia implements a "lockdown" philosophy - all entities are inaccessible unless you explicitly define a lock that allows some or full access.
|
||||
|
||||
Let's take an example: An object has a lock on itself that restricts how people may "delete" that
|
||||
object. Apart from knowing that it restricts deletion, the lock also knows that only players with
|
||||
|
|
@ -18,7 +15,7 @@ on the object, the `delete` command makes sure to check if this player is really
|
|||
It calls the lock, which in turn checks if the player's id is `34`. Only then will it allow `delete`
|
||||
to go on with its job.
|
||||
|
||||
## Setting and checking a lock
|
||||
## Working with locks
|
||||
|
||||
The in-game command for setting locks on objects is `lock`:
|
||||
|
||||
|
|
@ -46,7 +43,7 @@ command:
|
|||
return
|
||||
```
|
||||
|
||||
## Defining locks
|
||||
### Defining locks
|
||||
|
||||
Defining a lock (i.e. an access restriction) in Evennia is done by adding simple strings of lock
|
||||
definitions to the object's `locks` property using `obj.locks.add()`.
|
||||
|
|
@ -92,32 +89,27 @@ the default command set) actually checks for, as in the example of `delete` abov
|
|||
|
||||
Below are the access_types checked by the default commandset.
|
||||
|
||||
- [Commands](./Commands.md)
|
||||
- `cmd` - this defines who may call this command at all.
|
||||
- [Objects](./Objects.md):
|
||||
- `control` - who is the "owner" of the object. Can set locks, delete it etc. Defaults to the
|
||||
creator of the object.
|
||||
- `call` - who may call Object-commands stored on this Object except for the Object itself. By
|
||||
default, Objects share their Commands with anyone in the same location (e.g. so you can 'press' a
|
||||
`Button` object in the room). For Characters and Mobs (who likely only use those Commands for
|
||||
themselves and don't want to share them) this should usually be turned off completely, using
|
||||
something like `call:false()`.
|
||||
- `examine` - who may examine this object's properties.
|
||||
- `delete` - who may delete the object.
|
||||
- `edit` - who may edit properties and attributes of the object.
|
||||
- `view` - if the `look` command will display/list this object in descriptions
|
||||
- [Commands](./Commands.md)
|
||||
- `cmd` - this defines who may call this command at all.
|
||||
- [Objects](./Objects.md):
|
||||
- `control` - who is the "owner" of the object. Can set locks, delete it etc. Defaults to the creator of the object.
|
||||
- `call` - who may call Object-commands stored on this Object except for the Object itself. By default, Objects share their Commands with anyone in the same location (e.g. so you can 'press' a `Button` object in the room). For Characters and Mobs (who likely only use those Commands for themselves and don't want to share them) this should usually be turned off completely, using something like `call:false()`.
|
||||
- `examine` - who may examine this object's properties.
|
||||
- `delete` - who may delete the object.
|
||||
- `edit` - who may edit properties and attributes of the object.
|
||||
- `view` - if the `look` command will display/list this object in descriptions
|
||||
and if you will be able to see its description. Note that if
|
||||
you target it specifically by name, the system will still find it, just
|
||||
not be able to look at it. See `search` lock to completely hide the item.
|
||||
- `search` - this controls if the object can be found with the
|
||||
- `search` - this controls if the object can be found with the
|
||||
`DefaultObject.search` method (usually referred to with `caller.search`
|
||||
in Commands). This is how to create entirely 'undetectable' in-game objects.
|
||||
If not setting this lock excplicitly, all objects are assumed searchable.
|
||||
Note that if you are aiming to make some _permanently invisible game system,
|
||||
using a [Script](./Scripts.md) is a better bet.
|
||||
- `get`- who may pick up the object and carry it around.
|
||||
- `puppet` - who may "become" this object and control it as their "character".
|
||||
- `attrcreate` - who may create new attributes on the object (default True)
|
||||
- `get`- who may pick up the object and carry it around.
|
||||
- `puppet` - who may "become" this object and control it as their "character".
|
||||
- `attrcreate` - who may create new attributes on the object (default True)
|
||||
- [Characters](./Objects.md#characters):
|
||||
- Same as for Objects
|
||||
- [Exits](./Objects.md#exits):
|
||||
|
|
@ -141,15 +133,11 @@ boot listeners etc.
|
|||
- `examine` - who may view this help entry (usually everyone)
|
||||
- `edit` - who may edit this help entry.
|
||||
|
||||
So to take an example, whenever an exit is to be traversed, a lock of the type *traverse* will be
|
||||
checked. Defining a suitable lock type for an exit object would thus involve a lockstring `traverse:
|
||||
<lock functions>`.
|
||||
So to take an example, whenever an exit is to be traversed, a lock of the type *traverse* will be checked. Defining a suitable lock type for an exit object would thus involve a lockstring `traverse: <lock functions>`.
|
||||
|
||||
### Custom access_types
|
||||
|
||||
As stated above, the `access_type` part of the lock is simply the 'name' or 'type' of the lock. The
|
||||
text is an arbitrary string that must be unique for an object. If adding a lock with the same
|
||||
`access_type` as one that already exists on the object, the new one override the old one.
|
||||
As stated above, the `access_type` part of the lock is simply the 'name' or 'type' of the lock. The text is an arbitrary string that must be unique for an object. If adding a lock with the same `access_type` as one that already exists on the object, the new one override the old one.
|
||||
|
||||
For example, if you wanted to create a bulletin board system and wanted to restrict who can either
|
||||
read a board or post to a board. You could then define locks such as:
|
||||
|
|
@ -172,7 +160,7 @@ trying to read the board):
|
|||
|
||||
### Lock functions
|
||||
|
||||
A lock function is a normal Python function put in a place Evennia looks for such functions. The
|
||||
A _lock function_ is a normal Python function put in a place Evennia looks for such functions. The
|
||||
modules Evennia looks at is the list `settings.LOCK_FUNC_MODULES`. *All functions* in any of those
|
||||
modules will automatically be considered a valid lock function. The default ones are found in
|
||||
`evennia/locks/lockfuncs.py` and you can start adding your own in `mygame/server/conf/lockfuncs.py`.
|
||||
|
|
@ -240,7 +228,7 @@ permissions and dbrefs of *Accounts*, not on Characters.
|
|||
- `serversetting(settingname, value)` - Only returns True if Evennia has a given setting or a
|
||||
setting set to a given value.
|
||||
|
||||
## Checking simple strings
|
||||
### Checking simple strings
|
||||
|
||||
Sometimes you don't really need to look up a certain lock, you just want to check a lockstring. A
|
||||
common use is inside Commands, in order to check if a user has a certain permission. The lockhandler
|
||||
|
|
@ -253,10 +241,9 @@ has a method `check_lockstring(accessing_obj, lockstring, bypass_superuser=False
|
|||
return
|
||||
```
|
||||
|
||||
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](./Typeclasses.md)
|
||||
|
|
@ -307,7 +294,7 @@ This is how the `create` command sets up new objects. In sequence, this permissi
|
|||
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](./Objects.md)
|
||||
called `box`.
|
||||
|
|
@ -374,14 +361,6 @@ If you wanted to set this up in python code, it would look something like this:
|
|||
|
||||
## 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.
|
||||
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.
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -1,19 +1,13 @@
|
|||
# Msg
|
||||
|
||||
The [Msg](evennia.comms.models.Msg) object represents a database-saved
|
||||
piece of communication. Think of it as a discrete piece of email - it contains
|
||||
a message, some metadata and will always have a sender and one or more
|
||||
recipients.
|
||||
The [Msg](evennia.comms.models.Msg) object represents a database-saved piece of communication. Think of it as a discrete piece of email - it contains a message, some metadata and will always have a sender and one or more recipients.
|
||||
|
||||
Once created, a Msg is normally not changed. It is persitently saved in the
|
||||
database. This allows for comprehensive logging of communications. Here are some
|
||||
good uses for `Msg` objects:
|
||||
Once created, a Msg is normally not changed. It is persitently saved in the database. This allows for comprehensive logging of communications. Here are some good uses for `Msg` objects:
|
||||
|
||||
- page/tells (the `page` command is how Evennia uses them out of the box)
|
||||
- messages in a bulletin board
|
||||
- game-wide email stored in 'mailboxes'.
|
||||
|
||||
|
||||
```{important}
|
||||
|
||||
A `Msg` does not have any in-game representation. So if you want to use them
|
||||
|
|
@ -29,13 +23,9 @@ good uses for `Msg` objects:
|
|||
Channels dropped Msg-support. Now only used in `page` command by default.
|
||||
```
|
||||
|
||||
## Msg in code
|
||||
|
||||
The Msg is intended to be used exclusively in code, to build other game systems. It is _not_
|
||||
a [Typeclassed](./Typeclasses.md) entity, which means it cannot (easily) be overridden. It
|
||||
doesn't support Attributes (but it _does_ support [Tags](./Tags.md)). It tries to be lean
|
||||
and small since a new one is created for every message.
|
||||
## Working with Msg
|
||||
|
||||
The Msg is intended to be used exclusively in code, to build other game systems. It is _not_ a [Typeclassed](./Typeclasses.md) entity, which means it cannot (easily) be overridden. It doesn't support Attributes (but it _does_ support [Tags](./Tags.md)). It tries to be lean and small since a new one is created for every message.
|
||||
You create a new message with `evennia.create_message`:
|
||||
|
||||
```python
|
||||
|
|
@ -88,8 +78,4 @@ You can search for `Msg` objects in various ways:
|
|||
|
||||
## TempMsg
|
||||
|
||||
[evennia.comms.models.TempMsg](evennia.comms.models.TempMsg) is an object
|
||||
that implements the same API as the regular `Msg`, but which has no database
|
||||
component (and thus cannot be searched). It's meant to plugged into systems
|
||||
expecting a `Msg` but where you just want to process the message without saving
|
||||
it.
|
||||
[evennia.comms.models.TempMsg](evennia.comms.models.TempMsg) is an object that implements the same API as the regular `Msg`, but which has no database component (and thus cannot be searched). It's meant to plugged into systems expecting a `Msg` but where you just want to process the message without saving it.
|
||||
|
|
|
|||
|
|
@ -49,9 +49,9 @@ Here are the import paths for the relevant child classes in the game dir
|
|||
- `mygame.typeclasses.rooms.Room` (inherits from `DefaultRoom`)
|
||||
- `mygame.typeclasses.exits.Exit` (inherits from `DefaultExit`)
|
||||
|
||||
## How to create your own object types
|
||||
## Working with Objects
|
||||
|
||||
You can easily add your own in-game behavior by either modifying one of the typeclasses in your game dir or by inheriting from them.
|
||||
You can easily add your own in-game behavior by either modifying one of the typeclasses in your game dir or by inheriting from them.
|
||||
|
||||
You can put your new typeclass directly in the relevant module, or you could organize your code in some other way. Here we assume we make a new module `mygame/typeclasses/flowers.py`:
|
||||
|
||||
|
|
@ -71,13 +71,11 @@ You can put your new typeclass directly in the relevant module, or you could org
|
|||
self.db.desc = "This is a pretty rose with thorns."
|
||||
```
|
||||
|
||||
Now you just need to point to the class *Rose* with the `create` command
|
||||
to make a new rose:
|
||||
Now you just need to point to the class *Rose* with the `create` command to make a new rose:
|
||||
|
||||
create/drop MyRose:flowers.Rose
|
||||
|
||||
What the `create` command actually *does* is to use the [evennia.create_object](evennia.utils.create.create_object)
|
||||
function. You can do the same thing yourself in code:
|
||||
What the `create` command actually *does* is to use the [evennia.create_object](evennia.utils.create.create_object) function. You can do the same thing yourself in code:
|
||||
|
||||
```python
|
||||
from evennia import create_object
|
||||
|
|
@ -91,31 +89,9 @@ your own building commands).
|
|||
|
||||
This particular Rose class doesn't really do much, all it does it make sure the attribute
|
||||
`desc`(which is what the `look` command looks for) is pre-set, which is pretty pointless since you
|
||||
will usually want to change this at build time (using the `desc` command or using the
|
||||
[Spawner](./Prototypes.md)).
|
||||
will usually want to change this at build time (using the `desc` command or using the [Spawner](./Prototypes.md)).
|
||||
|
||||
## Adding common functionality
|
||||
|
||||
`Object`, `Character`, `Room` and `Exit` also inherit from `mygame.typeclasses.objects.ObjectParent`.
|
||||
This is an empty 'mixin' class. Optionally, you can modify this class if you want to easily add some _common_ functionality to all your Objects, Characters, Rooms and Exits at once. You can still customize each subclass separately (see the Python docs on [multiple inheritance](https://docs.python.org/3/tutorial/classes.html#multiple-inheritance) for details).
|
||||
|
||||
Here is an example:
|
||||
|
||||
```python
|
||||
# in mygame/typeclasses/objects.py
|
||||
# ...
|
||||
|
||||
from evennia.objects.objects import DefaultObject
|
||||
|
||||
class ObjectParent:
|
||||
def at_pre_get(self, getter, **kwargs):
|
||||
# make all entities by default un-pickable
|
||||
return False
|
||||
```
|
||||
|
||||
Now all of `Object`, `Exit`. `Room` and `Character` default to not being able to be picked up using the `get` command.
|
||||
|
||||
## Properties and functions on Objects
|
||||
### Properties and functions on Objects
|
||||
|
||||
Beyond the properties assigned to all [typeclassed](./Typeclasses.md) objects (see that page for a list
|
||||
of those), the Object also has the following custom properties:
|
||||
|
|
@ -149,23 +125,46 @@ The Object also has a host of useful utility functions. See the function headers
|
|||
|
||||
The Object Typeclass defines many more *hook methods* beyond `at_object_creation`. Evennia calls these hooks at various points. When implementing your custom objects, you will inherit from the base parent and overload these hooks with your own custom code. See `evennia.objects.objects` for an updated list of all the available hooks or the [API for DefaultObject here](evennia.objects.objects.DefaultObject).
|
||||
|
||||
## Subclasses of `Object`
|
||||
|
||||
There are three special subclasses of *Object* in default Evennia - *Characters*, *Rooms* and *Exits*. The reason they are separated is because these particular object types are fundamental, something you will always need and in some cases requires some extra attention in order to be recognized by the game engine (there is nothing stopping you from redefining them though). In practice they are all pretty similar to the base Object.
|
||||
## Characters
|
||||
|
||||
### Characters
|
||||
The [DefaultCharacters](evennia.objects.objects.DefaultCharacter) is the root class for player in-game entities. They are usually _puppeted_ by [Accounts](./Accounts.md).
|
||||
|
||||
Characters are objects controlled by [Accounts](./Accounts.md). When a new Account logs in to Evennia for the first time, a new `Character` object is created and the Account object is assigned to the `account` attribute. A `Character` object must have a [Default Commandset](./Command-Sets.md) set on itself at creation, or the account will not be able to issue any commands! If you just inherit your own class from `evennia.DefaultCharacter` and make sure to use `super()` to call the parent methods you should be fine. In `mygame/typeclasses/characters.py` is an empty `Character` class ready for you to modify.
|
||||
When a new Account logs in to Evennia for the first time, a new `Character` object is created and the Account object is assigned to the `account` attribute (but Evennia supports [alternative connection-styles](../Concepts/Connection-Styles.md) if so desired).
|
||||
|
||||
### Rooms
|
||||
A `Character` object must have a [Default Commandset](./Command-Sets.md) set on itself at creation, or the account will not be able to issue any commands!
|
||||
|
||||
*Rooms* are the root containers of all other objects. The only thing really separating a room from any other object is that they have no `location` of their own and that default commands like `@dig` creates objects of this class - so if you want to expand your rooms with more functionality, just inherit from `ev.DefaultRoom`. In `mygame/typeclasses/rooms.py` is an empty `Room` class ready for you to modify.
|
||||
If you want to change the default character created by the default commands, you can change it in settings:
|
||||
|
||||
### Exits
|
||||
BASE_CHARACTER_TYPECLASS = "typeclasses.characters.Character"
|
||||
|
||||
This deafult points at the empty class in `mygame/typeclasses/characters.py` , ready for you to modify as you please.
|
||||
|
||||
## Rooms
|
||||
|
||||
[Rooms](evennia.objects.objects.DefaultRoom) are the root containers of all other objects.
|
||||
|
||||
The only thing really separating a room from any other object is that they have no `location` of their own and that default commands like `dig` creates objects of this class - so if you want to expand your rooms with more functionality, just inherit from `evennia.DefaultRoom`.
|
||||
|
||||
To change the default room created by `dig`, `tunnel` and other commands, change it in settings:
|
||||
|
||||
BASE_ROOM_TYPECLASS = "typeclases.rooms.Room"
|
||||
|
||||
The empty class in `mygame/typeclasses/rooms.py` is a good place to start!
|
||||
|
||||
## Exits
|
||||
|
||||
*Exits* are objects connecting other objects (usually *Rooms*) together. An object named *North* or *in* might be an exit, as well as *door*, *portal* or *jump out the window*. An exit has two things that separate them from other objects. Firstly, their *destination* property is set and points to a valid object. This fact makes it easy and fast to locate exits in the database. Secondly, exits define a special [Transit Command](./Commands.md) on themselves when they are created. This command is named the same as the exit object and will, when called, handle the practicalities of moving the character to the Exits's *destination* - this allows you to just enter the name of the exit on its own to move around, just as you would expect.
|
||||
|
||||
The exit functionality is all defined on the Exit typeclass, so you could in principle completely change how exits work in your game (it's not recommended though, unless you really know what you are doing). Exits are [locked](./Locks.md) using an access_type called *traverse* and also make use of a few hook methods for giving feedback if the traversal fails. See `evennia.DefaultExit` for more info. In `mygame/typeclasses/exits.py` there is an empty `Exit` class for you to modify.
|
||||
The exit functionality is all defined on the Exit typeclass, so you could in principle completely change how exits work in your game (it's not recommended though, unless you really know what you are doing). Exits are [locked](./Locks.md) using an access_type called *traverse* and also make use of a few hook methods for giving feedback if the traversal fails. See `evennia.DefaultExit` for more info.
|
||||
|
||||
Exits are normally overridden on a case-by-case basis, but if you want to change the default exit createad by rooms like `dig` , `tunnel` or `open` you can change it in settings:
|
||||
|
||||
BASE_EXIT_TYPECLASS = "typeclasses.exits.Exit"
|
||||
|
||||
In `mygame/typeclasses/exits.py` there is an empty `Exit` class for you to modify.
|
||||
|
||||
### Exit details
|
||||
|
||||
The process of traversing an exit is as follows:
|
||||
|
||||
|
|
@ -182,4 +181,25 @@ The process of traversing an exit is as follows:
|
|||
1. `obj.at_post_move(source)`
|
||||
1. On the Exit object, `at_post_traverse(obj, source)` is triggered.
|
||||
|
||||
If the move fails for whatever reason, the Exit will look for an Attribute `err_traverse` on itself and display this as an error message. If this is not found, the Exit will instead call `at_failed_traverse(obj)` on itself.
|
||||
If the move fails for whatever reason, the Exit will look for an Attribute `err_traverse` on itself and display this as an error message. If this is not found, the Exit will instead call `at_failed_traverse(obj)` on itself.
|
||||
|
||||
## Adding common functionality
|
||||
|
||||
`Object`, `Character`, `Room` and `Exit` also inherit from `mygame.typeclasses.objects.ObjectParent`.
|
||||
This is an empty 'mixin' class. Optionally, you can modify this class if you want to easily add some _common_ functionality to all your Objects, Characters, Rooms and Exits at once. You can still customize each subclass separately (see the Python docs on [multiple inheritance](https://docs.python.org/3/tutorial/classes.html#multiple-inheritance) for details).
|
||||
|
||||
Here is an example:
|
||||
|
||||
```python
|
||||
# in mygame/typeclasses/objects.py
|
||||
# ...
|
||||
|
||||
from evennia.objects.objects import DefaultObject
|
||||
|
||||
class ObjectParent:
|
||||
def at_pre_get(self, getter, **kwargs):
|
||||
# make all entities by default un-pickable
|
||||
return False
|
||||
```
|
||||
|
||||
Now all of `Object`, `Exit`. `Room` and `Character` default to not being able to be picked up using the `get` command.
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ This makes the superuser impossible to lock out, but makes it unsuitable to actu
|
|||
test the game's locks and restrictions with (see `quell` below). Usually there is no need to have
|
||||
but one superuser.
|
||||
|
||||
## Managing Permissions
|
||||
## Working with Permissions
|
||||
|
||||
In-game, you use the `perm` command to add and remove permissions
|
||||
|
||||
|
|
@ -49,7 +49,7 @@ typeclassed entities as the property `.permissions`:
|
|||
obj.permissions.remove("Blacksmith")
|
||||
```
|
||||
|
||||
## The permission hierarchy
|
||||
### The permission hierarchy
|
||||
|
||||
Selected permission strings can be organized in a *permission hierarchy* by editing the tuple
|
||||
`settings.PERMISSION_HIERARCHY`. Evennia's default permission hierarchy is as follows
|
||||
|
|
@ -67,7 +67,7 @@ Selected permission strings can be organized in a *permission hierarchy* by edit
|
|||
|
||||
When checking a hierarchical permission (using one of the methods to follow), you will pass checks for your level and all *below* you. That is, even if the check explicitly checks for "Builder" level access, you will actually pass if you have one of "Builder", "Admin" or "Developer". By contrast, if you check for a non-hierarchical permission, like "Blacksmith" you *must* have exactly that permission to pass.
|
||||
|
||||
## Checking permissions
|
||||
### Checking permissions
|
||||
|
||||
It's important to note that you check for the permission of a *puppeted* [Object](./Objects.md) (like a Character), the check will always first use the permissions of any `Account` connected to that Object before checking for permissions on the Object. In the case of hierarchical permissions (Admins, Builders etc), the Account permission will always be used (this stops an Account from escalating their permission by puppeting a high-level Character). If the permission looked for is not in the hierarchy, an exact match is required, first on the Account and if not found there (or if no Account is connected), then on the Object itself.
|
||||
|
||||
|
|
@ -165,16 +165,6 @@ An example of a puppet with a connected account:
|
|||
# precedence.
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,5 @@
|
|||
# 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](../Setup/Running-Evennia.md).
|
||||
|
||||
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 │
|
||||
|
|
@ -24,4 +14,13 @@ Internet│ ┌──────────┐ ┌─┐ ┌─┐
|
|||
│Evennia
|
||||
```
|
||||
|
||||
The Server and Portal are glued together via an AMP (Asynchronous Messaging Protocol) connection. This allows the two programs to communicate seamlessly on the same machine.
|
||||
The _Portal_ and _Server_ consitutes the two main halves of Evennia.
|
||||
|
||||
These are two separate `twistd` processes and can be controlled from inside the game or from the command line as described [in the Running-Evennia doc](../Setup/Running-Evennia.md).
|
||||
|
||||
- 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.
|
||||
|
|
@ -1,53 +1,36 @@
|
|||
# Spawner and Prototypes
|
||||
|
||||
```shell
|
||||
> spawn goblin
|
||||
|
||||
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](./Objects.md), not any other type of
|
||||
entity.
|
||||
Spawned Goblin Grunt(#45)
|
||||
```
|
||||
|
||||
The normal way to create a custom object in Evennia is to make a [Typeclass](./Typeclasses.md). 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.
|
||||
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](./Objects.md), not any other type of entity.
|
||||
|
||||
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.
|
||||
The normal way to create a custom object in Evennia is to make a [Typeclass](./Typeclasses.md). 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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## Using the OLC
|
||||
## Working with Prototypes
|
||||
|
||||
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
|
||||
|
||||
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 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 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:
|
||||
|
||||
|
|
@ -60,15 +43,9 @@ In dictionary form, a prototype can look something like this:
|
|||
```
|
||||
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
|
||||
|
||||
|
|
@ -84,54 +61,31 @@ All keys starting with `prototype_` are for book keeping.
|
|||
- `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](./Locks.md) like `"edit:all();control:perm(Builder)"`
|
||||
- `aliases` - list of strings for use as aliases
|
||||
- `tags` - list [Tags](./Tags.md). These are given as tuples `(tag, category, data)`.
|
||||
- `attrs` - list of [Attributes](./Attributes.md). These are given as tuples `(attrname, value,
|
||||
category, lockstring)`
|
||||
- Any other keywords are interpreted as non-category [Attributes](./Attributes.md) and their values.
|
||||
This is convenient for simple Attributes - use `attrs` for full control of Attributes.
|
||||
- `attrs` - list of [Attributes](./Attributes.md). These are given as tuples `(attrname, value, category, lockstring)`
|
||||
- Any other keywords are interpreted as non-category [Attributes](./Attributes.md) 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.
|
||||
|
||||
### Prototype values
|
||||
|
||||
|
|
@ -144,17 +98,14 @@ It can be a hard-coded value:
|
|||
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```python
|
||||
{"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:
|
||||
|
||||
```python
|
||||
{"key": lambda: random.choice(("Urfgar", "Rick the smelly", "Blargh the foul", ...)), ...}
|
||||
|
|
@ -163,19 +114,16 @@ prototype:
|
|||
|
||||
#### 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
|
||||
|
||||
```python
|
||||
{"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](./FuncParser.md) run
|
||||
every time the prototype is used to spawn a new object.
|
||||
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](./FuncParser.md) run every time the prototype is used to spawn a new object.
|
||||
|
||||
Here is how a protfunc is defined (same as an inlinefunc).
|
||||
Here is how a protfunc is defined (same as other funcparser functions).
|
||||
|
||||
```python
|
||||
# this is a silly example, you can just color the text red with |r directly!
|
||||
|
|
@ -189,13 +137,9 @@ def red(*args, **kwargs):
|
|||
return f"|r{args[0]}|n"
|
||||
```
|
||||
|
||||
> 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`:
|
||||
|
||||
```python
|
||||
# in mygame/server/conf/settings.py
|
||||
|
|
@ -203,56 +147,38 @@ module to `settings.PROT_FUNC_MODULES`:
|
|||
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 `_`.
|
||||
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.
|
||||
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.
|
||||
|
||||
| Protfunc | Description |
|
||||
| --- | --- |
|
||||
| `$random()` | Returns random value in range [0, 1) |
|
||||
| `$randint(start, end)` | Returns random value in range [start, end] |
|
||||
| `$randint(start, end)` | Returns random value in range [start, end] |
|
||||
| `$left_justify(<text>)` | Left-justify text |
|
||||
| `$right_justify(<text>)` | Right-justify text to screen width |
|
||||
| `$center_justify(<text>)` | Center-justify text to screen width |
|
||||
| `$full_justify(<text>)` | Spread text across screen width by adding spaces |
|
||||
| `$protkey(<name>)` | Returns value of another key in this prototype (self-reference) |
|
||||
| `$add(<value1>, <value2>)` | Returns value1 + value2. Can also be lists, dicts etc |
|
||||
| `$sub(<value1>, <value2>)` | Returns value1 - value2 |
|
||||
| `$mult(<value1>, <value2>)` | Returns value1 * value2 |
|
||||
| `$div(<value1>, <value2>)` | Returns value2 / value1 |
|
||||
| `$toint(<value>)` | Returns value converted to integer (or value if not possible) |
|
||||
| `$eval(<code>)` | Returns result of [literal-eval](https://docs.python.org/2/library/ast.html#ast.literal_eval) of code string. Only simple python expressions. |
|
||||
| `$right_justify(<text>)` | Right-justify text to screen width |
|
||||
| `$center_justify(<text>)` | Center-justify text to screen width |
|
||||
| `$full_justify(<text>)` | Spread text across screen width by adding spaces |
|
||||
| `$protkey(<name>)` | Returns value of another key in this prototype (self-reference) |
|
||||
| `$add(<value1>, <value2>)` | Returns value1 + value2. Can also be lists, dicts etc |
|
||||
| `$sub(<value1>, <value2>)` | Returns value1 - value2 |
|
||||
| `$mult(<value1>, <value2>)` | Returns value1 * value2 |
|
||||
| `$div(<value1>, <value2>)` | Returns value2 / value1 |
|
||||
| `$toint(<value>)` | Returns value converted to integer (or value if not possible) |
|
||||
| `$eval(<code>)` | Returns result of [literal-eval](https://docs.python.org/2/library/ast.html#ast.literal_eval) of code string. Only simple python expressions. |
|
||||
| `$obj(<query>)` | Returns object #dbref searched globally by key, tag or #dbref. Error if more than one found. |
|
||||
| `$objlist(<query>)` | Like `$obj`, except always returns a list of zero, one or more results. |
|
||||
| `$dbref(dbref)` | Returns argument if it is formed as a #dbref (e.g. #1234), otherwise error. |
|
||||
|
||||
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
|
||||
## Database prototypes
|
||||
|
||||
A prototype can be defined and stored in two ways, either in the database or as a dict in a module.
|
||||
Stored as [Scripts](./Scripts.md) 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
|
||||
## Module-based prototypes
|
||||
|
||||
Stored as [Scripts](./Scripts.md) 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.
|
||||
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
|
||||
|
|
@ -283,28 +209,23 @@ Here is an example of a prototype defined in a module:
|
|||
> that you always add `prototype_key` to be consistent.
|
||||
|
||||
|
||||
## Using @spawn
|
||||
## 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
|
||||
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
|
||||
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", \
|
||||
"health": 20}
|
||||
|
||||
> 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 @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.
|
||||
|
||||
## Using evennia.prototypes.spawner()
|
||||
### Using evennia.prototypes.spawner()
|
||||
|
||||
In code you access the spawner mechanism directly via the call
|
||||
|
||||
|
|
@ -319,16 +240,6 @@ matching list of created objects. Example:
|
|||
obj1, obj2 = evennia.prototypes.spawner.spawn({"key": "Obj1", "desc": "A test"},
|
||||
{"key": "Obj2", "desc": "Another test"})
|
||||
```
|
||||
> 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](github:evennia.prototypes.spawner#spawn).
|
||||
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](github:evennia.prototypes.spawner#spawn).
|
||||
|
|
@ -2,32 +2,13 @@
|
|||
|
||||
[Script API reference](evennia.scripts.scripts)
|
||||
|
||||
*Scripts* are the out-of-character siblings to the in-character
|
||||
[Objects](./Objects.md). 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`.
|
||||
*Scripts* are the out-of-character siblings to the in-character [Objects](./Objects.md). 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](./Objects.md) with a `None`-location just to store some game data,
|
||||
you should really be using a Script instead.
|
||||
If you ever consider creating an [Object](./Objects.md) with a `None`-location just to store some game data, you should really be using a Script instead.
|
||||
|
||||
- Scripts are full [Typeclassed](./Typeclasses.md) entities - they have [Attributes](./Attributes.md) and
|
||||
can be modified in the same way. But they have _no in-game existence_, so no
|
||||
location or command-execution like [Objects](./Objects.md) and no connection to a particular
|
||||
player/session like [Accounts](./Accounts.md). 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](evennia.utils.utils.delay) and
|
||||
[evennia.utils.repeat](evennia.utils.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](./Typeclasses.md) entities - they have [Attributes](./Attributes.md) and can be modified in the same way. But they have _no in-game existence_, so no location or command-execution like [Objects](./Objects.md) and no connection to a particular player/session like [Accounts](./Accounts.md). 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](evennia.utils.utils.delay) and [evennia.utils.repeat](evennia.utils.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.
|
||||
|
||||
```{versionchanged} 1.0
|
||||
In previus Evennia versions, stopping the Script's timer also meant deleting the Script object.
|
||||
|
|
@ -36,7 +17,7 @@ you should really be using a Script instead.
|
|||
|
||||
```
|
||||
|
||||
## In-game command examples
|
||||
## Working with Scripts
|
||||
|
||||
There are two main commands controlling scripts in the default cmdset:
|
||||
|
||||
|
|
@ -53,10 +34,10 @@ The `scripts` command is used to view all scripts and perform operations on them
|
|||
> scripts/delete #566
|
||||
|
||||
```{versionchanged} 1.0
|
||||
The `addscript` command used to be only `script` which was easy to confuse with `scripts`.
|
||||
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).
|
||||
|
|
@ -111,13 +92,13 @@ new_script.delete()
|
|||
timed_script.delete()
|
||||
```
|
||||
|
||||
## Defining new Scripts
|
||||
### Defining new Scripts
|
||||
|
||||
A Script is defined as a class and is created in the same way as other
|
||||
[typeclassed](./Typeclasses.md) 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.
|
||||
|
|
@ -162,12 +143,9 @@ evennia.create_script('typeclasses.scripts.MyScript', key="another name",
|
|||
|
||||
```
|
||||
|
||||
See the [create_script](evennia.utils.create.create_script) and
|
||||
[search_script](evennia.utils.search.search_script) API documentation for more options
|
||||
on creating and finding Scripts.
|
||||
See the [create_script](evennia.utils.create.create_script) and [search_script](evennia.utils.search.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.
|
||||
|
||||
|
|
@ -199,8 +177,7 @@ Supported properties are:
|
|||
- `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.
|
||||
|
|
@ -226,30 +203,17 @@ The timer component is controlled with methods on the Script class:
|
|||
- `.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](evennia.utils.utils.delay) and
|
||||
[evennia.utils.repeat](evennia.utils.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.
|
||||
If the _only_ goal is to get a repeat/delay effect, the [evennia.utils.delay](evennia.utils.utils.delay) and [evennia.utils.repeat](evennia.utils.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.
|
||||
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`.
|
||||
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](./Accounts.md) or (more commonly) an [Object](./Objects.md).
|
||||
If so, the 'parent object' will be available to the script as either `.obj` or `.account`.
|
||||
|
|
@ -303,7 +267,7 @@ You can also attach the script as part of creating it:
|
|||
create_script('typeclasses.weather.Weather', obj=myroom)
|
||||
```
|
||||
|
||||
## Other Script methods
|
||||
### Other Script methods
|
||||
|
||||
A Script has all the properties of a typeclassed object, such as `db` and `ndb`(see
|
||||
[Typeclasses](./Typeclasses.md)). Setting `key` is useful in order to manage scripts (delete them by name
|
||||
|
|
@ -311,13 +275,9 @@ etc). These are usually set up in the Script's typeclass, but can also be assign
|
|||
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()`
|
||||
|
|
@ -325,12 +285,34 @@ reload.
|
|||
- `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](./Attributes.md), [Tags](./Tags.md) and [Locks](./Locks.md) etc like other
|
||||
Typeclassed entities.
|
||||
In addition, Scripts support [Attributes](./Attributes.md), [Tags](./Tags.md) and [Locks](./Locks.md) etc like other Typeclassed entities.
|
||||
|
||||
See also the methods involved in controlling a [Timed Script](#timed-scripts) above.
|
||||
See also the methods involved in controlling a [Timed Script](#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.
|
||||
|
||||
```python
|
||||
|
||||
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.
|
||||
|
|
@ -338,7 +320,6 @@ to access from anywhere. This means they need to be searched for in order to be
|
|||
Evennia supplies a convenient "container" `evennia.GLOBAL_SCRIPTS` to help organize your global
|
||||
scripts. All you need is the Script's `key`.
|
||||
|
||||
|
||||
```python
|
||||
from evennia import GLOBAL_SCRIPTS
|
||||
|
||||
|
|
@ -354,11 +335,7 @@ GLOBAL_SCRIPTS.weather.db.current_weather = "Cloudy"
|
|||
```
|
||||
|
||||
```{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`:
|
||||
|
|
@ -386,16 +363,10 @@ GLOBAL_SCRIPTS = {
|
|||
}
|
||||
```
|
||||
|
||||
Above we add two scripts with keys `myscript` and `storagescript`respectively. 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](evennia.utils.create.create_script) are
|
||||
supported here.
|
||||
|
||||
Above we add two scripts with keys `myscript` and `storagescript`respectively. 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](evennia.utils.create.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:
|
||||
|
|
@ -413,25 +384,3 @@ That is, if the script is deleted, next time you get it from `GLOBAL_SCRIPTS`, E
|
|||
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.
|
||||
|
||||
```python
|
||||
|
||||
from evennia.utils import logger
|
||||
|
||||
class Weather(Script):
|
||||
|
||||
# [...]
|
||||
|
||||
def at_repeat(self):
|
||||
|
||||
try:
|
||||
# [...]
|
||||
except Exception:
|
||||
logger.log_trace()
|
||||
|
|
|
|||
|
|
@ -15,17 +15,17 @@ A session object has its own [cmdset](./Command-Sets.md), usually the "unloggedi
|
|||
|
||||
A Session is not *persistent* - it is not a [Typeclass](./Typeclasses.md) and has no connection to the database. The Session will go away when a user disconnects and you will lose any custom data on it if the server reloads. The `.db` handler on Sessions is there to present a uniform API (so you can assume `.db` exists even if you don't know if you receive an Object or a Session), but this is just an alias to `.ndb`. So don't store any data on Sessions that you can't afford to lose in a reload.
|
||||
|
||||
## Properties on Sessions
|
||||
## Working with Sessions
|
||||
|
||||
### Properties on Sessions
|
||||
|
||||
Here are some important properties available on (Server-)Sessions
|
||||
|
||||
- `sessid` - The unique session-id. This is an integer starting from 1.
|
||||
- `address` - The connected client's address. Different protocols give different information here.
|
||||
- `logged_in` - `True` if the user authenticated to this session.
|
||||
- `account` - The [Account](./Accounts.md) this Session is attached to. If not logged in yet, this is
|
||||
`None`.
|
||||
- `puppet` - The [Character/Object](./Objects.md) currently puppeted by this Account/Session combo. If
|
||||
not logged in or in OOC mode, this is `None`.
|
||||
- `account` - The [Account](./Accounts.md) this Session is attached to. If not logged in yet, this is `None`.
|
||||
- `puppet` - The [Character/Object](./Objects.md) currently puppeted by this Account/Session combo. If not logged in or in OOC mode, this is `None`.
|
||||
- `ndb` - The [Non-persistent Attribute](./Attributes.md) handler.
|
||||
- `db` - As noted above, Sessions don't have regular Attributes. This is an alias to `ndb`.
|
||||
- `cmdset` - The Session's [CmdSetHandler](./Command-Sets.md)
|
||||
|
|
@ -38,61 +38,7 @@ Session statistics are mainly used internally by Evennia.
|
|||
last time this session was truly visibly active.
|
||||
- `cmd_total` - Total number of Commands passed through this Session.
|
||||
|
||||
## Multisession mode
|
||||
|
||||
The number of sessions possible to connect to a given account at the same time and how it works is given by the `MULTISESSION_MODE` setting:
|
||||
|
||||
* `MULTISESSION_MODE=0`: One session per account. When connecting with a new session the old one is disconnected. This is the default mode and emulates many classic mud code bases.
|
||||
```
|
||||
┌──────┐ │ ┌───────┐ ┌───────┐ ┌─────────┐
|
||||
│Client├─┼──►│Session├───►│Account├──►│Character│
|
||||
└──────┘ │ └───────┘ └───────┘ └─────────┘
|
||||
```
|
||||
* `MULTISESSION_MODE=1`: Many sessions per account, input/output from/to each session is treated the same. For the player this means they can connect to the game from multiple clients and see the same output in all of them. The result of a command given in one client (that is, through one Session) will be returned to *all* connected Sessions/clients with no distinction.
|
||||
```
|
||||
│
|
||||
┌──────┐ │ ┌───────┐
|
||||
│Client├─┼──►│Session├──┐
|
||||
└──────┘ │ └───────┘ └──►┌───────┐ ┌─────────┐
|
||||
│ │Account├──►│Character│
|
||||
┌──────┐ │ ┌───────┐ ┌──►└───────┘ └─────────┘
|
||||
│Client├─┼──►│Session├──┘
|
||||
└──────┘ │ └───────┘
|
||||
│
|
||||
```
|
||||
|
||||
* `MULTISESSION_MODE=2`: Many sessions per account, one character per session. In this mode, puppeting an Object/Character will link the puppet back only to the particular Session doing the puppeting. That is, input from that Session will make use of the CmdSet of that Object/Character and outgoing messages (such as the result of a `look`) will be passed back only to that puppeting Session. If another Session tries to puppet the same Character, the old Session will automatically un-puppet it. From the player's perspective, this will mean that they can open separate game clients and play a different Character in each using one game account.
|
||||
```
|
||||
│ ┌───────┐
|
||||
┌──────┐ │ ┌───────┐ │Account│ ┌─────────┐
|
||||
│Client├─┼──►│Session├──┐ │ │ ┌►│Character│
|
||||
└──────┘ │ └───────┘ └──┼───────┼──┘ └─────────┘
|
||||
│ │ │
|
||||
┌──────┐ │ ┌───────┐ ┌──┼───────┼──┐ ┌─────────┐
|
||||
│Client├─┼──►│Session├──┘ │ │ └►│Character│
|
||||
└──────┘ │ └───────┘ │ │ └─────────┘
|
||||
│ └───────┘
|
||||
```
|
||||
* `MULTISESSION_MODE=3`: Many sessions per account *and* character. This is the full multi-puppeting mode, where multiple sessions may not only connect to the player account but multiple sessions may also puppet a single character at the same time. From the user's perspective it means one can open multiple client windows, some for controlling different Characters and some that share a Character's input/output like in mode 1. This mode otherwise works the same as mode 2.
|
||||
```
|
||||
│ ┌───────┐
|
||||
┌──────┐ │ ┌───────┐ │Account│ ┌─────────┐
|
||||
│Client├─┼──►│Session├──┐ │ │ ┌►│Character│
|
||||
└──────┘ │ └───────┘ └──┼───────┼──┘ └─────────┘
|
||||
│ │ │
|
||||
┌──────┐ │ ┌───────┐ ┌──┼───────┼──┐
|
||||
│Client├─┼──►│Session├──┘ │ │ └►┌─────────┐
|
||||
└──────┘ │ └───────┘ │ │ │Character│
|
||||
│ │ │ ┌►└─────────┘
|
||||
┌──────┐ │ ┌───────┐ ┌──┼───────┼──┘ ▼
|
||||
│Client├─┼──►│Session├──┘ │ │
|
||||
└──────┘ │ └───────┘ └───────┘
|
||||
│
|
||||
```
|
||||
|
||||
> Note that even if multiple Sessions puppet one Character, there is only ever one instance of that Character.
|
||||
|
||||
## Returning data to the session
|
||||
### Returning data to the session
|
||||
|
||||
When you use `msg()` to return data to a user, the object on which you call the `msg()` matters. The
|
||||
`MULTISESSION_MODE` also matters, especially if greater than 1.
|
||||
|
|
@ -120,51 +66,29 @@ This is a *handler* that tracks all Sessions attached to or puppeting them. Use
|
|||
it (it's always a single one). It will be `None` if no session is involved, like when a mob or
|
||||
script triggers the Command.
|
||||
|
||||
## Customizing the Session object
|
||||
### Customizing the Session object
|
||||
|
||||
When would one want to customize the Session object? Consider for example a character creation
|
||||
system: You might decide to keep this on the out-of-character level. This would mean that you create
|
||||
the character at the end of some sort of menu choice. The actual char-create cmdset would then
|
||||
normally be put on the account. This works fine as long as you are `MULTISESSION_MODE` below 2.
|
||||
For higher modes, replacing the Account cmdset will affect *all* your connected sessions, also those
|
||||
not involved in character creation. In this case you want to instead put the char-create cmdset on
|
||||
the Session level - then all other sessions will keep working normally despite you creating a new
|
||||
character in one of them.
|
||||
When would one want to customize the Session object? Consider for example a character creation system: You might decide to keep this on the out-of-character level. This would mean that you create the character at the end of some sort of menu choice. The actual char-create cmdset would then normally be put on the account. This works fine as long as you are `MULTISESSION_MODE` below 2. For higher modes, replacing the Account cmdset will affect *all* your connected sessions, also those not involved in character creation. In this case you want to instead put the char-create cmdset on the Session level - then all other sessions will keep working normally despite you creating a new character in one of them.
|
||||
|
||||
By default, the session object gets the `commands.default_cmdsets.UnloggedinCmdSet` when the user
|
||||
first connects. Once the session is authenticated it has *no* default sets. To add a "logged-in"
|
||||
cmdset to the Session, give the path to the cmdset class with `settings.CMDSET_SESSION`. This set
|
||||
By default, the session object gets the `commands.default_cmdsets.UnloggedinCmdSet` when the user first connects. Once the session is authenticated it has *no* default sets. To add a "logged-in" cmdset to the Session, give the path to the cmdset class with `settings.CMDSET_SESSION`. This set
|
||||
will then henceforth always be present as soon as the account logs in.
|
||||
|
||||
To customize further you can completely override the Session with your own subclass. To replace the
|
||||
default Session class, change `settings.SERVER_SESSION_CLASS` to point to your custom class. This is
|
||||
a dangerous practice and errors can easily make your game unplayable. Make sure to take heed of the
|
||||
[original](https://github.com/evennia/evennia/blob/master/evennia/server/session.py) and make your
|
||||
changes carefully.
|
||||
To customize further you can completely override the Session with your own subclass. To replace the default Session class, change `settings.SERVER_SESSION_CLASS` to point to your custom class. This is a dangerous practice and errors can easily make your game unplayable. Make sure to take heed of the [original](https://github.com/evennia/evennia/blob/master/evennia/server/session.py) and make your changes carefully.
|
||||
|
||||
## Portal and Server Sessions
|
||||
|
||||
*Note: This is considered an advanced topic. You don't need to know this on a first read-through.*
|
||||
|
||||
Evennia is split into two parts, the [Portal and the Server](./Portal-And-Server.md). Each side tracks
|
||||
its own Sessions, syncing them to each other.
|
||||
Evennia is split into two parts, the [Portal and the Server](./Portal-And-Server.md). Each side tracks its own Sessions, syncing them to each other.
|
||||
|
||||
The "Session" we normally refer to is actually the `ServerSession`. Its counter-part on the Portal
|
||||
side is the `PortalSession`. Whereas the server sessions deal with game states, the portal session
|
||||
deals with details of the connection-protocol itself. The two are also acting as backups of critical
|
||||
data such as when the server reboots.
|
||||
|
||||
New Account connections are listened for and handled by the Portal using the [protocols](Portal-And-
|
||||
Server) it understands (such as telnet, ssh, webclient etc). When a new connection is established, a
|
||||
`PortalSession` is created on the Portal side. This session object looks different depending on
|
||||
which protocol is used to connect, but all still have a minimum set of attributes that are generic
|
||||
to all
|
||||
sessions.
|
||||
New Account connections are listened for and handled by the Portal using the [protocols](Portal-And- Server) it understands (such as telnet, ssh, webclient etc). When a new connection is established, a `PortalSession` is created on the Portal side. This session object looks different depending on which protocol is used to connect, but all still have a minimum set of attributes that are generic to all sessions.
|
||||
|
||||
These common properties are piped from the Portal, through the AMP connection, to the Server, which
|
||||
is now informed a new connection has been established. On the Server side, a `ServerSession` object
|
||||
is created to represent this. There is only one type of `ServerSession`; It looks the same
|
||||
regardless of how the Account connects.
|
||||
These common properties are piped from the Portal, through the AMP connection, to the Server, which is now informed a new connection has been established. On the Server side, a `ServerSession` object is created to represent this. There is only one type of `ServerSession`; It looks the same regardless of how the Account connects.
|
||||
|
||||
From now on, there is a one-to-one match between the `ServerSession` on one side of the AMP
|
||||
connection and the `PortalSession` on the other. Data arriving to the Portal Session is sent on to
|
||||
|
|
@ -181,22 +105,17 @@ Portal side. When the Server comes back up, this data is returned by the Portal
|
|||
in sync. This way an Account's login status and other connection-critical things can survive a
|
||||
server reboot (assuming the Portal is not stopped at the same time, obviously).
|
||||
|
||||
## Sessionhandlers
|
||||
### Sessionhandlers
|
||||
|
||||
Both the Portal and Server each have a *sessionhandler* to manage the connections. These handlers
|
||||
are global entities contain all methods for relaying data across the AMP bridge. All types of
|
||||
Sessions hold a reference to their respective Sessionhandler (the property is called
|
||||
`sessionhandler`) so they can relay data. See [protocols](../Concepts/Protocols.md) for more info
|
||||
on building new protocols.
|
||||
`sessionhandler`) so they can relay data. See [protocols](../Concepts/Protocols.md) for more info on building new protocols.
|
||||
|
||||
To get all Sessions in the game (i.e. all currently connected clients), you access the server-side
|
||||
Session handler, which you get by
|
||||
To get all Sessions in the game (i.e. all currently connected clients), you access the server-side Session handler, which you get by
|
||||
```
|
||||
from evennia.server.sessionhandler import SESSION_HANDLER
|
||||
```
|
||||
> Note: The `SESSION_HANDLER` singleton has an older alias `SESSIONS` that is commonly seen in
|
||||
various places as well.
|
||||
> Note: The `SESSION_HANDLER` singleton has an older alias `SESSIONS` that is commonly seen in various places as well.
|
||||
|
||||
See the
|
||||
[sessionhandler.py](https://github.com/evennia/evennia/blob/master/evennia/server/sessionhandler.py)
|
||||
module for details on the capabilities of the `ServerSessionHandler`.
|
||||
See the [sessionhandler.py](https://github.com/evennia/evennia/blob/master/evennia/server/sessionhandler.py) module for details on the capabilities of the `ServerSessionHandler`.
|
||||
|
|
@ -18,7 +18,7 @@ signal.
|
|||
Evennia uses the [Django Signal system](https://docs.djangoproject.com/en/2.2/topics/signals/).
|
||||
|
||||
|
||||
## Attaching a handler to a signal
|
||||
## Working with Signals
|
||||
|
||||
First you create your handler
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ account = search_account("foo")[0]
|
|||
signals.SIGNAL_ACCOUNT_POST_CONNECT.connect(myhandler, account)
|
||||
```
|
||||
|
||||
## Available signals
|
||||
### Available signals
|
||||
|
||||
All signals (including some django-specific defaults) are available in the module
|
||||
`evennia.server.signals`
|
||||
|
|
|
|||
|
|
@ -29,8 +29,9 @@ Another example would be a weather script affecting all rooms tagged as `outdoor
|
|||
|
||||
In Evennia, Tags are technically also used to implement `Aliases` (alternative names for objects) and `Permissions` (simple strings for [Locks](./Locks.md) to check for).
|
||||
|
||||
## Working with Tags
|
||||
|
||||
## Properties of Tags (and Aliases and Permissions)
|
||||
### Properties of Tags (and Aliases and Permissions)
|
||||
|
||||
Tags are *unique*. This means that there is only ever one Tag object with a given key and category.
|
||||
|
||||
|
|
@ -63,11 +64,9 @@ each entity type for correctly storing the data behind the scenes.
|
|||
*Aliases* and *Permissions*. The Taghandlers using this special field are especially intended to
|
||||
free up the *category* property for any use you desire.
|
||||
|
||||
## Adding/Removing Tags
|
||||
### Adding/Removing Tags
|
||||
|
||||
You can tag any *typeclassed* object, namely [Objects](./Objects.md), [Accounts](./Accounts.md),
|
||||
[Scripts](./Scripts.md) and [Channels](./Channels.md). General tags are added by the *Taghandler*. The
|
||||
tag handler is accessed as a property `tags` on the relevant entity:
|
||||
You can tag any *typeclassed* object, namely [Objects](./Objects.md), [Accounts](./Accounts.md), [Scripts](./Scripts.md) and [Channels](./Channels.md). General tags are added by the *Taghandler*. The tag handler is accessed as a property `tags` on the relevant entity:
|
||||
|
||||
```python
|
||||
mychair.tags.add("furniture")
|
||||
|
|
@ -92,7 +91,7 @@ You can also use the default `@tag` command:
|
|||
|
||||
This tags the chair with a 'furniture' Tag (the one with a `None` category).
|
||||
|
||||
## Searching for objects with a given tag
|
||||
### Searching for objects with a given tag
|
||||
|
||||
Usually tags are used as a quick way to find tagged database entities. You can retrieve all objects
|
||||
with a given Tag like this in code:
|
||||
|
|
@ -118,14 +117,9 @@ with a given Tag like this in code:
|
|||
accounts = evennia.search_tag_account("guestaccount")
|
||||
```
|
||||
|
||||
> Note that searching for just "furniture" will only return the objects tagged with the "furniture"
|
||||
tag that
|
||||
has a category of `None`. We must explicitly give the category to get the "luxurious" furniture.
|
||||
> Note that searching for just "furniture" will only return the objects tagged with the "furniture" tag that has a category of `None`. We must explicitly give the category to get the "luxurious" furniture.
|
||||
|
||||
Using any of the `search_tag` variants will all return [Django
|
||||
Querysets](https://docs.djangoproject.com/en/2.1/ref/models/querysets/), including if you only have
|
||||
one match. You can treat querysets as lists and iterate over them, or continue building search
|
||||
queries with them.
|
||||
Using any of the `search_tag` variants will all return [Django Querysets](https://docs.djangoproject.com/en/2.1/ref/models/querysets/), including if you only have one match. You can treat querysets as lists and iterate over them, or continue building search queries with them.
|
||||
|
||||
Remember when searching that not setting a category means setting it to `None` - this does *not*
|
||||
mean that category is undefined, rather `None` is considered the default, unnamed category.
|
||||
|
|
@ -143,16 +137,13 @@ objs = evennia.search_tag("foo")
|
|||
objs = evennia.search_tag("foo", category="bar")
|
||||
# or
|
||||
objs = evennia.search_tag(category="bar")
|
||||
|
||||
```
|
||||
|
||||
|
||||
|
||||
There is also an in-game command that deals with assigning and using ([Object-](./Objects.md)) tags:
|
||||
|
||||
@tag/search furniture
|
||||
tag/search furniture
|
||||
|
||||
## Using Aliases and Permissions
|
||||
## Aliases and Permissions
|
||||
|
||||
Aliases and Permissions are implemented using normal TagHandlers that simply save Tags with a
|
||||
different `tagtype`. These handlers are named `aliases` and `permissions` on all Objects. They are
|
||||
|
|
@ -169,14 +160,3 @@ used in the same way as Tags above:
|
|||
and so on. Similarly to how `@tag` works in-game, there is also the `@perm` command for assigning
|
||||
permissions and `@alias` command for aliases.
|
||||
|
||||
## Assorted notes
|
||||
|
||||
Generally, tags are enough on their own for grouping objects. Having no tag `category` is perfectly
|
||||
fine and the normal operation. Simply adding a new Tag for grouping objects is often better than
|
||||
making a new category. So think hard before deciding you really need to categorize your Tags.
|
||||
|
||||
That said, tag categories can be useful if you build some game system that uses tags. You can then
|
||||
use tag categories to make sure to separate tags created with this system from any other tags
|
||||
created elsewhere. You can then supply custom search methods that *only* find objects tagged with
|
||||
tags of that category. An example of this
|
||||
is found in the [Zone tutorial](../Concepts/Zones.md).
|
||||
|
|
@ -1,61 +1,55 @@
|
|||
# Typeclasses
|
||||
|
||||
*Typeclasses* form the core of Evennia's data storage. It allows Evennia to represent any number of
|
||||
different game entities as Python classes, without having to modify the database schema for every
|
||||
new type.
|
||||
*Typeclasses* form the core of Evennia's data storage. It allows Evennia to represent any number of different game entities as Python classes, without having to modify the database schema for every new type.
|
||||
|
||||
In Evennia the most important game entities, [Accounts](./Accounts.md), [Objects](./Objects.md), [Scripts](./Scripts.md) and [Channels](./Channels.md) are all Python classes inheriting, at varying distance, from `evennia.typeclasses.models.TypedObject`. In the documentation we refer to these objects as being "typeclassed" or even "being a typeclass".
|
||||
|
||||
This is how the inheritance looks for the typeclasses in Evennia:
|
||||
|
||||
```
|
||||
TypedObject
|
||||
_________________|_________________________________
|
||||
| | | |
|
||||
1: AccountDB ObjectDB ScriptDB ChannelDB
|
||||
| | | |
|
||||
2: DefaultAccount DefaultObject DefaultScript DefaultChannel
|
||||
| DefaultCharacter | |
|
||||
| DefaultRoom | |
|
||||
| DefaultExit | |
|
||||
| | | |
|
||||
3: Account Object Script Channel
|
||||
Character
|
||||
Room
|
||||
Exit
|
||||
┌───────────┐
|
||||
│TypedObject│
|
||||
└─────▲─────┘
|
||||
┌───────────────┬────────┴──────┬────────────────┐
|
||||
┌────┴────┐ ┌────┴───┐ ┌────┴────┐ ┌────┴───┐
|
||||
1: │AccountDB│ │ScriptDB│ │ChannelDB│ │ObjectDB│
|
||||
└────▲────┘ └────▲───┘ └────▲────┘ └────▲───┘
|
||||
┌───────┴──────┐ ┌──────┴──────┐ ┌──────┴───────┐ ┌──────┴──────┐
|
||||
2: │DefaultAccount│ │DefaultScript│ │DefaultChannel│ │DefaultObject│
|
||||
└───────▲──────┘ └──────▲──────┘ └──────▲───────┘ └──────▲──────┘
|
||||
│ │ │ │ Evennia
|
||||
────────┼───────────────┼───────────────┼────────────────┼─────────
|
||||
│ │ │ │ Gamedir
|
||||
┌───┴───┐ ┌───┴──┐ ┌───┴───┐ ┌──────┐ │
|
||||
3: │Account│ │Script│ │Channel│ │Object├─┤
|
||||
└───────┘ └──────┘ └───────┘ └──────┘ │
|
||||
┌─────────┐ │
|
||||
│Character├─┤
|
||||
└─────────┘ │
|
||||
┌────┐ │
|
||||
│Room├─┤
|
||||
└────┘ │
|
||||
┌────┐ │
|
||||
│Exit├─┘
|
||||
└────┘
|
||||
```
|
||||
|
||||
- **Level 1** above is the "database model" level. This describes the database tables and fields
|
||||
(this is technically a [Django model](https://docs.djangoproject.com/en/2.2/topics/db/models/)).
|
||||
- **Level 2** is where we find Evennia's default implementations of the various game entities, on
|
||||
top of the database. These classes define all the hook methods that Evennia calls in various
|
||||
situations. `DefaultObject` is a little special since it's the parent for `DefaultCharacter`,
|
||||
`DefaultRoom` and `DefaultExit`. They are all grouped under level 2 because they all represents
|
||||
defaults to build from.
|
||||
- **Level 3**, finally, holds empty template classes created in your game directory. This is the
|
||||
level you are meant to modify and tweak as you please, overloading the defaults as befits your game.
|
||||
The templates inherit directly from their defaults, so `Object` inherits from `DefaultObject` and
|
||||
`Room` inherits from `DefaultRoom`.
|
||||
- **Level 1** above is the "database model" level. This describes the database tables and fields (this is technically a [Django model](https://docs.djangoproject.com/en/2.2/topics/db/models/)).
|
||||
- **Level 2** is where we find Evennia's default implementations of the various game entities, on top of the database. These classes define all the hook methods that Evennia calls in various situations. `DefaultObject` is a little special since it's the parent for `DefaultCharacter`, `DefaultRoom` and `DefaultExit`. They are all grouped under level 2 because they all represents defaults to build from.
|
||||
- **Level 3**, finally, holds empty template classes created in your game directory. This is the level you are meant to modify and tweak as you please, overloading the defaults as befits your game. The templates inherit directly from their defaults, so `Object` inherits from `DefaultObject` and `Room` inherits from `DefaultRoom`.
|
||||
|
||||
The `typeclass/list` command will provide a list of all typeclasses known to
|
||||
Evennia. This can be useful for getting a feel for what is available. Note
|
||||
however that if you add a new module with a class in it but do not import that
|
||||
module from anywhere, the `typeclass/list` will not find it. To make it known
|
||||
to Evennia you must import that module from somewhere.
|
||||
> This diagram doesn't include the `ObjectParent` mixin for `Object`, `Character`, `Room` and `Exit`. This establishes a common parent for those classes, for shared properties. See [Objects](./Objects.md) for more details.
|
||||
|
||||
The `typeclass/list` command will provide a list of all typeclasses known to Evennia. This can be useful for getting a feel for what is available. Note however that if you add a new module with a class in it but do not import that module from anywhere, the `typeclass/list` will not find it. To make it known to Evennia you must import that module from somewhere.
|
||||
|
||||
|
||||
## Difference between typeclasses and classes
|
||||
|
||||
All Evennia classes inheriting from class in the table above share one important feature and two
|
||||
important limitations. This is why we don't simply call them "classes" but "typeclasses".
|
||||
[]()important limitations. This is why we don't simply call them "classes" but "typeclasses".
|
||||
|
||||
1. A typeclass can save itself to the database. This means that some properties (actually not that
|
||||
many) on the class actually represents database fields and can only hold very specific data types.
|
||||
This is detailed [below](./Typeclasses.md#about-typeclass-properties).
|
||||
1. Due to its connection to the database, the typeclass' name must be *unique* across the _entire_
|
||||
server namespace. That is, there must never be two same-named classes defined anywhere. So the below
|
||||
code would give an error (since `DefaultObject` is now globally found both in this module and in the
|
||||
default library):
|
||||
1. A typeclass can save itself to the database. This means that some properties (actually not that many) on the class actually represents database fields and can only hold very specific data types.
|
||||
1. Due to its connection to the database, the typeclass' name must be *unique* across the _entire_ server namespace. That is, there must never be two same-named classes defined anywhere. So the below code would give an error (since `DefaultObject` is now globally found both in this module and in the default library):
|
||||
|
||||
```python
|
||||
from evennia import DefaultObject as BaseObject
|
||||
|
|
@ -63,8 +57,8 @@ default library):
|
|||
pass
|
||||
```
|
||||
|
||||
1. A typeclass' `__init__` method should normally not be overloaded. This has mostly to do with the fact that the `__init__` method is not called in a predictable way. Instead Evennia suggest you use the `at_*_creation` hooks (like `at_object_creation` for Objects) for setting things the very first time the typeclass is saved to the database or the `at_init` hook which is called every time the object is cached to memory. If you know what you are doing and want to use `__init__`, it *must* both accept arbitrary keyword arguments and use `super` to call its parent:
|
||||
|
||||
1. A typeclass' `__init__` method should normally not be overloaded. This has mostly to do with the fact that the `__init__` method is not called in a predictable way. Instead Evennia suggest you use the `at_*_creation` hooks (like `at_object_creation` for Objects) for setting things the very first time the typeclass is saved to the database or the `at_init` hook which is called every time the object is cached to memory. If you know what you are doing and want to use `__init__`, it *must* both accept arbitrary keyword arguments and use `super` to call its parent:
|
||||
|
||||
```python
|
||||
def __init__(self, **kwargs):
|
||||
# my content
|
||||
|
|
@ -75,8 +69,9 @@ default library):
|
|||
Apart from this, a typeclass works like any normal Python class and you can
|
||||
treat it as such.
|
||||
|
||||
## Working with typeclasses
|
||||
|
||||
## Creating a new typeclass
|
||||
### Creating a new typeclass
|
||||
|
||||
It's easy to work with Typeclasses. Either you use an existing typeclass or you create a new Python class inheriting from an existing typeclass. Here is an example of creating a new type of Object:
|
||||
|
||||
|
|
@ -123,8 +118,6 @@ functions take a lot of extra keywords allowing you to set things like [Attribut
|
|||
[Tags](./Tags.md) all in one go. These keywords don't use the `db_*` prefix. This will also automatically
|
||||
save the new instance to the database, so you don't need to call `save()` explicitly.
|
||||
|
||||
## About typeclass properties
|
||||
|
||||
An example of a database field is `db_key`. This stores the "name" of the entity you are modifying
|
||||
and can thus only hold a string. This is one way of making sure to update the `db_key`:
|
||||
|
||||
|
|
@ -187,14 +180,11 @@ respective pages for [Objects](./Objects.md), [Scripts](./Scripts.md), [Accounts
|
|||
entities using [Evennia's flat API](../Evennia-API.md) to explore which properties and methods they have
|
||||
available.
|
||||
|
||||
## Overloading hooks
|
||||
### Overloading hooks
|
||||
|
||||
The way to customize typeclasses is usually to overload *hook methods* on them. Hooks are methods
|
||||
that Evennia call in various situations. An example is the `at_object_creation` hook on `Objects`,
|
||||
which is only called once, the very first time this object is saved to the database. Other examples
|
||||
are the `at_login` hook of Accounts and the `at_repeat` hook of Scripts.
|
||||
The way to customize typeclasses is usually to overload *hook methods* on them. Hooks are methods that Evennia call in various situations. An example is the `at_object_creation` hook on `Objects`, which is only called once, the very first time this object is saved to the database. Other examples are the `at_login` hook of Accounts and the `at_repeat` hook of Scripts.
|
||||
|
||||
## Querying for typeclasses
|
||||
### Querying for typeclasses
|
||||
|
||||
Most of the time you search for objects in the database by using convenience methods like the
|
||||
`caller.search()` of [Commands](./Commands.md) or the search functions like `evennia.search_objects`.
|
||||
|
|
@ -235,7 +225,7 @@ matches = ScriptDB.objects.filter(db_key__contains="Combat")
|
|||
When querying from the database model parent you don't need to use `filter_family` or `get_family` -
|
||||
you will always query all children on the database model.
|
||||
|
||||
## Updating existing typeclass instances
|
||||
### Updating existing typeclass instances
|
||||
|
||||
If you already have created instances of Typeclasses, you can modify the *Python code* at any time -
|
||||
due to how Python inheritance works your changes will automatically be applied to all children once you have reloaded the server.
|
||||
|
|
@ -278,7 +268,7 @@ py from typeclasses.furniture import Furniture;
|
|||
It is recommended that you plan your game properly before starting to build, to avoid having to
|
||||
retroactively update objects more than necessary.
|
||||
|
||||
## Swap typeclass
|
||||
### Swap typeclass
|
||||
|
||||
If you want to swap an already existing typeclass, there are two ways to do so: From in-game and via code. From inside the game you can use the default `@typeclass` command:
|
||||
|
||||
|
|
@ -309,7 +299,7 @@ models that are "real" in the typeclass system (that is, are represented by actu
|
|||
|
||||
Evennia modifies Django's proxy model in various ways to allow them to work without any boiler plate (for example you don't need to set the Django "proxy" property in the model `Meta` subclass, Evennia handles this for you using metaclasses). Evennia also makes sure you can query subclasses as well as patches django to allow multiple inheritance from the same base class.
|
||||
|
||||
## Caveats
|
||||
### Caveats
|
||||
|
||||
Evennia uses the *idmapper* to cache its typeclasses (Django proxy models) in memory. The idmapper allows things like on-object handlers and properties to be stored on typeclass instances and to not get lost as long as the server is running (they will only be cleared on a Server reload). Django does not work like this by default; by default every time you search for an object in the database you'll get a *different* instance of that object back and anything you stored on it that was not in the database would be lost. The bottom line is that Evennia's Typeclass instances subside in memory a lot longer than vanilla Django model instance do.
|
||||
|
||||
|
|
|
|||
|
|
@ -54,14 +54,57 @@ class UnloggedinCmdSet(default_cmds.UnloggedinCmdSet):
|
|||
|
||||
## Multisession mode and multi-playing
|
||||
|
||||
The multisession modes are described in detail in the [Session documentation](../Components/Sessions.md#multisession-mode). In brief, this is controlled by a [setting](../Setup/Settings.md). Here's the default:
|
||||
The number of sessions possible to connect to a given account at the same time and how it works is given by the `MULTISESSION_MODE` setting:
|
||||
|
||||
MULTISESSION_MODE = 0
|
||||
* `MULTISESSION_MODE=0`: One session per account. When connecting with a new session the old one is disconnected. This is the default mode and emulates many classic mud code bases.
|
||||
```
|
||||
┌──────┐ │ ┌───────┐ ┌───────┐ ┌─────────┐
|
||||
│Client├─┼──►│Session├───►│Account├──►│Character│
|
||||
└──────┘ │ └───────┘ └───────┘ └─────────┘
|
||||
```
|
||||
* `MULTISESSION_MODE=1`: Many sessions per account, input/output from/to each session is treated the same. For the player this means they can connect to the game from multiple clients and see the same output in all of them. The result of a command given in one client (that is, through one Session) will be returned to *all* connected Sessions/clients with no distinction.
|
||||
```
|
||||
│
|
||||
┌──────┐ │ ┌───────┐
|
||||
│Client├─┼──►│Session├──┐
|
||||
└──────┘ │ └───────┘ └──►┌───────┐ ┌─────────┐
|
||||
│ │Account├──►│Character│
|
||||
┌──────┐ │ ┌───────┐ ┌──►└───────┘ └─────────┘
|
||||
│Client├─┼──►│Session├──┘
|
||||
└──────┘ │ └───────┘
|
||||
│
|
||||
```
|
||||
|
||||
- `MULTISESSION_MODE=0`: One [Session](../Components/Sessions.md) per [Account](../Components/Accounts.md), routed to one [puppet](../Components/Objects.md). If connecting with a new session/client, it will kick the previous one.
|
||||
- `MULTISESSION_MODE=1`: Multiple sessions per Account, all routed to one puppet. Allows you to control one puppet from multiple client windows.
|
||||
- `MULTISESSION_MODE=2`: Multiple sessions per Account, each routed to a different puppet. This allows for multi-playing.
|
||||
- `MULTISESSION_MODE=3`: Multiple sessions per account, And multiple sessions per puppet. This is full multi-playing, including being able to control each puppet from multiple clients.
|
||||
* `MULTISESSION_MODE=2`: Many sessions per account, one character per session. In this mode, puppeting an Object/Character will link the puppet back only to the particular Session doing the puppeting. That is, input from that Session will make use of the CmdSet of that Object/Character and outgoing messages (such as the result of a `look`) will be passed back only to that puppeting Session. If another Session tries to puppet the same Character, the old Session will automatically un-puppet it. From the player's perspective, this will mean that they can open separate game clients and play a different Character in each using one game account.
|
||||
```
|
||||
│ ┌───────┐
|
||||
┌──────┐ │ ┌───────┐ │Account│ ┌─────────┐
|
||||
│Client├─┼──►│Session├──┐ │ │ ┌►│Character│
|
||||
└──────┘ │ └───────┘ └──┼───────┼──┘ └─────────┘
|
||||
│ │ │
|
||||
┌──────┐ │ ┌───────┐ ┌──┼───────┼──┐ ┌─────────┐
|
||||
│Client├─┼──►│Session├──┘ │ │ └►│Character│
|
||||
└──────┘ │ └───────┘ │ │ └─────────┘
|
||||
│ └───────┘
|
||||
```
|
||||
* `MULTISESSION_MODE=3`: Many sessions per account *and* character. This is the full multi-puppeting mode, where multiple sessions may not only connect to the player account but multiple sessions may also puppet a single character at the same time. From the user's perspective it means one can open multiple client windows, some for controlling different Characters and some that share a Character's input/output like in mode 1. This mode otherwise works the same as mode 2.
|
||||
```
|
||||
│ ┌───────┐
|
||||
┌──────┐ │ ┌───────┐ │Account│ ┌─────────┐
|
||||
│Client├─┼──►│Session├──┐ │ │ ┌►│Character│
|
||||
└──────┘ │ └───────┘ └──┼───────┼──┘ └─────────┘
|
||||
│ │ │
|
||||
┌──────┐ │ ┌───────┐ ┌──┼───────┼──┐
|
||||
│Client├─┼──►│Session├──┘ │ │ └►┌─────────┐
|
||||
└──────┘ │ └───────┘ │ │ │Character│
|
||||
│ │ │ ┌►└─────────┘
|
||||
┌──────┐ │ ┌───────┐ ┌──┼───────┼──┘ ▼
|
||||
│Client├─┼──►│Session├──┘ │ │
|
||||
└──────┘ │ └───────┘ └───────┘
|
||||
│
|
||||
```
|
||||
|
||||
> Note that even if multiple Sessions puppet one Character, there is only ever one instance of that Character.
|
||||
|
||||
Mode `0` is the default and mimics how many legacy codebases work, especially in the DIKU world. The equivalence of higher modes are often 'hacked' into existing servers to allow for players to have multiple characters.
|
||||
|
||||
|
|
|
|||
|
|
@ -250,6 +250,10 @@ div.highlight {
|
|||
border-radius: 5px;
|
||||
}
|
||||
|
||||
div.highlight-shell.notranslate > div.highlight .nb {
|
||||
color: #79ecff !important;
|
||||
}
|
||||
|
||||
|
||||
div.note {
|
||||
background-color: #eee;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue