Reorganize docs into flat folder layout

This commit is contained in:
Griatch 2020-06-17 18:06:41 +02:00
parent 106558cec0
commit 892d8efb93
135 changed files with 34 additions and 1180 deletions

View file

@ -0,0 +1,107 @@
# Accounts
All *users* (real people) that starts a game [Session](Sessions) on Evennia are doing so through an
object called *Account*. The Account object has no in-game representation, it represents a unique
game account. In order to actually get on the game the Account must *puppet* an [Object](Objects)
(normally a [Character](Objects#Character)).
Exactly how many Sessions can interact with an Account and its Puppets at once is determined by
Evennia's [MULTISESSION_MODE](Sessions#Multisession-mode) setting.
Apart from storing login information and other account-specific data, the Account object is what is
chatting on [Channels](Communications). It is also a good place to store [Permissions](Locks) to be
consistent between different in-game characters as well as configuration options. The Account
object also has its own [CmdSet](Command-Sets), the `AccountCmdSet`.
Logged into default evennia, you can use the `ooc` command to leave your current
[character](Objects) and go into OOC mode. You are quite limited in this mode, basically it works
like a simple chat program. It acts as a staging area for switching between Characters (if your
game supports that) or as a safety mode if your Character gets deleted. Use `ic` to attempt to
(re)puppet a Character.
Note that the Account object can have, and often does have, a different set of
[Permissions](Locks#Permissions) 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 create your own Account types
You will usually not want more than one Account typeclass for all new accounts (but you could in
principle create a system that changes an account's typeclass dynamically).
An Evennia Account is, per definition, a Python class that includes `evennia.DefaultAccount` among
its parents. In `mygame/typeclasses/accounts.py` there is an empty class ready for you to modify.
Evennia defaults to using this (it inherits directly from `DefaultAccount`).
Here's an example of modifying the default Account class in code:
```python
# in mygame/typeclasses/accounts.py
from evennia import DefaultAccount
class Account(DefaultAccount): # [...]
at_account_creation(self): "this is called only once, when account is first created"
self.db.real_name = None # this is set later self.db.real_address = None # "
self.db.config_1 = True # default config self.db.config_2 = False # "
self.db.config_3 = 1 # "
# ... whatever else our game needs to know ``` Reload the server with `reload`.
```
... However, if you use `examine *self` (the asterisk makes you examine your Account object rather
than your Character), you won't see your new Attributes yet. This is because `at_account_creation`
is only called the very *first* time the Account is called and your Account object already exists
(any new Accounts that connect will see them though). To update yourself you need to make sure to
re-fire the hook on all the Accounts you have already created. Here is an example of how to do this
using `py`:
``` py [account.at_account_creation() for account in evennia.managers.accounts.all()] ```
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
Beyond those properties assigned to all typeclassed objects (see [Typeclasses](Typeclasses)), the
Account also has the following custom properties:
- `user` - a unique link to a `User` Django object, representing the logged-in user.
- `obj` - an alias for `character`.
- `name` - an alias for `user.username`
- `sessions` - an instance of
[ObjectSessionHandler](github:evennia.objects.objects#objectsessionhandler)
managing all connected Sessions (physical connections) this object listens to (Note: In older
versions of Evennia, this was a list). The so-called `session-id` (used in many places) is found
as
a property `sessid` on each Session instance.
- `is_superuser` (bool: True/False) - if this account is a superuser.
Special handlers:
- `cmdset` - This holds all the current [Commands](Commands) of this Account. By default these are
the commands found in the cmdset defined by `settings.CMDSET_ACCOUNT`.
- `nicks` - This stores and handles [Nicks](Nicks), in the same way as nicks it works on Objects.
For Accounts, nicks are primarily used to store custom aliases for
[Channels](Communications#Channels).
Selection of special methods (see `evennia.DefaultAccount` for details):
- `get_puppet` - get a currently puppeted object connected to the Account and a given session id, if
any.
- `puppet_object` - connect a session to a puppetable Object.
- `unpuppet_object` - disconnect a session from a puppetable Object.
- `msg` - send text to the Account
- `execute_cmd` - runs a command as if this Account did it.
- `search` - search for Accounts.

View file

@ -0,0 +1,395 @@
# Attributes
When performing actions in Evennia it is often important that you store data for later. If you write
a menu system, you have to keep track of the current location in the menu tree so that the player
can give correct subsequent commands. If you are writing a combat system, you might have a
combattant's next roll get easier dependent on if their opponent failed. Your characters will
probably need to store roleplaying-attributes like strength and agility. And so on.
[Typeclassed](Typeclasses) game entities ([Accounts](Accounts), [Objects](Objects),
[Scripts](Scripts) and [Channels](Communications)) always have *Attributes* associated with them.
Attributes are used to store any type of data 'on' such entities. This is different from storing
data in properties already defined on entities (such as `key` or `location`) - these have very
specific names and require very specific types of data (for example you couldn't assign a python
*list* to the `key` property no matter how hard you tried). `Attributes` come into play when you
want to assign arbitrary data to arbitrary names.
**Attributes are _not_ secure by default and any player may be able to change them unless you
[prevent this behavior](Attributes#locking-and-checking-attributes).**
## The .db and .ndb shortcuts
To save persistent data on a Typeclassed object you normally use the `db` (DataBase) operator. Let's
try to save some data to a *Rose* (an [Object](Objects)):
```python
# saving
rose.db.has_thorns = True
# getting it back
is_ouch = rose.db.has_thorns
```
This looks like any normal Python assignment, but that `db` makes sure that an *Attribute* is
created behind the scenes and is stored in the database. Your rose will continue to have thorns
throughout the life of the server now, until you deliberately remove them.
To be sure to save **non-persistently**, i.e. to make sure NOT to create a database entry, you use
`ndb` (NonDataBase). It works in the same way:
```python
# saving
rose.ndb.has_thorns = True
# getting it back
is_ouch = rose.ndb.has_thorns
```
Technically, `ndb` has nothing to do with `Attributes`, despite how similar they look. No
`Attribute` object is created behind the scenes when using `ndb`. In fact the database is not
invoked at all since we are not interested in persistence. There is however an important reason to
use `ndb` to store data rather than to just store variables direct on entities - `ndb`-stored data
is tracked by the server and will not be purged in various cache-cleanup operations Evennia may do
while it runs. Data stored on `ndb` (as well as `db`) will also be easily listed by example the
`@examine` command.
You can also `del` properties on `db` and `ndb` as normal. This will for example delete an
`Attribute`:
```python
del rose.db.has_thorns
```
Both `db` and `ndb` defaults to offering an `all` property on themselves. This returns all
associated attributes or non-persistent properties.
```python
list_of_all_rose_attributes = rose.db.all
list_of_all_rose_ndb_attrs = rose.ndb.all
```
If you use `all` as the name of an attribute, this will be used instead. Later deleting your custom
`all` will return the default behaviour.
## The AttributeHandler
The `.db` and `.ndb` properties are very convenient but if you don't know the name of the Attribute
beforehand they cannot be used. Behind the scenes `.db` actually accesses the `AttributeHandler`
which sits on typeclassed entities as the `.attributes` property. `.ndb` does the same for the
`.nattributes` property.
The handlers have normal access methods that allow you to manage and retrieve `Attributes` and
`NAttributes`:
- `has('attrname')` - this checks if the object has an Attribute with this key. This is equivalent
to doing `obj.db.attrname`.
- `get(...)` - this retrieves the given Attribute. Normally the `value` property of the Attribute is
returned, but the method takes keywords for returning the Attribute object itself. By supplying an
`accessing_object` to the call one can also make sure to check permissions before modifying
anything.
- `add(...)` - this adds a new Attribute to the object. An optional [lockstring](Locks) can be
supplied here to restrict future access and also the call itself may be checked against locks.
- `remove(...)` - Remove the given Attribute. This can optionally be made to check for permission
before performing the deletion. - `clear(...)` - removes all Attributes from object.
- `all(...)` - returns all Attributes (of the given category) attached to this object.
See [this section](Attributes#locking-and-checking-attributes) for more about locking down Attribute
access and editing. The `Nattribute` offers no concept of access control.
Some examples:
```python
import evennia
obj = evennia.search_object("MyObject")
obj.attributes.add("test", "testvalue")
print(obj.db.test) # prints "testvalue"
print(obj.attributes.get("test")) # "
print(obj.attributes.all()) # prints [<AttributeObject>]
obj.attributes.remove("test")
```
## 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#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) is an example of using
Attributes
in this way). To modify this property you need to use the [Attribute
Handler](Attributes#The_Attribute_Handler).
- `strvalue` - this is a separate value field that only accepts strings. This severely limits the
data possible to store, but allows for easier database lookups. This property is usually not used
except when re-using Attributes for some other purpose ([Nicks](Nicks) use it). It is only
accessible via the [Attribute Handler](Attributes#The_Attribute_Handler).
There are also two special properties:
- `attrtype` - this is used internally by Evennia to separate [Nicks](Nicks), from Attributes (Nicks
use Attributes behind the scenes).
- `model` - this is a *natural-key* describing the model this Attribute is attached to. This is on
the form *appname.modelclass*, like `objects.objectdb`. It is used by the Attribute and
NickHandler to quickly sort matches in the database. Neither this nor `attrtype` should normally
need to be modified.
Non-database attributes have no equivalence to `category` nor `strvalue`, `attrtype` or `model`.
## Persistent vs non-persistent
So *persistent* data means that your data will survive a server reboot, whereas with
*non-persistent* data it will not ...
... So why would you ever want to use non-persistent data? The answer is, you don't have to. Most of
the time you really want to save as much as you possibly can. Non-persistent data is potentially
useful in a few situations though.
- You are worried about database performance. Since Evennia caches Attributes very aggressively,
this is not an issue unless you are reading *and* writing to your Attribute very often (like many
times per second). Reading from an already cached Attribute is as fast as reading any Python
property. But even then this is not likely something to worry about: Apart from Evennia's own
caching, modern database systems themselves also cache data very efficiently for speed. Our
default
database even runs completely in RAM if possible, alleviating much of the need to write to disk
during heavy loads.
- A more valid reason for using non-persistent data is if you *want* to lose your state when logging
off. Maybe you are storing throw-away data that are re-initialized at server startup. Maybe you
are implementing some caching of your own. Or maybe you are testing a buggy [Script](Scripts) that
does potentially harmful stuff to your character object. With non-persistent storage you can be
sure
that whatever is messed up, it's nothing a server reboot can't clear up.
- NAttributes have no restrictions at all on what they can store (see next section), since they
don't need to worry about being saved to the database - they work very well for temporary storage.
- You want to implement a fully or partly *non-persistent world*. Who are we to argue with your
grand vision!
## What types of data can I save in an Attribute?
> None of the following affects NAttributes, which does not invoke the database at all. There are no
> restrictions to what can be stored in a NAttribute.
The database doesn't know anything about Python objects, so Evennia must *serialize* Attribute
values into a string representation in order to store it to the database. This is done using the
`pickle` module of Python (the only exception is if you use the `strattr` keyword of the
AttributeHandler to save to the `strvalue` field of the Attribute. In that case you can only save
*strings* which will not be pickled).
It's important to note that when you access the data in an Attribute you are *always* de-serializing
it from the database representation every time. This is because we allow for storing
database-entities in Attributes too. If we cached it as its Python form, we might end up with
situations where the database entity was deleted since we last accessed the Attribute.
De-serializing data with a database-entity in it means querying the database for that object and
making sure it still exists (otherwise it will be set to `None`). Performance-wise this is usually
not a big deal. But if you are accessing the Attribute as part of some big loop or doing a large
amount of reads/writes you should first extract it to a temporary variable, operate on *that* and
then save the result back to the Attribute. If you are storing a more complex structure like a
`dict` or a `list` you should make sure to "disconnect" it from the database before looping over it,
as mentioned in the [Retrieving Mutable Objects](Attributes#retrieving-mutable-objects) section
below.
### Storing single objects
With a single object, we mean anything that is *not iterable*, like numbers, strings or custom class
instances without the `__iter__` method.
* You can generally store any non-iterable Python entity that can be
[pickled](http://docs.python.org/library/pickle.html).
* Single database objects/typeclasses can be stored as any other in the Attribute. These can
normally *not* be pickled, but Evennia will behind the scenes convert them to an internal
representation using their classname, database-id and creation-date with a microsecond precision,
guaranteeing you get the same object back when you access the Attribute later.
* If you *hide* a database object inside a non-iterable custom class (like stored as a variable
inside it), Evennia will not know it's there and won't convert it safely. Storing classes with
such hidden database objects is *not* supported and will lead to errors!
```python
# Examples of valid single-value attribute data:
obj.db.test1 = 23
obj.db.test1 = False
# a database object (will be stored as an internal representation)
obj.db.test2 = myobj
# example of an invalid, "hidden" dbobject
class Invalid(object):
def __init__(self, dbobj):
# no way for Evennia to know this is a dbobj
self.dbobj = dbobj
invalid = Invalid(myobj)
obj.db.invalid = invalid # will cause error!
```
### Storing multiple objects
This means storing objects in a collection of some kind and are examples of *iterables*, pickle-able
entities you can loop over in a for-loop. Attribute-saving supports the following iterables:
* [Tuples](https://docs.python.org/2/library/functions.html#tuple), like `(1,2,"test", <dbobj>)`.
* [Lists](https://docs.python.org/2/tutorial/datastructures.html#more-on-lists), like `[1,2,"test",
<dbobj>]`.
* [Dicts](https://docs.python.org/2/tutorial/datastructures.html#dictionaries), like `{1:2,
"test":<dbobj>]`.
* [Sets](https://docs.python.org/2/tutorial/datastructures.html#sets), like `{1,2,"test",<dbobj>}`.
*
[collections.OrderedDict](https://docs.python.org/2/library/collections.html#collections.OrderedDict),
like `OrderedDict((1,2), ("test", <dbobj>))`.
* [collections.Deque](https://docs.python.org/2/library/collections.html#collections.deque), like
`deque((1,2,"test",<dbobj>))`.
* *Nestings* of any combinations of the above, like lists in dicts or an OrderedDict of tuples, each
containing dicts, etc.
* All other iterables (i.e. entities with the `__iter__` method) will be converted to a *list*.
Since you can use any combination of the above iterables, this is generally not much of a
limitation.
Any entity listed in the [Single object](Attributes#Storing-Single-Objects) section above can be
stored in the iterable.
> As mentioned in the previous section, database entities (aka typeclasses) are not possible to
> pickle. So when storing an iterable, Evennia must recursively traverse the iterable *and all its
> nested sub-iterables* in order to find eventual database objects to convert. This is a very fast
> process but for efficiency you may want to avoid too deeply nested structures if you can.
```python
# examples of valid iterables to store
obj.db.test3 = [obj1, 45, obj2, 67]
# a dictionary
obj.db.test4 = {'str':34, 'dex':56, 'agi':22, 'int':77}
# a mixed dictionary/list
obj.db.test5 = {'members': [obj1,obj2,obj3], 'enemies':[obj4,obj5]}
# a tuple with a list in it
obj.db.test6 = (1,3,4,8, ["test", "test2"], 9)
# a set
obj.db.test7 = set([1,2,3,4,5])
# in-situ manipulation
obj.db.test8 = [1,2,{"test":1}]
obj.db.test8[0] = 4
obj.db.test8[2]["test"] = 5
# test8 is now [4,2,{"test":5}]
```
### Retrieving Mutable objects
A side effect of the way Evennia stores Attributes is that *mutable* iterables (iterables that can
be modified in-place after they were created, which is everything except tuples) are handled by
custom objects called `_SaverList`, `_SaverDict` etc. These `_Saver...` classes behave just like the
normal variant except that they are aware of the database and saves to it whenever new data gets
assigned to them. This is what allows you to do things like `self.db.mylist[7] = val` and be sure
that the new version of list is saved. Without this you would have to load the list into a temporary
variable, change it and then re-assign it to the Attribute in order for it to save.
There is however an important thing to remember. If you retrieve your mutable iterable into another
variable, e.g. `mylist2 = obj.db.mylist`, your new variable (`mylist2`) will *still* be a
`_SaverList`. This means it will continue to save itself to the database whenever it is updated!
```python
obj.db.mylist = [1,2,3,4]
mylist = obj.db.mylist
mylist[3] = 5 # this will also update database
print(mylist) # this is now [1,2,3,5]
print(obj.db.mylist) # this is also [1,2,3,5]
```
To "disconnect" your extracted mutable variable from the database you simply need to convert the
`_Saver...` iterable to a normal Python structure. So to convert a `_SaverList`, you use the
`list()` function, for a `_SaverDict` you use `dict()` and so on.
```python
obj.db.mylist = [1,2,3,4]
mylist = list(obj.db.mylist) # convert to normal list
mylist[3] = 5
print(mylist) # this is now [1,2,3,5]
print(obj.db.mylist) # this is still [1,2,3,4]
```
A further problem comes with *nested mutables*, like a dict containing lists of dicts or something
like that. Each of these nested mutables would be `_Saver*` structures connected to the database and
disconnecting the outermost one of them would not disconnect those nested within. To make really
sure you disonnect a nested structure entirely from the database, Evennia provides a special
function `evennia.utils.dbserialize.deserialize`:
```
from evennia.utils.dbserialize import deserialize
decoupled_mutables = deserialize(nested_mutables)
```
The result of this operation will be a structure only consisting of normal Python mutables (`list`
instead of `_SaverList` and so on).
Remember, this is only valid for *mutable* iterables.
[Immutable](http://en.wikipedia.org/wiki/Immutable) objects (strings, numbers, tuples etc) are
already disconnected from the database from the onset.
```python
obj.db.mytup = (1,2,[3,4])
obj.db.mytup[0] = 5 # this fails since tuples are immutable
# this works but will NOT update database since outermost is a tuple
obj.db.mytup[2][1] = 5
print(obj.db.mytup[2][1]) # this still returns 4, not 5
mytup1 = obj.db.mytup # mytup1 is already disconnected from database since outermost
# iterable is a tuple, so we can edit the internal list as we want
# without affecting the database.
```
> Attributes will fetch data fresh from the database whenever you read them, so
> if you are performing big operations on a mutable Attribute property (such as looping over a list
> or dict) you should make sure to "disconnect" the Attribute's value first and operate on this
> rather than on the Attribute. You can gain dramatic speed improvements to big loops this
> way.
## Locking and checking Attributes
Attributes are normally not locked down by default, but you can easily change that for individual
Attributes (like those that may be game-sensitive in games with user-level building).
First you need to set a *lock string* on your Attribute. Lock strings are specified [Locks](Locks).
The relevant lock types are
- `attrread` - limits who may read the value of the Attribute
- `attredit` - limits who may set/change this Attribute
You cannot use the `db` handler to modify Attribute object (such as setting a lock on them) - The
`db` handler will return the Attribute's *value*, not the Attribute object itself. Instead you use
the AttributeHandler and set it to return the object instead of the value:
```python
lockstring = "attread:all();attredit:perm(Admins)"
obj.attributes.get("myattr", return_obj=True).locks.add(lockstring)
```
Note the `return_obj` keyword which makes sure to return the `Attribute` object so its LockHandler
could be accessed.
A lock is no good if nothing checks it -- and by default Evennia does not check locks on Attributes.
You have to add a check to your commands/code wherever it fits (such as before setting an
Attribute).
```python
# in some command code where we want to limit
# setting of a given attribute name on an object
attr = obj.attributes.get(attrname,
return_obj=True,
accessing_obj=caller,
default=None,
default_access=False)
if not attr:
caller.msg("You cannot edit that Attribute!")
return
# edit the Attribute here
```
The same keywords are available to use with `obj.attributes.set()` and `obj.attributes.remove()`,
those will check for the `attredit` lock type.

View file

@ -0,0 +1,229 @@
# Batch Code Processor
For an introduction and motivation to using batch processors, see [here](Batch-Processors). This
page describes the Batch-*code* processor. The Batch-*command* one is covered [here](Batch-Command-
Processor).
## Basic Usage
The batch-code processor is a superuser-only function, invoked by
> @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
> @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.
## 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:
- `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.
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.
Below is a version of the example file found in `evennia/contrib/tutorial_examples/`.
```python
#
# This is an example batch-code build file for Evennia.
#
#HEADER
# This will be included in all other #CODE blocks
from evennia import create_object, search_object
from evennia.contrib.tutorial_examples import red_button
from typeclasses.objects import Object
limbo = search_object('Limbo')[0]
#CODE
red_button = create_object(red_button.RedButton, key="Red button",
location=limbo, aliases=["button"])
# caller points to the one running the script
caller.msg("A red button was created.")
# importing more code from another batch-code file
#INSERT batch_code_insert
#CODE
table = create_object(Object, key="Blue Table", location=limbo)
chair = create_object(Object, key="Blue Chair", location=limbo)
string = "A %s and %s were created."
if DEBUG:
table.delete()
chair.delete()
string += " Since debug was active, " \
"they were deleted again."
caller.msg(string % (table, chair))
```
This uses Evennia's Python API to create three objects in sequence.
## Debug mode
Try to run the example script with
> @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 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.
> @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!
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
from evennia.contrib.tutorial_examples import red_button
from typeclasses.objects import Object
limbo = search.objects(caller, 'Limbo', global_search=True)[0]
red_button = create.create_object(red_button.RedButton, key="Red button",
location=limbo, aliases=["button"])
# caller points to the one running the script
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
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).
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.
### Safety
Or rather the lack of it. There is a reason only *superusers* are allowed to run the batch-code
processor by default. The code-processor runs **without any Evennia security checks** and allows
full access to Python. If an untrusted party could run the code-processor they could execute
arbitrary python code on your machine, which is potentially a very dangerous thing. If you want to
allow other users to access the batch-code processor you should make sure to run Evennia as a
separate and very limited-access user on your machine (i.e. in a 'jail'). By comparison, the batch-
command processor is much safer since the user running it is still 'inside' the game and can't
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".
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) 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:
caller.ndb.all_rooms = {}
#CODE
# create and store the castle
castle = create_object("rooms.Room", key="Castle")
caller.ndb.all_rooms["castle"] = castle
#CODE
# 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
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.
### 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.

View file

@ -0,0 +1,182 @@
# Batch Command Processor
For an introduction and motivation to using batch processors, see [here](Batch-Processors). This
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
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
with `BATCH_IMPORT_PATH` in your settings. Default folder is (assuming your game is in the `mygame`
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
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
it fail* (there is no universal way for the processor to know what a failure looks like for all
different commands). So keep a close watch on the output, or use *Interactive mode* (see below) to
run the file in a more controlled, gradual manner.
## The batch file
The batch file is a simple plain-text file containing Evennia commands. Just like you would write
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).
- 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.
Below is a version of the example file found in `evennia/contrib/tutorial_examples/batch_cmds.ev`.
```bash
#
# This is an example batch build file for Evennia.
#
# This creates a red button
@create button:tutorial_examples.red_button.RedButton
# (This comment ends input for @create)
# Next command. Let's create something.
@set button/desc =
This is a large red button. Now and then
it flashes in an evil, yet strangely tantalizing way.
A big sign sits next to it. It says:
-----------
Press me!
-----------
... It really begs to be pressed! You
know you want to!
# This inserts the commands from another batch-cmd file named
# batch_insert_file.ev.
#INSERT examples.batch_insert_file
# (This ends the @set command). Note that single line breaks
# and extra whitespace in the argument are ignored. Empty lines
# translate into line breaks in the output.
# Now let's place the button where it belongs (let's say limbo #2 is
# the evil lair in our example)
@teleport #2
# (This comments ends the @teleport command.)
# Now we drop it so others can see it.
# The very last command in the file needs not be ended with #.
drop button
```
To test this, run `@batchcommand` on the file:
> @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.
> 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.
> @batchcommand/interactive tutorial_examples.batch_cmds
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!
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.
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.
## 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 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.
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)*: 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.
## Assorted notes
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))
- [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](http://www.vim.org/) users can use amfl's [vim-evennia](https://github.com/amfl/vim-evennia)
mode instead, see its readme for install instructions.

View file

@ -0,0 +1,82 @@
# Batch Processors
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.
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
just list in-game commands in a text file. The code-processor on the other hand is much more
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)
- The [Batch Code Processor](Batch-Code-Processor)
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](http://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.
First, we should make it clear that Evennia itself handles international characters just fine. It
(and Django) uses [unicode](http://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*.
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.
More help with encodings can be found in the entry [Text Encodings](Text-Encodings) and also in the
Wikipedia article [here](http://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
allowed.

View file

@ -0,0 +1,82 @@
# Bootstrap Components and Utilities
Bootstrap provides many utilities and components you can use when customizing Evennia's web
presence. We'll go over a few examples here that you might find useful.
> Please take a look at either [the basic web tutorial](Add-a-simple-new-web-page) or [the web
character view tutorial](Web-Character-View-Tutorial)
> to get a feel for how to add pages to Evennia's website to test these examples.
## General Styling
Bootstrap provides base styles for your site. These can be customized through CSS, but the default
styles are intended to provide a consistent, clean look for sites.
### Color
Most elements can be styled with default colors. [Take a look at the
documentation](https://getbootstrap.com/docs/4.0/utilities/colors/) to learn more about these colors
- suffice to say, adding a class of text-* or bg-*, for instance, text-primary, sets the text color
or background color.
### Borders
Simply adding a class of 'border' to an element adds a border to the element. For more in-depth
info, please [read the documentation on
borders.](https://getbootstrap.com/docs/4.0/utilities/borders/).
```
<span class="border border-dark"></span>
```
You can also easily round corners just by adding a class.
```
<img src="..." class="rounded" />
```
### Spacing
Bootstrap provides classes to easily add responsive margin and padding. Most of the time, you might
like to add margins or padding through CSS itself - however these classes are used in the default
Evennia site. [Take a look at the docs](https://getbootstrap.com/docs/4.0/utilities/spacing/) to
learn more.
***
## Components
### Buttons
[Buttons](https://getbootstrap.com/docs/4.0/components/buttons/) in Bootstrap are very easy to use -
button styling can be added to `<button>`, `<a>`, and `<input>` elements.
```
<a class="btn btn-primary" href="#" role="button">I'm a Button</a>
<button class="btn btn-primary" type="submit">Me too!</button>
<input class="btn btn-primary" type="button" value="Button">
<input class="btn btn-primary" type="submit" value="Also a Button">
<input class="btn btn-primary" type="reset" value="Button as Well">
```
### Cards
[Cards](https://getbootstrap.com/docs/4.0/components/card/) provide a container for other elements
that stands out from the rest of the page. The "Accounts", "Recently Connected", and "Database
Stats" on the default webpage are all in cards. Cards provide quite a bit of formatting options -
the following is a simple example, but read the documentation or look at the site's source for more.
```
<div class="card">
<div class="card-body">
<h4 class="card-title">Card title</h4>
<h6 class="card-subtitle mb-2 text-muted">Card subtitle</h6>
<p class="card-text">Fancy, isn't it?</p>
<a href="#" class="card-link">Card link</a>
</div>
</div>
```
### Jumbotron
[Jumbotrons](https://getbootstrap.com/docs/4.0/components/jumbotron/) are useful for featuring an
image or tagline for your game. They can flow with the rest of your content or take up the full
width of the page - Evennia's base site uses the former.
```
<div class="jumbotron jumbotron-fluid">
<div class="container">
<h1 class="display-3">Full Width Jumbotron</h1>
<p class="lead">Look at the source of the default Evennia page for a regular Jumbotron</p>
</div>
</div>
```
### Forms
[Forms](https://getbootstrap.com/docs/4.0/components/forms/) are highly customizable with Bootstrap.
For a more in-depth look at how to use forms and their styles in your own Evennia site, please read
over [the web character gen tutorial.](Web-Character-Generation)

View file

@ -0,0 +1,297 @@
# Coding Utils
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.
## Searching
A common thing to do is to search for objects. There it's easiest to use the `search` method defined
on all objects. This will search for objects in the same location and inside the self object:
```python
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").
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_*`.
```python
from evennia import search_object
obj = search_object(objname)
```
- [evennia.search_account](../wiki/evennia.accounts.manager#accountdbmanagersearch_account)
- [evennia.search_object](../wiki/evennia.objects.manager#objectdbmanagersearch_object)
- [evennia.search_object_by_tag](../wiki/evennia.utils.search#search_object_by_tag)
- [evennia.search_script](../wiki/evennia.scripts.manager#scriptdbmanagersearch_script)
- [evennia.search_channel](../wiki/evennia.comms.managers#channeldbmanagersearch_channel)
- [evennia.search_message](../wiki/evennia.comms.managers#msgmanagersearch_message)
- [evennia.search_help](../wiki/evennia.help.manager#helpentrymanagersearch_help)
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).
```python
import evennia
myobj = evennia.create_objects("game.gamesrc.objects.myobj.MyObj", key="MyObj")
```
- [evennia.create_account](../wiki/evennia.utils.create#create_account)
- [evennia.create_object](../wiki/evennia.utils.create#create_object)
- [evennia.create_script](../wiki/evennia.utils.create#create_script)
- [evennia.create_channel](../wiki/evennia.utils.create#create_channel)
- [evennia.create_help_entry](../wiki/evennia.utils.create#create_help_entry)
- [evennia.create_message](../wiki/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.
## 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.
```python
from evennia import logger
#
logger.log_err("This is an Error!")
logger.log_warn("This is a Warning!")
logger.log_info("This is normal information")
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
kill the server.
```python
try:
# [some code that may fail...]
except Exception:
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.
```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`.
## Time Utilities
### Game time
Evennia tracks the current server time. You can access this time via the `evennia.gametime`
shortcut:
```python
from evennia import gametime
# all the functions below return times in seconds).
# total running time of the server
runtime = gametime.runtime()
# time since latest hard reboot (not including reloads)
uptime = gametime.uptime()
# server epoch (its start time)
server_epoch = gametime.server_epoch()
# in-game epoch (this can be set by `settings.TIME_GAME_EPOCH`.
# If not, the server epoch is used.
game_epoch = gametime.game_epoch()
# in-game time passed since time started running
gametime = gametime.gametime()
# in-game time plus game epoch (i.e. the current in-game
# time stamp)
gametime = gametime.gametime(absolute=True)
# reset the game time (back to game epoch)
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](github:evennia.utils.gametime#schedule) function:
```python
import evennia
def church_clock:
limbo = evennia.search_object(key="Limbo")
limbo.msg_contents("The church clock chimes two.")
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:
- style 0 - `5d:45m:12s` (standard colon output)
- style 1 - `5d` (shows only the longest time unit)
- style 2 - `5 days, 45 minutes` (full format, ignores seconds)
- style 3 - `5 days, 45 minutes, 12 seconds` (full format, with seconds)
### utils.delay()
```python
from evennia import utils
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)
# 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).
The `deferred` return object can usually be ignored, but calling its `.cancel()` method will abort
the delay prematurely.
`utils.delay` is the lightest form of delayed call in Evennia. For other way to create time-bound
tasks, see the [TickerHandler](TickerHandler) and [Scripts](Scripts).
> 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
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](http://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) 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
if (utils.inherits_from(obj, "typeclasses.objects.animals.Animal"):
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-
complete* selection of text utilities found in `evennia/utils/utils.py` (shortcut `evennia.utils`).
If nothing else it can be good to look here before starting to develop a solution of your own.
### 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.
```python
outtxt = fill(intxt, width=78, indent=4)
```
### utils.crop()
This function will crop a very long line, adding a suffix to show the line actually continues. This
can be useful in listings when showing multiple lines would mess up things.
```python
intxt = "This is a long text that we want to crop."
outtxt = crop(intxt, width=19, suffix="[...]")
# outtxt is now "This is a long text[...]"
```
### 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.
```python
#python code is entered at a given indentation
intxt = """
This is an example text that will end
up with a lot of whitespace on the left.
It also has indentations of
its own."""
outtxt = dedent(intxt)
# outtxt will now retain all internal indentation
# but be shifted all the way to the left.
```
Normally you do the dedent in the display code (this is for example how the help system homogenizes
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.
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](Text-Encodings) for more info.
### Ansi Coloring Tools
- [evennia.ansi](api:evennia.utils.ansi)
## Display utilities
### Making ascii tables
The [EvTable](github:evennia.utils.evtable#evtable) class (`evennia/utils/evtable.py`) can be used
to create correctly formatted text tables. There is also
[EvForm](github: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](github:evennia.utils.evmenu#evmenu)

View file

@ -0,0 +1,376 @@
# Command Sets
Command Sets are intimately linked with [Commands](Commands) and you should be familiar with
Commands before reading this page. The two pages were split for ease of reading.
A *Command Set* (often referred to as a CmdSet or cmdset) is the basic unit for storing one or more
*Commands*. A given Command can go into any number of different command sets. Storing Command
classes in a command set is the way to make commands available to use in your game.
When storing a CmdSet on an object, you will make the commands in that command set available to the
object. An example is the default command set stored on new Characters. This command set contains
all the useful commands, from `look` and `inventory` to `@dig` and `@reload`
([permissions](Locks#Permissions) then limit which players may use them, but that's a separate
topic).
When an account enters a command, cmdsets from the Account, Character, its location, and elsewhere
are pulled together into a *merge stack*. This stack is merged together in a specific order to
create a single "merged" cmdset, representing the pool of commands available at that very moment.
An example would be a `Window` object that has a cmdset with two commands in it: `look through
window` and `open window`. The command set would be visible to players in the room with the window,
allowing them to use those commands only there. You could imagine all sorts of clever uses of this,
like a `Television` object which had multiple commands for looking at it, switching channels and so
on. The tutorial world included with Evennia showcases a dark room that replaces certain critical
commands with its own versions because the Character cannot see.
If you want a quick start into defining your first commands and using them with command sets, you
can head over to the [Adding Command Tutorial](Adding-Command-Tutorial) which steps through things
without the explanations.
## Defining Command Sets
A CmdSet is, as most things in Evennia, defined as a Python class inheriting from the correct parent
(`evennia.CmdSet`, which is a shortcut to `evennia.commands.cmdset.CmdSet`). The CmdSet class only
needs to define one method, called `at_cmdset_creation()`. All other class parameters are optional,
but are used for more advanced set manipulation and coding (see the [merge rules](Command-
Sets#merge-rules) section).
```python
# file mygame/commands/mycmdset.py
from evennia import CmdSet
# this is a theoretical custom module with commands we
# created previously: mygame/commands/mycommands.py
from commands import mycommands
class MyCmdSet(CmdSet):
def at_cmdset_creation(self):
"""
The only thing this method should need
to do is to add commands to the set.
"""
self.add(mycommands.MyCommand1())
self.add(mycommands.MyCommand2())
self.add(mycommands.MyCommand3())
```
The CmdSet's `add()` method can also take another CmdSet as input. In this case all the commands
from that CmdSet will be appended to this one as if you added them line by line:
```python
def at_cmdset_creation():
...
self.add(AdditionalCmdSet) # adds all command from this set
...
```
If you added your command to an existing cmdset (like to the default cmdset), that set is already
loaded into memory. You need to make the server aware of the code changes:
```
@reload
```
You should now be able to use the command.
If you created a new, fresh cmdset, this must be added to an object in order to make the commands
within available. A simple way to temporarily test a cmdset on yourself is use the `@py` command to
execute a python snippet:
```python
@py self.cmdset.add('commands.mycmdset.MyCmdSet')
```
This will stay with you until you `@reset` or `@shutdown` the server, or you run
```python
@py self.cmdset.delete('commands.mycmdset.MyCmdSet')
```
In the example above, a specific Cmdset class is removed. Calling `delete` without arguments will
remove the latest added cmdset.
> Note: Command sets added using `cmdset.add` are, by default, *not* persistent in the database.
If you want the cmdset to survive a reload, you can do:
```
@py self.cmdset.add(commands.mycmdset.MyCmdSet, permanent=True)
```
Or you could add the cmdset as the *default* cmdset:
```
@py self.cmdset.add_default(commands.mycmdset.MyCmdSet)
```
An object can only have one "default" cmdset (but can also have none). This is meant as a safe fall-
back even if all other cmdsets fail or are removed. It is always persistent and will not be affected
by `cmdset.delete()`. To remove a default cmdset you must explicitly call `cmdset.remove_default()`.
Command sets are often added to an object in its `at_object_creation` method. For more examples of
adding commands, read the [Step by step tutorial](Adding-Command-Tutorial). Generally you can
customize which command sets are added to your objects by using `self.cmdset.add()` or
`self.cmdset.add_default()`.
> Important: Commands are identified uniquely by key *or* alias (see [Commands](Commands)). If any
overlap exists, two commands are considered identical. Adding a Command to a command set that
already has an identical command will *replace* the previous command. This is very important. You
must take this behavior into account when attempting to overload any default Evennia commands with
your own. Otherwise, you may accidentally "hide" your own command in your command set when adding a
new one that has a matching alias.
### Properties on Command Sets
There are several extra flags that you can set on CmdSets in order to modify how they work. All are
optional and will be set to defaults otherwise. Since many of these relate to *merging* cmdsets,
you might want to read the [Adding and Merging Command Sets](Command-Sets#adding-and-merging-
command-sets) section for some of these to make sense.
- `key` (string) - an identifier for the cmdset. This is optional, but should be unique. It is used
for display in lists, but also to identify special merging behaviours using the `key_mergetype`
dictionary below.
- `mergetype` (string) - allows for one of the following string values: "*Union*", "*Intersect*",
"*Replace*", or "*Remove*".
- `priority` (int) - This defines the merge order of the merge stack - cmdsets will merge in rising
order of priority with the highest priority set merging last. During a merger, the commands from the
set with the higher priority will have precedence (just what happens depends on the [merge
type](Command-Sets#adding-and-merging-command-sets)). If priority is identical, the order in the
merge stack determines preference. The priority value must be greater or equal to `-100`. Most in-
game sets should usually have priorities between `0` and `100`. Evennia default sets have priorities
as follows (these can be changed if you want a different distribution):
- EmptySet: `-101` (should be lower than all other sets)
- SessionCmdSet: `-20`
- AccountCmdSet: `-10`
- CharacterCmdSet: `0`
- ExitCmdSet: ` 101` (generally should always be available)
- ChannelCmdSet: `101` (should usually always be available) - since exits never accept
arguments, there is no collision between exits named the same as a channel even though the commands
"collide".
- `key_mergetype` (dict) - a dict of `key:mergetype` pairs. This allows this cmdset to merge
differently with certain named cmdsets. If the cmdset to merge with has a `key` matching an entry in
`key_mergetype`, it will not be merged according to the setting in `mergetype` but according to the
mode in this dict. Please note that this is more complex than it may seem due to the [merge
order](Command-Sets#adding-and-merging-command-sets) of command sets. Please review that section
before using `key_mergetype`.
- `duplicates` (bool/None default `None`) - this determines what happens when merging same-priority
cmdsets containing same-key commands together. The`dupicate` option will *only* apply when merging
the cmdset with this option onto one other cmdset with the same priority. The resulting cmdset will
*not* retain this `duplicate` setting.
- `None` (default): No duplicates are allowed and the cmdset being merged "onto" the old one
will take precedence. The result will be unique commands. *However*, the system will assume this
value to be `True` for cmdsets on Objects, to avoid dangerous clashes. This is usually the safe bet.
- `False`: Like `None` except the system will not auto-assume any value for cmdsets defined on
Objects.
- `True`: Same-named, same-prio commands will merge into the same cmdset. This will lead to a
multimatch error (the user will get a list of possibilities in order to specify which command they
meant). This is is useful e.g. for on-object cmdsets (example: There is a `red button` and a `green
button` in the room. Both have a `press button` command, in cmdsets with the same priority. This
flag makes sure that just writing `press button` will force the Player to define just which object's
command was intended).
- `no_objs` this is a flag for the cmdhandler that builds the set of commands available at every
moment. It tells the handler not to include cmdsets from objects around the account (nor from rooms
or inventory) when building the merged set. Exit commands will still be included. This option can
have three values:
- `None` (default): Passthrough of any value set explicitly earlier in the merge stack. If never
set explicitly, this acts as `False`.
- `True`/`False`: Explicitly turn on/off. If two sets with explicit `no_objs` are merged,
priority determines what is used.
- `no_exits` - this is a flag for the cmdhandler that builds the set of commands available at every
moment. It tells the handler not to include cmdsets from exits. This flag can have three values:
- `None` (default): Passthrough of any value set explicitly earlier in the merge stack. If
never set explicitly, this acts as `False`.
- `True`/`False`: Explicitly turn on/off. If two sets with explicit `no_exits` are merged,
priority determines what is used.
- `no_channels` (bool) - this is a flag for the cmdhandler that builds the set of commands available
at every moment. It tells the handler not to include cmdsets from available in-game channels. This
flag can have three values:
- `None` (default): Passthrough of any value set explicitly earlier in the merge stack. If
never set explicitly, this acts as `False`.
- `True`/`False`: Explicitly turn on/off. If two sets with explicit `no_channels` are merged,
priority determines what is used.
## Command Sets Searched
When a user issues a command, it is matched against the [merged](Command-Sets#adding-and-merging-
command-sets) command sets available to the player at the moment. Which those are may change at any
time (such as when the player walks into the room with the `Window` object described earlier).
The currently valid command sets are collected from the following sources:
- The cmdsets stored on the currently active [Session](Sessions). Default is the empty
`SessionCmdSet` with merge priority `-20`.
- The cmdsets defined on the [Account](Accounts). Default is the AccountCmdSet with merge priority
`-10`.
- All cmdsets on the Character/Object (assuming the Account is currently puppeting such a
Character/Object). Merge priority `0`.
- The cmdsets of all objects carried by the puppeted Character (checks the `call` lock). Will not be
included if `no_objs` option is active in the merge stack.
- The cmdsets of the Character's current location (checks the `call` lock). Will not be included if
`no_objs` option is active in the merge stack.
- The cmdsets of objects in the current location (checks the `call` lock). Will not be included if
`no_objs` option is active in the merge stack.
- The cmdsets of Exits in the location. Merge priority `+101`. Will not be included if `no_exits`
*or* `no_objs` option is active in the merge stack.
- The [channel](Communications) cmdset containing commands for posting to all channels the account
or character is currently connected to. Merge priority `+101`. Will not be included if `no_channels`
option is active in the merge stack.
Note that an object does not *have* to share its commands with its surroundings. A Character's
cmdsets should not be shared for example, or all other Characters would get multi-match errors just
by being in the same room. The ability of an object to share its cmdsets is managed by its `call`
[lock](Locks). For example, [Character objects](Objects) defaults to `call:false()` so that any
cmdsets on them can only be accessed by themselves, not by other objects around them. Another
example might be to lock an object with `call:inside()` to only make their commands available to
objects inside them, or `cmd:holds()` to make their commands available only if they are held.
## Adding and Merging Command Sets
*Note: This is an advanced topic. It's very useful to know about, but you might want to skip it if
this is your first time learning about commands.*
CmdSets have the special ability that they can be *merged* together into new sets. Which of the
ingoing commands end up in the merged set is defined by the *merge rule* and the relative
*priorities* of the two sets. Removing the latest added set will restore things back to the way it
was before the addition.
CmdSets are non-destructively stored in a stack inside the cmdset handler on the object. This stack
is parsed to create the "combined" cmdset active at the moment. CmdSets from other sources are also
included in the merger such as those on objects in the same room (like buttons to press) or those
introduced by state changes (such as when entering a menu). The cmdsets are all ordered after
priority and then merged together in *reverse order*. That is, the higher priority will be merged
"onto" lower-prio ones. By defining a cmdset with a merge-priority between that of two other sets,
you will make sure it will be merged in between them.
The very first cmdset in this stack is called the *Default cmdset* and is protected from accidental
deletion. Running `obj.cmdset.delete()` will never delete the default set. Instead one should add
new cmdsets on top of the default to "hide" it, as described below. Use the special
`obj.cmdset.delete_default()` only if you really know what you are doing.
CmdSet merging is an advanced feature useful for implementing powerful game effects. Imagine for
example a player entering a dark room. You don't want the player to be able to find everything in
the room at a glance - maybe you even want them to have a hard time to find stuff in their backpack!
You can then define a different CmdSet with commands that override the normal ones. While they are
in the dark room, maybe the `look` and `inv` commands now just tell the player they cannot see
anything! Another example would be to offer special combat commands only when the player is in
combat. Or when being on a boat. Or when having taken the super power-up. All this can be done on
the fly by merging command sets.
### Merge Rules
Basic rule is that command sets are merged in *reverse priority order*. That is, lower-prio sets are
merged first and higher prio sets are merged "on top" of them. Think of it like a layered cake with
the highest priority on top.
To further understand how sets merge, we need to define some examples. Let's call the first command
set **A** and the second **B**. We assume **B** is the command set already active on our object and
we will merge **A** onto **B**. In code terms this would be done by `object.cdmset.add(A)`.
Remember, B is already active on `object` from before.
We let the **A** set have higher priority than **B**. A priority is simply an integer number. As
seen in the list above, Evennia's default cmdsets have priorities in the range `-101` to `120`. You
are usually safe to use a priority of `0` or `1` for most game effects.
In our examples, both sets contain a number of commands which we'll identify by numbers, like `A1,
A2` for set **A** and `B1, B2, B3, B4` for **B**. So for that example both sets contain commands
with the same keys (or aliases) "1" and "2" (this could for example be "look" and "get" in the real
game), whereas commands 3 and 4 are unique to **B**. To describe a merge between these sets, we
would write `A1,A2 + B1,B2,B3,B4 = ?` where `?` is a list of commands that depend on which merge
type **A** has, and which relative priorities the two sets have. By convention, we read this
statement as "New command set **A** is merged onto the old command set **B** to form **?**".
Below are the available merge types and how they work. Names are partly borrowed from [Set
theory](http://en.wikipedia.org/wiki/Set_theory).
- **Union** (default) - The two cmdsets are merged so that as many commands as possible from each
cmdset ends up in the merged cmdset. Same-key commands are merged by priority.
# Union
A1,A2 + B1,B2,B3,B4 = A1,A2,B3,B4
- **Intersect** - Only commands found in *both* cmdsets (i.e. which have the same keys) end up in
the merged cmdset, with the higher-priority cmdset replacing the lower one's commands.
# Intersect
A1,A3,A5 + B1,B2,B4,B5 = A1,A5
- **Replace** - The commands of the higher-prio cmdset completely replaces the lower-priority
cmdset's commands, regardless of if same-key commands exist or not.
# Replace
A1,A3 + B1,B2,B4,B5 = A1,A3
- **Remove** - The high-priority command sets removes same-key commands from the lower-priority
cmdset. They are not replaced with anything, so this is a sort of filter that prunes the low-prio
set using the high-prio one as a template.
# Remove
A1,A3 + B1,B2,B3,B4,B5 = B2,B4,B5
Besides `priority` and `mergetype`, a command-set also takes a few other variables to control how
they merge:
- `duplicates` (bool) - determines what happens when two sets of equal priority merge. Default is
that the new set in the merger (i.e. **A** above) automatically takes precedence. But if
*duplicates* is true, the result will be a merger with more than one of each name match. This will
usually lead to the player receiving a multiple-match error higher up the road, but can be good for
things like cmdsets on non-player objects in a room, to allow the system to warn that more than one
'ball' in the room has the same 'kick' command defined on it and offer a chance to select which
ball to kick ... Allowing duplicates only makes sense for *Union* and *Intersect*, the setting is
ignored for the other mergetypes.
- `key_mergetypes` (dict) - allows the cmdset to define a unique mergetype for particular cmdsets,
identified by their cmdset `key`. Format is `{CmdSetkey:mergetype}`. Example:
`{'Myevilcmdset','Replace'}` which would make sure for this set to always use 'Replace' on the
cmdset with the key `Myevilcmdset` only, no matter what the main `mergetype` is set to.
> Warning: The `key_mergetypes` dictionary *can only work on the cmdset we merge onto*. When using
`key_mergetypes` it is thus important to consider the merge priorities - you must make sure that you
pick a priority *between* the cmdset you want to detect and the next higher one, if any. That is, if
we define a cmdset with a high priority and set it to affect a cmdset that is far down in the merge
stack, we would not "see" that set when it's time for us to merge. Example: Merge stack is
`A(prio=-10), B(prio=-5), C(prio=0), D(prio=5)`. We now merge a cmdset `E(prio=10)` onto this stack,
with a `key_mergetype={"B":"Replace"}`. But priorities dictate that we won't be merged onto B, we
will be merged onto E (which is a merger of the lower-prio sets at this point). Since we are merging
onto E and not B, our `key_mergetype` directive won't trigger. To make sure it works we must make
sure we merge onto B. Setting E's priority to, say, -4 will make sure to merge it onto B and affect
it appropriately.
More advanced cmdset example:
```python
from commands import mycommands
class MyCmdSet(CmdSet):
key = "MyCmdSet"
priority = 4
mergetype = "Replace"
key_mergetypes = {'MyOtherCmdSet':'Union'}
def at_cmdset_creation(self):
"""
The only thing this method should need
to do is to add commands to the set.
"""
self.add(mycommands.MyCommand1())
self.add(mycommands.MyCommand2())
self.add(mycommands.MyCommand3())
```
### Assorted Notes
It is very important to remember that two commands are compared *both* by their `key` properties
*and* by their `aliases` properties. If either keys or one of their aliases match, the two commands
are considered the *same*. So consider these two Commands:
- A Command with key "kick" and alias "fight"
- A Command with key "punch" also with an alias "fight"
During the cmdset merging (which happens all the time since also things like channel commands and
exits are merged in), these two commands will be considered *identical* since they share alias. It
means only one of them will remain after the merger. Each will also be compared with all other
commands having any combination of the keys and/or aliases "kick", "punch" or "fight".
... So avoid duplicate aliases, it will only cause confusion.

View file

@ -0,0 +1,663 @@
# Commands
Commands are intimately linked to [Command Sets](Command-Sets) and you need to read that page too to
be familiar with how the command system works. The two pages were split for easy reading.
The basic way for users to communicate with the game is through *Commands*. These can be commands
directly related to the game world such as *look*, *get*, *drop* and so on, or administrative
commands such as *examine* or *@dig*.
The [default commands](Default-Command-Help) coming with Evennia are 'MUX-like' in that they use @
for admin commands, support things like switches, syntax with the '=' symbol etc, but there is
nothing that prevents you from implementing a completely different command scheme for your game. You
can find the default commands in `evennia/commands/default`. You should not edit these directly -
they will be updated by the Evennia team as new features are added. Rather you should look to them
for inspiration and inherit your own designs from them.
There are two components to having a command running - the *Command* class and the [Command
Set](Command-Sets) (command sets were split into a separate wiki page for ease of reading).
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). There is also a step-by-step [Adding Command
Tutorial](Adding-Command-Tutorial) that will get you started quickly without the extra explanations.
## Defining Commands
All commands are implemented as normal Python classes inheriting from the base class `Command`
(`evennia.Command`). You will find that this base class is very "bare". The default commands of
Evennia actually inherit from a child of `Command` called `MuxCommand` - this is the class that
knows all the mux-like syntax like `/switches`, splitting by "=" etc. Below we'll avoid mux-
specifics and use the base `Command` class directly.
```python
# basic Command definition
from evennia import Command
class MyCmd(Command):
"""
This is the help-text for the command
"""
key = "mycommand"
def parse(self):
# parsing the command line here
def func(self):
# executing the command here
```
Here is a minimalistic command with no custom parsing:
```python
from evennia import Command
class CmdEcho(Command):
key = "echo"
def func(self):
# echo the caller's input back to the caller
self.caller.msg("Echo: {}".format(self.args)
```
You define a new command by assigning a few class-global properties on your inherited class and
overloading one or two hook functions. The full gritty mechanic behind how commands work are found
towards the end of this page; for now you only need to know that the command handler creates an
instance of this class and uses that instance whenever you use this command - it also dynamically
assigns the new command instance a few useful properties that you can assume to always be available.
### Who is calling the command?
In Evennia there are three types of objects that may call the command. It is important to be aware
of this since this will also assign appropriate `caller`, `session`, `sessid` and `account`
properties on the command body at runtime. Most often the calling type is `Session`.
* A [Session](Sessions). This is by far the most common case when a user is entering a command in
their client.
* `caller` - this is set to the puppeted [Object](Objects) if such an object exists. If no
puppet is found, `caller` is set equal to `account`. Only if an Account is not found either (such as
before being logged in) will this be set to the Session object itself.
* `session` - a reference to the [Session](Sessions) object itself.
* `sessid` - `sessid.id`, a unique integer identifier of the session.
* `account` - the [Account](Accounts) object connected to this Session. None if not logged in.
* An [Account](Accounts). This only happens if `account.execute_cmd()` was used. No Session
information can be obtained in this case.
* `caller` - this is set to the puppeted Object if such an object can be determined (without
Session info this can only be determined in `MULTISESSION_MODE=0` or `1`). If no puppet is found,
this is equal to `account`.
* `session` - `None*`
* `sessid` - `None*`
* `account` - Set to the Account object.
* An [Object](Objects). This only happens if `object.execute_cmd()` was used (for example by an
NPC).
* `caller` - This is set to the calling Object in question.
* `session` - `None*`
* `sessid` - `None*`
* `account` - `None`
> `*)`: There is a way to make the Session available also inside tests run directly on Accounts and
Objects, and that is to pass it to `execute_cmd` like so: `account.execute_cmd("...",
session=<Session>)`. Doing so *will* make the `.session` and `.sessid` properties available in the
command.
### Properties assigned to the command instance at run-time
Let's say account *Bob* with a character *BigGuy* enters the command *look at sword*. After the
system having successfully identified this as the "look" command and determined that BigGuy really
has access to a command named `look`, it chugs the `look` command class out of storage and either
loads an existing Command instance from cache or creates one. After some more checks it then assigns
it the following properties:
- `caller` - The character BigGuy, in this example. This is a reference to the object executing the
command. The value of this depends on what type of object is calling the command; see the previous
section.
- `session` - the [Session](Sessions) Bob uses to connect to the game and control BigGuy (see also
previous section).
- `sessid` - the unique id of `self.session`, for quick lookup.
- `account` - the [Account](Accounts) Bob (see previous section).
- `cmdstring` - the matched key for the command. This would be *look* in our example.
- `args` - this is the rest of the string, except the command name. So if the string entered was
*look at sword*, `args` would be " *at sword*". Note the space kept - Evennia would correctly
interpret `lookat sword` too. This is useful for things like `/switches` that should not use space.
In the `MuxCommand` class used for default commands, this space is stripped. Also see the
`arg_regex` property if you want to enforce a space to make `lookat sword` give a command-not-found
error.
- `obj` - the game [Object](Objects) on which this command is defined. This need not be the caller,
but since `look` is a common (default) command, this is probably defined directly on *BigGuy* - so
`obj` will point to BigGuy. Otherwise `obj` could be an Account or any interactive object with
commands defined on it, like in the example of the "check time" command defined on a "Clock" object.
- `cmdset` - this is a reference to the merged CmdSet (see below) from which this command was
matched. This variable is rarely used, it's main use is for the [auto-help system](Help-
System#command-auto-help-system) (*Advanced note: the merged cmdset need NOT be the same as
`BigGuy.cmdset`. The merged set can be a combination of the cmdsets from other objects in the room,
for example*).
- `raw_string` - this is the raw input coming from the user, without stripping any surrounding
whitespace. The only thing that is stripped is the ending newline marker.
#### Other useful utility methods:
- `.get_help(caller, cmdset)` - Get the help entry for this command. By default the arguments are
not
used, but they could be used to implement alternate help-display systems.
- `.client_width()` - Shortcut for getting the client's screen-width. Note that not all clients will
truthfully report this value - that case the `settings.DEFAULT_SCREEN_WIDTH` will be returned.
- `.styled_table(*args, **kwargs)` - This returns an [EvTable](api:evennia.utils#module-
evennia.utils.evtable) styled based on the
session calling this command. The args/kwargs are the same as for EvTable, except styling defaults
are set.
- `.styled_header`, `_footer`, `separator` - These will produce styled decorations for
display to the user. They are useful for creating listings and forms with colors adjustable per-
user.
### Defining your own command classes
Beyond the properties Evennia always assigns to the command at run-time (listed above), your job is
to define the following class properties:
- `key` (string) - the identifier for the command, like `look`. This should (ideally) be unique. A
key can consist of more than one word, like "press button" or "pull left lever". Note that *both*
`key` and `aliases` below determine the identity of a command. So two commands are considered if
either matches. This is important for merging cmdsets described below.
- `aliases` (optional list) - a list of alternate names for the command (`["glance", "see", "l"]`).
Same name rules as for `key` applies.
- `locks` (string) - a [lock definition](Locks), usually on the form `cmd:<lockfuncs>`. Locks is a
rather big topic, so until you learn more about locks, stick to giving the lockstring `"cmd:all()"`
to make the command available to everyone (if you don't provide a lock string, this will be assigned
for you).
- `help_category` (optional string) - setting this helps to structure the auto-help into categories.
If none is set, this will be set to *General*.
- `save_for_next` (optional boolean). This defaults to `False`. If `True`, a copy of this command
object (along with any changes you have done to it) will be stored by the system and can be accessed
by the next command by retrieving `self.caller.ndb.last_cmd`. The next run command will either clear
or replace the storage.
- `arg_regex` (optional raw string): Used to force the parser to limit itself and tell it when the
command-name ends and arguments begin (such as requiring this to be a space or a /switch). This is
done with a regular expression. [See the arg_regex section](Commands#on-arg_regex) for the details.
- `auto_help` (optional boolean). Defaults to `True`. This allows for turning off the [auto-help
system](Help-System#command-auto-help-system) on a per-command basis. This could be useful if you
either want to write your help entries manually or hide the existence of a command from `help`'s
generated list.
- `is_exit` (bool) - this marks the command as being used for an in-game exit. This is, by default,
set by all Exit objects and you should not need to set it manually unless you make your own Exit
system. It is used for optimization and allows the cmdhandler to easily disregard this command when
the cmdset has its `no_exits` flag set.
- `is_channel` (bool)- this marks the command as being used for an in-game channel. This is, by
default, set by all Channel objects and you should not need to set it manually unless you make your
own Channel system. is used for optimization and allows the cmdhandler to easily disregard this
command when its cmdset has its `no_channels` flag set.
- `msg_all_sessions` (bool): This affects the behavior of the `Command.msg` method. If unset
(default), calling `self.msg(text)` from the Command will always only send text to the Session that
actually triggered this Command. If set however, `self.msg(text)` will send to all Sessions relevant
to the object this Command sits on. Just which Sessions receives the text depends on the object and
the server's `MULTISESSION_MODE`.
You should also implement at least two methods, `parse()` and `func()` (You could also implement
`perm()`, but that's not needed unless you want to fundamentally change how access checks work).
- `at_pre_cmd()` is called very first on the command. If this function returns anything that
evaluates to `True` the command execution is aborted at this point.
- `parse()` is intended to parse the arguments (`self.args`) of the function. You can do this in any
way you like, then store the result(s) in variable(s) on the command object itself (i.e. on `self`).
To take an example, the default mux-like system uses this method to detect "command switches" and
store them as a list in `self.switches`. Since the parsing is usually quite similar inside a command
scheme you should make `parse()` as generic as possible and then inherit from it rather than re-
implementing it over and over. In this way, the default `MuxCommand` class implements a `parse()`
for all child commands to use.
- `func()` is called right after `parse()` and should make use of the pre-parsed input to actually
do whatever the command is supposed to do. This is the main body of the command. The return value
from this method will be returned from the execution as a Twisted Deferred.
- `at_post_cmd()` is called after `func()` to handle eventual cleanup.
Finally, you should always make an informative [doc
string](http://www.python.org/dev/peps/pep-0257/#what-is-a-docstring) (`__doc__`) at the top of your
class. This string is dynamically read by the [Help System](Help-System) to create the help entry
for this command. You should decide on a way to format your help and stick to that.
Below is how you define a simple alternative "`smile`" command:
```python
from evennia import Command
class CmdSmile(Command):
"""
A smile command
Usage:
smile [at] [<someone>]
grin [at] [<someone>]
Smiles to someone in your vicinity or to the room
in general.
(This initial string (the __doc__ string)
is also used to auto-generate the help
for this command)
"""
key = "smile"
aliases = ["smile at", "grin", "grin at"]
locks = "cmd:all()"
help_category = "General"
def parse(self):
"Very trivial parser"
self.target = self.args.strip()
def func(self):
"This actually does things"
caller = self.caller
if not self.target or self.target == "here":
string = f"{caller.key} smiles"
else:
target = caller.search(self.target)
if not target:
return
string = f"{caller.key} smiles at {target.key}"
caller.location.msg_contents(string)
```
The power of having commands as classes and to separate `parse()` and `func()`
lies in the ability to inherit functionality without having to parse every
command individually. For example, as mentioned the default commands all
inherit from `MuxCommand`. `MuxCommand` implements its own version of `parse()`
that understands all the specifics of MUX-like commands. Almost none of the
default commands thus need to implement `parse()` at all, but can assume the
incoming string is already split up and parsed in suitable ways by its parent.
Before you can actually use the command in your game, you must now store it
within a *command set*. See the [Command Sets](Command-Sets) page.
### On arg_regex
The command parser is very general and does not require a space to end your command name. This means
that the alias `:` to `emote` can be used like `:smiles` without modification. It also means
`getstone` will get you the stone (unless there is a command specifically named `getstone`, then
that will be used). If you want to tell the parser to require a certain separator between the
command name and its arguments (so that `get stone` works but `getstone` gives you a 'command not
found' error) you can do so with the `arg_regex` property.
The `arg_regex` is a [raw regular expression string](http://docs.python.org/library/re.html). The
regex will be compiled by the system at runtime. This allows you to customize how the part
*immediately following* the command name (or alias) must look in order for the parser to match for
this command. Some examples:
- `commandname argument` (`arg_regex = r"\s.+"`): This forces the parser to require the command name
to be followed by one or more spaces. Whatever is entered after the space will be treated as an
argument. However, if you'd forget the space (like a command having no arguments), this would *not*
match `commandname`.
- `commandname` or `commandname argument` (`arg_regex = r"\s.+|$"`): This makes both `look` and
`look me` work but `lookme` will not.
- `commandname/switches arguments` (`arg_regex = r"(?:^(?:\s+|\/).*$)|^$"`. If you are using
Evennia's `MuxCommand` Command parent, you may wish to use this since it will allow `/switche`s to
work as well as having or not having a space.
The `arg_regex` allows you to customize the behavior of your commands. You can put it in the parent
class of your command to customize all children of your Commands. However, you can also change the
base default behavior for all Commands by modifying `settings.COMMAND_DEFAULT_ARG_REGEX`.
## Exiting a command
Normally you just use `return` in one of your Command class' hook methods to exit that method. That
will however still fire the other hook methods of the Command in sequence. That's usually what you
want but sometimes it may be useful to just abort the command, for example if you find some
unacceptable input in your parse method. To exit the command this way you can raise
`evennia.InterruptCommand`:
```python
from evennia import InterruptCommand
class MyCommand(Command):
# ...
def parse(self):
# ...
# if this fires, `func()` and `at_post_cmd` will not
# be called at all
raise InterruptCommand()
```
## Pauses in commands
Sometimes you want to pause the execution of your command for a little while before continuing -
maybe you want to simulate a heavy swing taking some time to finish, maybe you want the echo of your
voice to return to you with an ever-longer delay. Since Evennia is running asynchronously, you
cannot use `time.sleep()` in your commands (or anywhere, really). If you do, the *entire game* will
be frozen for everyone! So don't do that. Fortunately, Evennia offers a really quick syntax for
making pauses in commands.
In your `func()` method, you can use the `yield` keyword. This is a Python keyword that will freeze
the current execution of your command and wait for more before processing.
> Note that you *cannot* just drop `yield` into any code and expect it to pause. Evennia will only
pause for you if you `yield` inside the Command's `func()` method. Don't expect it to work anywhere
else.
Here's an example of a command using a small pause of five seconds between messages:
```python
from evennia import Command
class CmdWait(Command):
"""
A dummy command to show how to wait
Usage:
wait
"""
key = "wait"
locks = "cmd:all()"
help_category = "General"
def func(self):
"""Command execution."""
self.msg("Starting to wait ...")
yield 5
self.msg("... This shows after 5 seconds. Waiting ...")
yield 2
self.msg("... And now another 2 seconds have passed.")
```
The important line is the `yield 5` and `yield 2` lines. It will tell Evennia to pause execution
here and not continue until the number of seconds given has passed.
There are two things to remember when using `yield` in your Command's `func` method:
1. The paused state produced by the `yield` is not saved anywhere. So if the server reloads in the
middle of your command pausing, it will *not* resume when the server comes back up - the remainder
of the command will never fire. So be careful that you are not freezing the character or account in
a way that will not be cleared on reload.
2. If you use `yield` you may not also use `return <values>` in your `func` method. You'll get an
error explaining this. This is due to how Python generators work. You can however use a "naked"
`return` just fine. Usually there is no need for `func` to return a value, but if you ever do need
to mix `yield` with a final return value in the same `func`, look at
[twisted.internet.defer.returnValue](https://twistedmatrix.com/documents/current/api/twisted.internet.defer.html#returnValue).
## Asking for user input
The `yield` keyword can also be used to ask for user input. Again you can't
use Python's `input` in your command, for it would freeze Evennia for
everyone while waiting for that user to input their text. Inside a Command's
`func` method, the following syntax can also be used:
```python
answer = yield("Your question")
```
Here's a very simple example:
```python
class CmdConfirm(Command):
"""
A dummy command to show confirmation.
Usage:
confirm
"""
key = "confirm"
def func(self):
answer = yield("Are you sure you want to go on?")
if answer.strip().lower() in ("yes", "y"):
self.msg("Yes!")
else:
self.msg("No!")
```
This time, when the user enters the 'confirm' command, she will be asked if she wants to go on.
Entering 'yes' or "y" (regardless of case) will give the first reply, otherwise the second reply
will show.
> Note again that the `yield` keyword does not store state. If the game reloads while waiting for
the user to answer, the user will have to start over. It is not a good idea to use `yield` for
important or complex choices, a persistent [EvMenu](EvMenu) might be more appropriate in this case.
## System commands
*Note: This is an advanced topic. Skip it if this is your first time learning about commands.*
There are several command-situations that are exceptional in the eyes of the server. What happens if
the account enters an empty string? What if the 'command' given is infact the name of a channel the
user wants to send a message to? Or if there are multiple command possibilities?
Such 'special cases' are handled by what's called *system commands*. A system command is defined
in the same way as other commands, except that their name (key) must be set to one reserved by the
engine (the names are defined at the top of `evennia/commands/cmdhandler.py`). You can find (unused)
implementations of the system commands in `evennia/commands/default/system_commands.py`. Since these
are not (by default) included in any `CmdSet` they are not actually used, they are just there for
show. When the special situation occurs, Evennia will look through all valid `CmdSet`s for your
custom system command. Only after that will it resort to its own, hard-coded implementation.
Here are the exceptional situations that triggers system commands. You can find the command keys
they use as properties on `evennia.syscmdkeys`:
- No input (`syscmdkeys.CMD_NOINPUT`) - the account just pressed return without any input. Default
is to do nothing, but it can be useful to do something here for certain implementations such as line
editors that interpret non-commands as text input (an empty line in the editing buffer).
- Command not found (`syscmdkeys.CMD_NOMATCH`) - No matching command was found. Default is to
display the "Huh?" error message.
- Several matching commands where found (`syscmdkeys.CMD_MULTIMATCH`) - Default is to show a list of
matches.
- User is not allowed to execute the command (`syscmdkeys.CMD_NOPERM`) - Default is to display the
"Huh?" error message.
- Channel (`syscmdkeys.CMD_CHANNEL`) - This is a [Channel](Communications) name of a channel you are
subscribing to - Default is to relay the command's argument to that channel. Such commands are
created by the Comm system on the fly depending on your subscriptions.
- New session connection (`syscmdkeys.CMD_LOGINSTART`). This command name should be put in the
`settings.CMDSET_UNLOGGEDIN`. Whenever a new connection is established, this command is always
called on the server (default is to show the login screen).
Below is an example of redefining what happens when the account doesn't provide any input (e.g. just
presses return). Of course the new system command must be added to a cmdset as well before it will
work.
```python
from evennia import syscmdkeys, Command
class MyNoInputCommand(Command):
"Usage: Just press return, I dare you"
key = syscmdkeys.CMD_NOINPUT
def func(self):
self.caller.msg("Don't just press return like that, talk to me!")
```
## Dynamic Commands
*Note: This is an advanced topic.*
Normally Commands are created as fixed classes and used without modification. There are however
situations when the exact key, alias or other properties is not possible (or impractical) to pre-
code ([Exits](Commands#Exits) is an example of this).
To create a command with a dynamic call signature, first define the command body normally in a class
(set your `key`, `aliases` to default values), then use the following call (assuming the command
class you created is named `MyCommand`):
```python
cmd = MyCommand(key="newname",
aliases=["test", "test2"],
locks="cmd:all()",
...)
```
*All* keyword arguments you give to the Command constructor will be stored as a property on the
command object. This will overload existing properties defined on the parent class.
Normally you would define your class and only overload things like `key` and `aliases` at run-time.
But you could in principle also send method objects (like `func`) as keyword arguments in order to
make your command completely customized at run-time.
## Exits
*Note: This is an advanced topic.*
Exits are examples of the use of a [Dynamic Command](Commands#Dynamic_Commands).
The functionality of [Exit](Objects) objects in Evennia is not hard-coded in the engine. Instead
Exits are normal [typeclassed](Typeclasses) objects that auto-create a [CmdSet](Commands#CmdSets) on
themselves when they load. This cmdset has a single dynamically created Command with the same
properties (key, aliases and locks) as the Exit object itself. When entering the name of the exit,
this dynamic exit-command is triggered and (after access checks) moves the Character to the exit's
destination.
Whereas you could customize the Exit object and its command to achieve completely different
behaviour, you will usually be fine just using the appropriate `traverse_*` hooks on the Exit
object. But if you are interested in really changing how things work under the hood, check out
`evennia/objects/objects.py` for how the `Exit` typeclass is set up.
## Command instances are re-used
*Note: This is an advanced topic that can be skipped when first learning about Commands.*
A Command class sitting on an object is instantiated once and then re-used. So if you run a command
from object1 over and over you are in fact running the same command instance over and over (if you
run the same command but sitting on object2 however, it will be a different instance). This is
usually not something you'll notice, since every time the Command-instance is used, all the relevant
properties on it will be overwritten. But armed with this knowledge you can implement some of the
more exotic command mechanism out there, like the command having a 'memory' of what you last entered
so that you can back-reference the previous arguments etc.
> Note: On a server reload, all Commands are rebuilt and memory is flushed.
To show this in practice, consider this command:
```python
class CmdTestID(Command):
key = "testid"
def func(self):
if not hasattr(self, "xval"):
self.xval = 0
self.xval += 1
self.caller.msg("Command memory ID: {} (xval={})".format(id(self), self.xval))
```
Adding this to the default character cmdset gives a result like this in-game:
```
> testid
Command memory ID: 140313967648552 (xval=1)
> testid
Command memory ID: 140313967648552 (xval=2)
> testid
Command memory ID: 140313967648552 (xval=3)
```
Note how the in-memory address of the `testid` command never changes, but `xval` keeps ticking up.
## Dynamically created commands
*This is also an advanced topic.*
Commands can also be created and added to a cmdset on the fly. Creating a class instance with a
keyword argument, will assign that keyword argument as a property on this paricular command:
```
class MyCmdSet(CmdSet):
def at_cmdset_creation(self):
self.add(MyCommand(myvar=1, foo="test")
```
This will start the `MyCommand` with `myvar` and `foo` set as properties (accessable as `self.myvar`
and `self.foo`). How they are used is up to the Command. Remember however the discussion from the
previous section - since the Command instance is re-used, those properties will *remain* on the
command as long as this cmdset and the object it sits is in memory (i.e. until the next reload).
Unless `myvar` and `foo` are somehow reset when the command runs, they can be modified and that
change will be remembered for subsequent uses of the command.
## How commands actually work
*Note: This is an advanced topic mainly of interest to server developers.*
Any time the user sends text to Evennia, the server tries to figure out if the text entered
corresponds to a known command. This is how the command handler sequence looks for a logged-in user:
1. A user enters a string of text and presses enter.
2. The user's Session determines the text is not some protocol-specific control sequence or OOB
command, but sends it on to the command handler.
3. Evennia's *command handler* analyzes the Session and grabs eventual references to Account and
eventual puppeted Characters (these will be stored on the command object later). The *caller*
property is set appropriately.
4. If input is an empty string, resend command as `CMD_NOINPUT`. If no such command is found in
cmdset, ignore.
5. If command.key matches `settings.IDLE_COMMAND`, update timers but don't do anything more.
6. The command handler gathers the CmdSets available to *caller* at this time:
- The caller's own currently active CmdSet.
- CmdSets defined on the current account, if caller is a puppeted object.
- CmdSets defined on the Session itself.
- The active CmdSets of eventual objects in the same location (if any). This includes commands
on [Exits](Objects#Exits).
- Sets of dynamically created *System commands* representing available
[Communications](Communications#Channels).
7. All CmdSets *of the same priority* are merged together in groups. Grouping avoids order-
dependent issues of merging multiple same-prio sets onto lower ones.
8. All the grouped CmdSets are *merged* in reverse priority into one combined CmdSet according to
each set's merge rules.
9. Evennia's *command parser* takes the merged cmdset and matches each of its commands (using its
key and aliases) against the beginning of the string entered by *caller*. This produces a set of
candidates.
10. The *cmd parser* next rates the matches by how many characters they have and how many percent
matches the respective known command. Only if candidates cannot be separated will it return multiple
matches.
- If multiple matches were returned, resend as `CMD_MULTIMATCH`. If no such command is found in
cmdset, return hard-coded list of matches.
- If no match was found, resend as `CMD_NOMATCH`. If no such command is found in cmdset, give
hard-coded error message.
11. If a single command was found by the parser, the correct command object is plucked out of
storage. This usually doesn't mean a re-initialization.
12. It is checked that the caller actually has access to the command by validating the *lockstring*
of the command. If not, it is not considered as a suitable match and `CMD_NOMATCH` is triggered.
13. If the new command is tagged as a channel-command, resend as `CMD_CHANNEL`. If no such command
is found in cmdset, use hard-coded implementation.
14. Assign several useful variables to the command instance (see previous sections).
15. Call `at_pre_command()` on the command instance.
16. Call `parse()` on the command instance. This is fed the remainder of the string, after the name
of the command. It's intended to pre-parse the string into a form useful for the `func()` method.
17. Call `func()` on the command instance. This is the functional body of the command, actually
doing useful things.
18. Call `at_post_command()` on the command instance.
## Assorted notes
The return value of `Command.func()` is a Twisted
[deferred](http://twistedmatrix.com/documents/current/core/howto/defer.html).
Evennia does not use this return value at all by default. If you do, you must
thus do so asynchronously, using callbacks.
```python
# in command class func()
def callback(ret, caller):
caller.msg("Returned is %s" % ret)
deferred = self.execute_command("longrunning")
deferred.addCallback(callback, self.caller)
```
This is probably not relevant to any but the most advanced/exotic designs (one might use it to
create a "nested" command structure for example).
The `save_for_next` class variable can be used to implement state-persistent commands. For example
it can make a command operate on "it", where it is determined by what the previous command operated
on.

View file

@ -0,0 +1,113 @@
# Communications
Apart from moving around in the game world and talking, players might need other forms of
communication. This is offered by Evennia's `Comm` system. Stock evennia implements a 'MUX-like'
system of channels, but there is nothing stopping you from changing things to better suit your
taste.
Comms rely on two main database objects - `Msg` and `Channel`. There is also the `TempMsg` which
mimics the API of a `Msg` but has no connection to the database.
## Msg
The `Msg` object is the basic unit of communication in Evennia. A message works a little like an
e-mail; it always has a sender (a [Account](Accounts)) and one or more recipients. The recipients
may be either other Accounts, or a *Channel* (see below). You can mix recipients to send the message
to both Channels and Accounts if you like.
Once created, a `Msg` is normally not changed. It is peristently saved in the database. This allows
for comprehensive logging of communications. This could be useful for allowing senders/receivers to
have 'mailboxes' with the messages they want to keep.
### Properties defined on `Msg`
- `senders` - this is a reference to one or many [Account](Accounts) or [Objects](Objects) (normally
*Characters*) sending the message. This could also be an *External Connection* such as a message
coming in over IRC/IMC2 (see below). There is usually only one sender, but the types can also be
mixed in any combination.
- `receivers` - a list of target [Accounts](Accounts), [Objects](Objects) (usually *Characters*) or
*Channels* to send the message to. The types of receivers can be mixed in any combination.
- `header` - this is a text field for storing a title or header for the message.
- `message` - the actual text being sent.
- `date_sent` - when message was sent (auto-created).
- `locks` - a [lock definition](Locks).
- `hide_from` - this can optionally hold a list of objects, accounts or channels to hide this `Msg`
from. This relationship is stored in the database primarily for optimization reasons, allowing for
quickly post-filter out messages not intended for a given target. There is no in-game methods for
setting this, it's intended to be done in code.
You create new messages in code using `evennia.create_message` (or
`evennia.utils.create.create_message.`)
## TempMsg
`evennia.comms.models` also has `TempMsg` which mimics the API of `Msg` but is not connected to the
database. TempMsgs are used by Evennia for channel messages by default. They can be used for any
system expecting a `Msg` but when you don't actually want to save anything.
## Channels
Channels are [Typeclassed](Typeclasses) entities, which mean they can be easily extended and their
functionality modified. To change which channel typeclass Evennia uses, change
settings.BASE_CHANNEL_TYPECLASS.
Channels act as generic distributors of messages. Think of them as "switch boards" redistributing
`Msg` or `TempMsg` objects. Internally they hold a list of "listening" objects and any `Msg` (or
`TempMsg`) sent to the channel will be distributed out to all channel listeners. Channels have
[Locks](Locks) to limit who may listen and/or send messages through them.
The *sending* of text to a channel is handled by a dynamically created [Command](Commands) that
always have the same name as the channel. This is created for each channel by the global
`ChannelHandler`. The Channel command is added to the Account's cmdset and normal command locks are
used to determine which channels are possible to write to. When subscribing to a channel, you can
then just write the channel name and the text to send.
The default ChannelCommand (which can be customized by pointing `settings.CHANNEL_COMMAND_CLASS` to
your own command), implements a few convenient features:
- It only sends `TempMsg` objects. Instead of storing individual entries in the database it instead
dumps channel output a file log in `server/logs/channel_<channelname>.log`. This is mainly for
practical reasons - we find one rarely need to query individual Msg objects at a later date. Just
stupidly dumping the log to a file also means a lot less database overhead.
- It adds a `/history` switch to view the 20 last messages in the channel. These are read from the
end of the log file. One can also supply a line number to start further back in the file (but always
20 entries at a time). It's used like this:
> public/history
> public/history 35
There are two default channels created in stock Evennia - `MudInfo` and `Public`. `MudInfo`
receives server-related messages meant for Admins whereas `Public` is open to everyone to chat on
(all new accounts are automatically joined to it when logging in, it is useful for asking
questions). The default channels are defined by the `DEFAULT_CHANNELS` list (see
`evennia/settings_default.py` for more details).
You create new channels with `evennia.create_channel` (or `evennia.utils.create.create_channel`).
In code, messages are sent to a channel using the `msg` or `tempmsg` methods of channels:
channel.msg(msgobj, header=None, senders=None, persistent=True)
The argument `msgobj` can be either a string, a previously constructed `Msg` or a `TempMsg` - in the
latter cases all the following keywords are ignored since the message objects already contains all
this information. If `msgobj` is a string, the other keywords are used for creating a new `Msg` or
`TempMsg` on the fly, depending on if `persistent` is set or not. By default, a `TempMsg` is emitted
for channel communication (since the default ChannelCommand instead logs to a file).
```python
# assume we have a 'sender' object and a channel named 'mychan'
# manually sending a message to a channel
mychan.msg("Hello!", senders=[sender])
```
### Properties defined on `Channel`
- `key` - main name for channel
- `aliases` - alternative native names for channels
- `desc` - optional description of channel (seen in listings)
- `keep_log` (bool) - if the channel should store messages (default)
- `locks` - A [lock definition](Locks). Channels normally use the access_types `send, control` and
`listen`.

View file

@ -0,0 +1,36 @@
# 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](Start-Stop-Reload) 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) 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) and the
tutorial section on how to add new commands to a default command set.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,181 @@
# EvEditor
Evennia offers a powerful in-game line editor in `evennia.utils.eveditor.EvEditor`. This editor,
mimicking the well-known VI line editor. It offers line-by-line editing, undo/redo, line deletes,
search/replace, fill, dedent and more.
### Launching the editor
The editor is created as follows:
```python
from evennia.utils.eveditor import EvEditor
EvEditor(caller,
loadfunc=None, savefunc=None, quitfunc=None,
key="")
```
- `caller` (Object or Account): The user of the editor.
- `loadfunc` (callable, optional): This is a function called when the editor is first started. It
is called with `caller` as its only argument. The return value from this function is used as the
starting text in the editor buffer.
- `savefunc` (callable, optional): This is called when the user saves their buffer in the editor is
called with two arguments, `caller` and `buffer`, where `buffer` is the current buffer.
- `quitfunc` (callable, optional): This is called when the user quits the editor. If given, all
cleanup and exit messages to the user must be handled by this function.
- `key` (str, optional): This text will be displayed as an identifier and reminder while editing.
It has no other mechanical function.
- `persistent` (default `False`): if set to `True`, the editor will survive a reboot.
### Example of usage
This is an example command for setting a specific Attribute using the editor.
```python
from evennia import Command
from evennia.utils import eveditor
class CmdSetTestAttr(Command):
"""
Set the "test" Attribute using
the line editor.
Usage:
settestattr
"""
key = "settestattr"
def func(self):
"Set up the callbacks and launch the editor"
def load(caller):
"get the current value"
return caller.attributes.get("test")
def save(caller, buffer):
"save the buffer"
caller.attributes.set("test", buffer)
def quit(caller):
"Since we define it, we must handle messages"
caller.msg("Editor exited")
key = "%s/test" % self.caller
# launch the editor
eveditor.EvEditor(self.caller,
loadfunc=load, savefunc=save, quitfunc=quit,
key=key)
```
### 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
(`loadfunc`, `savefunc` and `quitfunc`) as top-level functions defined in the module. Since these
functions will be stored, Python will need to find them.
```python
from evennia import Command
from evennia.utils import eveditor
def load(caller):
"get the current value"
return caller.attributes.get("test")
def save(caller, buffer):
"save the buffer"
caller.attributes.set("test", buffer)
def quit(caller):
"Since we define it, we must handle messages"
caller.msg("Editor exited")
class CmdSetTestAttr(Command):
"""
Set the "test" Attribute using
the line editor.
Usage:
settestattr
"""
key = "settestattr"
def func(self):
"Set up the callbacks and launch the editor"
key = "%s/test" % self.caller
# launch the editor
eveditor.EvEditor(self.caller,
loadfunc=load, savefunc=save, quitfunc=quit,
key=key, persistent=True)
```
### Line editor usage
The editor mimics the `VIM` editor as best as possible. The below is an excerpt of the return from
the in-editor help command (`:h`).
```
<txt> - any non-command is appended to the end of the buffer.
: <l> - view buffer or only line <l>
:: <l> - view buffer without line numbers or other parsing
::: - print a ':' as the only character on the line...
:h - this help.
:w - save the buffer (don't quit)
:wq - save buffer and quit
:q - quit (will be asked to save if buffer was changed)
:q! - quit without saving, no questions asked
:u - (undo) step backwards in undo history
:uu - (redo) step forward in undo history
:UU - reset all changes back to initial state
:dd <l> - delete line <n>
:dw <l> <w> - delete word or regex <w> in entire buffer or on line <l>
:DD - clear buffer
:y <l> - yank (copy) line <l> to the copy buffer
:x <l> - cut line <l> and store it in the copy buffer
:p <l> - put (paste) previously copied line directly after <l>
:i <l> <txt> - insert new text <txt> at line <l>. Old line will move down
:r <l> <txt> - replace line <l> with text <txt>
:I <l> <txt> - insert text at the beginning of line <l>
:A <l> <txt> - append text after the end of line <l>
:s <l> <w> <txt> - search/replace word or regex <w> in buffer or on line <l>
:f <l> - flood-fill entire buffer or line <l>
:fi <l> - indent entire buffer or line <l>
:fd <l> - de-indent entire buffer or line <l>
:echo - turn echoing of the input on/off (helpful for some clients)
Legend:
<l> - line numbers, or range lstart:lend, e.g. '3:7'.
<w> - one word or several enclosed in quotes.
<txt> - longer string, usually not needed to be enclosed in quotes.
```
### The EvEditor to edit code
The `EvEditor` is also used to edit some Python code in Evennia. The `@py` command supports an
`/edit` switch that will open the EvEditor in code mode. This mode isn't significantly different
from the standard one, except it handles automatic indentation of blocks and a few options to
control this behavior.
- `:<` to remove a level of indentation for the future lines.
- `:+` to add a level of indentation for the future lines.
- `:=` to disable automatic indentation altogether.
Automatic indentation is there to make code editing more simple. Python needs correct indentation,
not as an aesthetic addition, but as a requirement to determine beginning and ending of blocks. The
EvEditor will try to guess the next level of indentation. If you type a block "if", for instance,
the EvEditor will propose you an additional level of indentation at the next line. This feature
cannot be perfect, however, and sometimes, you will have to use the above options to handle
indentation.
`:=` 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
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.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,37 @@
# EvMore
When sending a very long text to a user client, it might scroll beyond of the height of the client
window. The `evennia.utils.evmore.EvMore` class gives the user the in-game ability to only view one
page of text at a time. It is usually used via its access function, `evmore.msg`.
The name comes from the famous unix pager utility *more* which performs just this function.
### Using EvMore
To use the pager, just pass the long text through it:
```python
from evennia.utils import evmore
evmore.msg(receiver, long_text)
```
Where receiver is an [Object](Objects) or a [Account](Accounts). If the text is longer than the
client's screen height (as determined by the NAWS handshake or by `settings.CLIENT_DEFAULT_HEIGHT`)
the pager will show up, something like this:
>[...]
aute irure dolor in reprehenderit in voluptate velit
esse cillum dolore eu fugiat nulla pariatur. Excepteur
sint occaecat cupidatat non proident, sunt in culpa qui
officia deserunt mollit anim id est laborum.
>(**more** [1/6] retur**n**|**b**ack|**t**op|**e**nd|**a**bort)
where the user will be able to hit the return key to move to the next page, or use the suggested
commands to jump to previous pages, to the top or bottom of the document as well as abort the
paging.
The pager takes several more keyword arguments for controlling the message output. See the
[evmore-API](github:evennia.utils.evmore) for more info.

View file

@ -0,0 +1,122 @@
# Help System
An important part of Evennia is the online help system. This allows the players and staff alike to
learn how to use the game's commands as well as other information pertinent to the game. The help
system has many different aspects, from the normal editing of help entries from inside the game, to
auto-generated help entries during code development using the *auto-help system*.
## Viewing the help database
The main command is `help`:
help [searchstring]
This will show a list of help entries, ordered after categories. You will find two sections,
*Command help entries* and *Other help entries* (initially you will only have the first one). You
can use help to get more info about an entry; you can also give partial matches to get suggestions.
If you give category names you will only be shown the topics in that category.
## Command Auto-help system
A common item that requires help entries are in-game commands. Keeping these entries up-to-date with
the actual source code functionality can be a chore. Evennia's commands are therefore auto-
documenting straight from the sources through its *auto-help system*. Only commands that you and
your character can actually currently use are picked up by the auto-help system. That means an admin
will see a considerably larger amount of help topics than a normal player when using the default
`help` command.
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. There is no need to manually
create and maintain help database entries for commands; as long as you keep the docstrings updated
your help will be dynamically updated for you as well.
Example (from a module with command definitions):
```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.
"""
# [...]
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 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, "General" is assumed.
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) 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()` 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 all help entries not involving commands (this is handled automatically by the [Command
Auto-help system](Help-System#command-auto-help-system)). Non-automatic help entries describe how
your particular game is played - its rules, world descriptions and so on.
A help entry consists of four parts:
- The *topic*. This is the name of the help entry. This is what players search for when they are
looking for help. The topic can contain spaces and also partial matches will be found.
- The *help category*. Examples are *Administration*, *Building*, *Comms* or *General*. This is an
overall grouping of similar help topics, used by the engine to give a better overview.
- The *text* - the help text itself, of any length.
- locks - a [lock definition](Locks). This can be used to limit access to this help entry, maybe
because it's staff-only or otherwise meant to be restricted. Help commands check for `access_type`s
`view` and `edit`. An example of a lock string would be `view:perm(Builders)`.
You can create new help entries in code by using `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()")
```
From inside the game those with the right permissions can use the `@sethelp` command to add and
modify help entries.
> @sethelp/add emote = The emote command is ...
Using `@sethelp` you can add, delete and append text to existing entries. By default new entries
will go in the *General* help category. You can change this using a different form of the `@sethelp`
command:
> @sethelp/add emote, Roleplaying = Emoting is important because ...
If the category *Roleplaying* did not already exist, it is created and will appear in the help
index.
You can, finally, define a lock for the help entry by following the category with a [lock
definition](Locks):
> @sethelp/add emote, Roleplaying, view:all() = Emoting is ...

View file

@ -0,0 +1,174 @@
# Inputfuncs
An *inputfunc* is an Evennia function that handles a particular input (an [inputcommand](OOB)) from
the client. The inputfunc is the last destination for the inputcommand along the [ingoing message
path](Messagepath#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
```python
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
```
## 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.
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.
## Default inputfuncs
Evennia defines a few default inputfuncs to handle the common cases. These are defined in
`evennia/server/inputfuncs.py`.
### text
- 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), 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.
### default
The default function, as mentioned above, absorbs all non-recognized inputcommands. The default one
will just log an error.
### client_options
- Input: `("client_options, (), {key:value, ...})`
- Output:
- normal: None
- get: `("client_options", (), {key:value, ...})`
This is a direct command for setting protocol options. These are settable with the `@option`
command, but this offers a client-side way to set them. Not all connection protocols makes use of
all flags, but here are the possible keywords:
- get (bool): If this is true, ignore all other kwargs and immediately return the current settings
as an outputcommand `("client_options", (), {key=value, ...})`-
- client (str): A client identifier, like "mushclient".
- version (str): A client version
- ansi (bool): Supports ansi colors
- xterm256 (bool): Supports xterm256 colors or not
- mxp (bool): Supports MXP or not
- utf-8 (bool): Supports UTF-8 or not
- screenreader (bool): Screen-reader mode on/off
- mccp (bool): MCCP compression on/off
- screenheight (int): Screen height in lines
- screenwidth (int): Screen width in characters
- inputdebug (bool): Debug input functions
- 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.
### 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.
### 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.
### login
> Note: this is currently experimental and not very well tested.
- Input: `("login", (username, password), {})`
- Output: Depends on login hooks
This performs the inputfunc version of a login operation on the current Session.
### 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:
- "name" or "key": The key of the Account or puppeted Character.
- "location": Name of the current location, or "None".
- "servername": Name of the Evennia server connected to.
### repeat
- 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). 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
- Input: `("unrepeat", (), ("callback":funcname,
"interval": secs)`
- Output: None
This is a convenience wrapper for sending "stop" to the `repeat` inputfunc.
### monitor
- 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) 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:
- "name": The current character name
- "location": The current location
- "desc": The description Argument
## unmonitor
- Input: `("unmonitor", (), {"name":name})`
- Output: None
A convenience wrapper that sends "stop" to the `monitor` function.

View file

@ -0,0 +1,495 @@
# Locks
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),
[Objects](Objects), [Scripts](Scripts), [Accounts](Accounts), [Help System](Help-System),
[messages](Communications#Msg) and [channels](Communications#Channels)) 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.
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
the specific ID of, say, `34` are allowed to delete it. So whenever a player tries to run `delete`
on the object, the `delete` command makes sure to check if this player is really allowed to do so.
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
The in-game command for setting locks on objects is `lock`:
> lock obj = <lockstring>
The `<lockstring>` is a string of a certain form that defines the behaviour of the lock. We will go
into more detail on how `<lockstring>` should look in the next section.
Code-wise, Evennia handles locks through what is usually called `locks` on all relevant entities.
This is a handler that allows you to add, delete and check locks.
```python
myobj.locks.add(<lockstring>)
```
One can call `locks.check()` to perform a lock check, but to hide the underlying implementation all
objects also have a convenience function called `access`. This should preferably be used. In the
example below, `accessing_obj` is the object requesting the 'delete' access whereas `obj` is the
object that might get deleted. This is how it would look (and does look) from inside the `delete`
command:
```python
if not obj.access(accessing_obj, 'delete'):
accessing_obj.msg("Sorry, you may not delete that.")
return
```
## 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()`.
Here are some examples of lock strings (not including the quotes):
```python
delete:id(34) # only allow obj #34 to delete
edit:all() # let everyone edit
# only those who are not "very_weak" or are Admins may pick this up
get: not attr(very_weak) or perm(Admin)
```
Formally, a lockstring has the following syntax:
```python
access_type: [NOT] lockfunc1([arg1,..]) [AND|OR] [NOT] lockfunc2([arg1,...]) [...]
```
where `[]` marks optional parts. `AND`, `OR` and `NOT` are not case sensitive and excess spaces are
ignored. `lockfunc1, lockfunc2` etc are special _lock functions_ available to the lock system.
So, a lockstring consists of the type of restriction (the `access_type`), a colon (`:`) and then an
expression involving function calls that determine what is needed to pass the lock. Each function
returns either `True` or `False`. `AND`, `OR` and `NOT` work as they do normally in Python. If the
total result is `True`, the lock is passed.
You can create several lock types one after the other by separating them with a semicolon (`;`) in
the lockstring. The string below yields the same result as the previous example:
delete:id(34);edit:all();get: not attr(very_weak) or perm(Admin)
### Valid access_types
An `access_type`, the first part of a lockstring, defines what kind of capability a lock controls,
such as "delete" or "edit". You may in principle name your `access_type` anything as long as it is
unique for the particular object. The name of the access types is not case-sensitive.
If you want to make sure the lock is used however, you should pick `access_type` names that you (or
the default command set) actually checks for, as in the example of `delete` above that uses the
'delete' `access_type`.
Below are the access_types checked by the default commandset.
- [Commands](Commands)
- `cmd` - this defines who may call this command at all.
- [Objects](Objects):
- `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
- `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#Characters):
- Same as for Objects
- [Exits](Objects#Exits):
- Same as for Objects
- `traverse` - who may pass the exit.
- [Accounts](Accounts):
- `examine` - who may examine the account's properties.
- `delete` - who may delete the account.
- `edit` - who may edit the account's attributes and properties.
- `msg` - who may send messages to the account.
- `boot` - who may boot the account.
- [Attributes](Attributes): (only checked by `obj.secure_attr`)
- `attrread` - see/access attribute
- `attredit` - change/delete attribute
- [Channels](Communications#Channels):
- `control` - who is administrating the channel. This means the ability to delete the channel,
boot listeners etc.
- `send` - who may send to the channel.
- `listen` - who may subscribe and listen to the channel.
- [HelpEntry](Help-System):
- `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>`.
### 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.
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:
```python
obj.locks.add("read:perm(Player);post:perm(Admin)")
```
This will create a 'read' access type for Characters having the `Player` permission or above and a
'post' access type for those with `Admin` permissions or above (see below how the `perm()` lock
function works). When it comes time to test these permissions, simply check like this (in this
example, the `obj` may be a board on the bulletin board system and `accessing_obj` is the player
trying to read the board):
```python
if not obj.access(accessing_obj, 'read'):
accessing_obj.msg("Sorry, you may not read that.")
return
```
### Lock functions
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`.
You can append the setting to add more module paths. To replace a default lock function, just add
your own with the same name.
A lock function must always accept at least two arguments - the *accessing object* (this is the
object wanting to get access) and the *accessed object* (this is the object with the lock). Those
two are fed automatically as the first two arguments to the function when the lock is checked. Any
arguments explicitly given in the lock definition will appear as extra arguments.
```python
# A simple example lock function. Called with e.g. `id(34)`. This is
# defined in, say mygame/server/conf/lockfuncs.py
def id(accessing_obj, accessed_obj, *args, **kwargs):
if args:
wanted_id = args[0]
return accessing_obj.id == wanted_id
return False
```
The above could for example be used in a lock function like this:
```python
# we have `obj` and `owner_object` from before
obj.locks.add("edit: id(%i)" % owner_object.id)
```
We could check if the "edit" lock is passed with something like this:
```python
# as part of a Command's func() method, for example
if not obj.access(caller, "edit"):
caller.msg("You don't have access to edit this!")
return
```
In this example, everyone except the `caller` with the right `id` will get the error.
> (Using the `*` and `**` syntax causes Python to magically put all extra arguments into a list
`args` and all keyword arguments into a dictionary `kwargs` respectively. If you are unfamiliar with
how `*args` and `**kwargs` work, see the Python manuals).
Some useful default lockfuncs (see `src/locks/lockfuncs.py` for more):
- `true()/all()` - give access to everyone
- `false()/none()/superuser()` - give access to none. Superusers bypass the check entirely and are
thus the only ones who will pass this check.
- `perm(perm)` - this tries to match a given `permission` property, on an Account firsthand, on a
Character second. See [below](Locks#permissions).
- `perm_above(perm)` - like `perm` but requires a "higher" permission level than the one given.
- `id(num)/dbref(num)` - checks so the access_object has a certain dbref/id.
- `attr(attrname)` - checks if a certain [Attribute](Attributes) exists on accessing_object.
- `attr(attrname, value)` - checks so an attribute exists on accessing_object *and* has the given
value.
- `attr_gt(attrname, value)` - checks so accessing_object has a value larger (`>`) than the given
value.
- `attr_ge, attr_lt, attr_le, attr_ne` - corresponding for `>=`, `<`, `<=` and `!=`.
- `holds(objid)` - checks so the accessing objects contains an object of given name or dbref.
- `inside()` - checks so the accessing object is inside the accessed object (the inverse of
`holds()`).
- `pperm(perm)`, `pid(num)/pdbref(num)` - same as `perm`, `id/dbref` but always looks for
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
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
has a method `check_lockstring(accessing_obj, lockstring, bypass_superuser=False)` that allows this.
```python
# inside command definition
if not self.caller.locks.check_lockstring(self.caller, "dummy:perm(Admin)"):
self.caller.msg("You must be an Admin or higher to do this!")
return
```
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
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)
of the respective entity, in the hook method `basetype_setup()` (which you usually don't want to
edit unless you want to change how basic stuff like rooms and exits store their internal variables).
This is called once, before `at_object_creation`, so just put them in the latter method on your
child object to change the default. Also creation commands like `create` changes the locks of
objects you create - for example it sets the `control` lock_type so as to allow you, its creator, to
control and delete the object.
# Permissions
> This section covers the underlying code use of permissions. If you just want to learn how to
practically assign permissions in-game, refer to the [Building Permissions](Building-Permissions)
page, which details how you use the `perm` command.
A *permission* is simply a list of text strings stored in the handler `permissions` on `Objects`
and `Accounts`. Permissions can be used as a convenient way to structure access levels and
hierarchies. It is set by the `perm` command. Permissions are especially handled by the `perm()` and
`pperm()` lock functions listed above.
Let's say we have a `red_key` object. We also have red chests that we want to unlock with this key.
perm red_key = unlocks_red_chests
This gives the `red_key` object the permission "unlocks_red_chests". Next we lock our red chests:
lock red chest = unlock:perm(unlocks_red_chests)
What this lock will expect is to the fed the actual key object. The `perm()` lock function will
check the permissions set on the key and only return true if the permission is the one given.
Finally we need to actually check this lock somehow. Let's say the chest has an command `open <key>`
sitting on itself. Somewhere in its code the command needs to figure out which key you are using and
test if this key has the correct permission:
```python
# self.obj is the chest
# and used_key is the key we used as argument to
# the command. The self.caller is the one trying
# to unlock the chest
if not self.obj.access(used_key, "unlock"):
self.caller.msg("The key does not fit!")
return
```
All new accounts are given a default set of permissions defined by
`settings.PERMISSION_ACCOUNT_DEFAULT`.
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:
Developer # like superuser but affected by locks
Admin # can administrate accounts
Builder # can edit the world
Helper # can edit help files
Player # can chat and send tells (default level)
(Also the plural form works, so you could use `Developers` etc too).
> There is also a `Guest` level below `Player` that is only active if `settings.GUEST_ENABLED` is
set. This is never part of `settings.PERMISSION_HIERARCHY`.
The main use of this is that if you use the lock function `perm()` mentioned above, a lock check for
a particular permission in the hierarchy will *also* grant access to those with *higher* hierarchy
access. So if you have the permission "Admin" you will also pass a lock defined as `perm(Builder)`
or any of those levels below "Admin".
When doing an access check from an [Object](Objects) or Character, the `perm()` lock function 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.
Here is how you use `perm` to give an account more permissions:
perm/account Tommy = Builders
perm/account/del Tommy = Builders # remove it again
Note the use of the `/account` switch. It means you assign the permission to the
[Accounts](Accounts) Tommy instead of any [Character](Objects) that also happens to be named
"Tommy".
Putting permissions on the *Account* guarantees that they are kept, *regardless* of which Character
they are currently puppeting. This is especially important to remember when assigning permissions
from the *hierarchy tree* - as mentioned above, an Account's permissions will overrule that of its
character. So to be sure to avoid confusion you should generally put hierarchy permissions on the
Account, not on their Characters (but see also [quelling](Locks#Quelling)).
Below is an example of an object without any connected account
```python
obj1.permissions = ["Builders", "cool_guy"]
obj2.locks.add("enter:perm_above(Accounts) and perm(cool_guy)")
obj2.access(obj1, "enter") # this returns True!
```
And one example of a puppet with a connected account:
```python
account.permissions.add("Accounts")
puppet.permissions.add("Builders", "cool_guy")
obj2.locks.add("enter:perm_above(Accounts) and perm(cool_guy)")
obj2.access(puppet, "enter") # this returns False!
```
## 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 so no checks are even run. This allows for the
superuser to always have access to everything in an emergency. But it also hides 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 so your locks get tested correctly.
## Quelling
The `quell` command can be used to enforce the `perm()` lockfunc to ignore permissions on the
Account and instead use the permissions on the Character only. This can be used e.g. by staff to
test out things with a lower permission level. Return to the normal operation with `unquell`. Note
that quelling will use the smallest of any hierarchical permission on the Account or Character, so
one cannot escalate one's Account permission by quelling to a high-permission Character. Also the
superuser can quell their powers this way, making them affectable by locks.
## More Lock definition examples
examine: attr(eyesight, excellent) or perm(Builders)
You are only allowed to do *examine* on this object if you have 'excellent' eyesight (that is, has
an Attribute `eyesight` with the value `excellent` defined on yourself) or if you have the
"Builders" permission string assigned to you.
open: holds('the green key') or perm(Builder)
This could be called by the `open` command on a "door" object. The check is passed if you are a
Builder or has the right key in your inventory.
cmd: perm(Builders)
Evennia's command handler looks for a lock of type `cmd` to determine if a user is allowed to even
call upon a particular command or not. When you define a command, this is the kind of lock you must
set. See the default command set for lots of examples. If a character/account don't pass the `cmd`
lock type the command will not even appear in their `help` list.
cmd: not perm(no_tell)
"Permissions" can also be used to block users or implement highly specific bans. The above example
would be be added as a lock string to the `tell` command. This will allow everyone *not* having the
"permission" `no_tell` to use the `tell` command. You could easily give an account the "permission"
`no_tell` to disable their use of this particular command henceforth.
```python
dbref = caller.id
lockstring = "control:id(%s);examine:perm(Builders);delete:id(%s) or perm(Admin);get:all()" %
(dbref, dbref)
new_obj.locks.add(lockstring)
```
This is how the `create` command sets up new objects. In sequence, this permission string sets the
owner of this object be the creator (the one running `create`). Builders may examine the object
whereas only Admins and the creator may delete it. Everyone can pick it up.
## A complete example of setting locks on an object
Assume we have two objects - one is ourselves (not superuser) and the other is an [Object](Objects)
called `box`.
> create/drop box
> desc box = "This is a very big and heavy box."
We want to limit which objects can pick up this heavy box. Let's say that to do that we require the
would-be lifter to to have an attribute *strength* on themselves, with a value greater than 50. We
assign it to ourselves to begin with.
> set self/strength = 45
Ok, so for testing we made ourselves strong, but not strong enough. Now we need to look at what
happens when someone tries to pick up the the box - they use the `get` command (in the default set).
This is defined in `evennia/commands/default/general.py`. In its code we find this snippet:
```python
if not obj.access(caller, 'get'):
if obj.db.get_err_msg:
caller.msg(obj.db.get_err_msg)
else:
caller.msg("You can't get that.")
return
```
So the `get` command looks for a lock with the type *get* (not so surprising). It also looks for an
[Attribute](Attributes) on the checked object called _get_err_msg_ in order to return a customized
error message. Sounds good! Let's start by setting that on the box:
> set box/get_err_msg = You are not strong enough to lift this box.
Next we need to craft a Lock of type *get* on our box. We want it to only be passed if the accessing
object has the attribute *strength* of the right value. For this we would need to create a lock
function that checks if attributes have a value greater than a given value. Luckily there is already
such a one included in evennia (see `evennia/locks/lockfuncs.py`), called `attr_gt`.
So the lock string will look like this: `get:attr_gt(strength, 50)`. We put this on the box now:
lock box = get:attr_gt(strength, 50)
Try to `get` the object and you should get the message that we are not strong enough. Increase your
strength above 50 however and you'll pick it up no problem. Done! A very heavy box!
If you wanted to set this up in python code, it would look something like this:
```python
from evennia import create_object
# create, then set the lock
box = create_object(None, key="box")
box.locks.add("get:attr_gt(strength, 50)")
# or we can assign locks in one go right away
box = create_object(None, key="box", locks="get:attr_gt(strength, 50)")
# set the attributes
box.db.desc = "This is a very big and heavy box."
box.db.get_err_msg = "You are not strong enough to lift this box."
# one heavy box, ready to withstand all but the strongest...
```
## On Django's permission system
Django also implements a comprehensive permission/security system of its own. The reason we don't
use that is because it is app-centric (app in the Django sense). Its permission strings are of the
form `appname.permstring` and it automatically adds three of them for each database model in the app
- for the app evennia/object this would be for example 'object.create', 'object.admin' and
'object.edit'. This makes a lot of sense for a web application, not so much for a MUD, especially
when we try to hide away as much of the underlying architecture as possible.
The django permissions are not completely gone however. We use it for validating passwords during
login. It is also used exclusively for managing Evennia's web-based admin site, which is a graphical
front-end for the database of Evennia. You edit and assign such permissions directly from the web
interface. It's stand-alone from the permissions described above.

View file

@ -0,0 +1,79 @@
# MonitorHandler
The *MonitorHandler* is a system for watching changes in properties or Attributes on objects. A
monitor can be thought of as a sort of trigger that responds to change.
The main use for the MonitorHandler is to report changes to the client; for example the client
Session may ask Evennia to monitor the value of the Characer's `health` attribute and report
whenever it changes. This way the client could for example update its health bar graphic as needed.
## Using the MonitorHandler
The MontorHandler is accessed from the singleton `evennia.MONITOR_HANDLER`. The code for the handler
is in `evennia.scripts.monitorhandler`.
Here's how to add a new monitor:
```python
from evennia import MONITOR_HANDLER
MONITOR_HANDLER.add(obj, fieldname, callback,
idstring="", persistent=False, **kwargs)
```
- `obj` ([Typeclassed](Typeclasses) entity) - the object to monitor. Since this must be
typeclassed, it means you can't monitor changes on [Sessions](Sessions) with the monitorhandler, for
example.
- `fieldname` (str) - the name of a field or [Attribute](Attributes) on `obj`. If you want to
monitor a database field you must specify its full name, including the starting `db_` (like
`db_key`, `db_location` etc). Any names not starting with `db_` are instead assumed to be the names
of Attributes. This difference matters, since the MonitorHandler will automatically know to watch
the `db_value` field of the Attribute.
- `callback`(callable) - This will be called as `callback(fieldname=fieldname, obj=obj, **kwargs)`
when the field updates.
- `idstring` (str) - this is used to separate multiple monitors on the same object and fieldname.
This is required in order to properly identify and remove the monitor later. It's also used for
saving it.
- `persistent` (bool) - if True, the monitor will survive a server reboot.
Example:
```python
from evennia import MONITOR_HANDLER as monitorhandler
def _monitor_callback(fieldname="", obj=None, **kwargs):
# reporting callback that works both
# for db-fields and Attributes
if fieldname.startswith("db_"):
new_value = getattr(obj, fieldname)
else: # an attribute
new_value = obj.attributes.get(fieldname)
obj.msg("%s.%s changed to '%s'." % \
(obj.key, fieldname, new_value))
# (we could add _some_other_monitor_callback here too)
# monitor Attribute (assume we have obj from before)
monitorhandler.add(obj, "desc", _monitor_callback)
# monitor same db-field with two different callbacks (must separate by id_string)
monitorhandler.add(obj, "db_key", _monitor_callback, id_string="foo")
monitorhandler.add(obj, "db_key", _some_other_monitor_callback, id_string="bar")
```
A monitor is uniquely identified by the combination of the *object instance* it is monitoring, the
*name* of the field/attribute to monitor on that object and its `idstring` (`obj` + `fieldname` +
`idstring`). The `idstring` will be the empty string unless given explicitly.
So to "un-monitor" the above you need to supply enough information for the system to uniquely find
the monitor to remove:
```
monitorhandler.remove(obj, "desc")
monitorhandler.remove(obj, "db_key", idstring="foo")
monitorhandler.remove(obj, "db_key", idstring="bar")
```

View file

@ -0,0 +1,124 @@
# Nicks
*Nicks*, short for *Nicknames* is a system allowing an object (usually a [Account](Accounts)) to
assign custom replacement names for other game entities.
Nicks are not to be confused with *Aliases*. Setting an Alias on a game entity actually changes an
inherent attribute on that entity, and everyone in the game will be able to use that alias to
address the entity thereafter. A *Nick* on the other hand, is used to map a different way *you
alone* can refer to that entity. Nicks are also commonly used to replace your input text which means
you can create your own aliases to default commands.
Default Evennia use Nicks in three flavours that determine when Evennia actually tries to do the
substitution.
- inputline - replacement is attempted whenever you write anything on the command line. This is the
default.
- objects - replacement is only attempted when referring to an object
- accounts - replacement is only attempted when referring an account
Here's how to use it in the default command set (using the `nick` command):
nick ls = look
This is a good one for unix/linux users who are accustomed to using the `ls` command in their daily
life. It is equivalent to `nick/inputline ls = look`.
nick/object mycar2 = The red sports car
With this example, substitutions will only be done specifically for commands expecting an object
reference, such as
look mycar2
becomes equivalent to "`look The red sports car`".
nick/accounts tom = Thomas Johnsson
This is useful for commands searching for accounts explicitly:
@find *tom
One can use nicks to speed up input. Below we add ourselves a quicker way to build red buttons. In
the future just writing *rb* will be enough to execute that whole long string.
nick rb = @create button:examples.red_button.RedButton
Nicks could also be used as the start for building a "recog" system suitable for an RP mud.
nick/account Arnold = The mysterious hooded man
The nick replacer also supports unix-style *templating*:
nick build $1 $2 = @create/drop $1;$2
This will catch space separated arguments and store them in the the tags `$1` and `$2`, to be
inserted in the replacement string. This example allows you to do `build box crate` and have Evennia
see `@create/drop box;crate`. You may use any `$` numbers between 1 and 99, but the markers must
match between the nick pattern and the replacement.
> If you want to catch "the rest" of a command argument, make sure to put a `$` tag *with no spaces
to the right of it* - it will then receive everything up until the end of the line.
You can also use [shell-type wildcards](http://www.linfo.org/wildcard.html):
- \* - matches everything.
- ? - matches a single character.
- [seq] - matches everything in the sequence, e.g. [xyz] will match both x, y and z
- [!seq] - matches everything *not* in the sequence. e.g. [!xyz] will match all but x,y z.
## Coding with nicks
Nicks are stored as the `Nick` database model and are referred from the normal Evennia
[object](Objects) through the `nicks` property - this is known as the *NickHandler*. The NickHandler
offers effective error checking, searches and conversion.
```python
# A command/channel nick:
obj.nicks.add("greetjack", "tell Jack = Hello pal!")
# An object nick:
obj.nicks.add("rose", "The red flower", nick_type="object")
# An account nick:
obj.nicks.add("tom", "Tommy Hill", nick_type="account")
# My own custom nick type (handled by my own game code somehow):
obj.nicks.add("hood", "The hooded man", nick_type="my_identsystem")
# get back the translated nick:
full_name = obj.nicks.get("rose", nick_type="object")
# delete a previous set nick
object.nicks.remove("rose", nick_type="object")
```
In a command definition you can reach the nick handler through `self.caller.nicks`. See the `nick`
command in `evennia/commands/default/general.py` for more examples.
As a last note, The Evennia [channel](Communications) alias systems are using nicks with the
`nick_type="channel"` in order to allow users to create their own custom aliases to channels.
# Advanced note
Internally, nicks are [Attributes](Attributes) saved with the `db_attrype` set to "nick" (normal
Attributes has this set to `None`).
The nick stores the replacement data in the Attribute.db_value field as a tuple with four fields
`(regex_nick, template_string, raw_nick, raw_template)`. Here `regex_nick` is the converted regex
representation of the `raw_nick` and the `template-string` is a version of the `raw_template`
prepared for efficient replacement of any `$`- type markers. The `raw_nick` and `raw_template` are
basically the unchanged strings you enter to the `nick` command (with unparsed `$` etc).
If you need to access the tuple for some reason, here's how:
```python
tuple = obj.nicks.get("nickname", return_tuple=True)
# or, alternatively
tuple = obj.nicks.get("nickname", return_obj=True).value
```

View file

@ -0,0 +1,185 @@
# Objects
All in-game objects in Evennia, be it characters, chairs, monsters, rooms or hand grenades are
represented by an Evennia *Object*. Objects form the core of Evennia and is probably what you'll
spend most time working with. Objects are [Typeclassed](Typeclasses) entities.
## How to create your own object types
An Evennia Object is, per definition, a Python class that includes `evennia.DefaultObject` among its
parents. In `mygame/typeclasses/objects.py` there is already a class `Object` that inherits from
`DefaultObject` and that you can inherit from. You can put your new typeclass directly in that
module or you could organize your code in some other way. Here we assume we make a new module
`mygame/typeclasses/flowers.py`:
```python
# mygame/typeclasses/flowers.py
from typeclasses.objects import Object
class Rose(Object):
"""
This creates a simple rose object
"""
def at_object_creation(self):
"this is called only once, when object is first created"
# add a persistent attribute 'desc'
# to object (silly example).
self.db.desc = "This is a pretty rose with thorns."
```
You could save this in the `mygame/typeclasses/objects.py` (then you'd not need to import `Object`)
or you can put it in a new module. Let's say we do the latter, making a module
`typeclasses/flowers.py`. 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 `evennia.create_object`. You can do the same
thing yourself in code:
```python
from evennia import create_object
new_rose = create_object("typeclasses.flowers.Rose", key="MyRose")
```
(The `@create` command will auto-append the most likely path to your typeclass, if you enter the
call manually you have to give the full path to the class. The `create.create_object` function is
powerful and should be used for all coded object creating (so this is what you use when defining
your own building commands). Check out the `ev.create_*` functions for how to build other entities
like [Scripts](Scripts)).
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](Spawner-and-Prototypes)). The `Object` typeclass offers many more hooks that is available
to use though - see next section.
## Properties and functions on Objects
Beyond the properties assigned to all [typeclassed](Typeclasses) objects (see that page for a list
of those), the Object also has the following custom properties:
- `aliases` - a handler that allows you to add and remove aliases from this object. Use
`aliases.add()` to add a new alias and `aliases.remove()` to remove one.
- `location` - a reference to the object currently containing this object.
- `home` is a backup location. The main motivation is to have a safe place to move the object to if
its `location` is destroyed. All objects should usually have a home location for safety.
- `destination` - this holds a reference to another object this object links to in some way. Its
main use is for [Exits](Objects#Exits), it's otherwise usually unset.
- `nicks` - as opposed to aliases, a [Nick](Nicks) holds a convenient nickname replacement for a
real name, word or sequence, only valid for this object. This mainly makes sense if the Object is
used as a game character - it can then store briefer shorts, example so as to quickly reference game
commands or other characters. Use nicks.add(alias, realname) to add a new one.
- `account` - this holds a reference to a connected [Account](Accounts) controlling this object (if
any). Note that this is set also if the controlling account is *not* currently online - to test if
an account is online, use the `has_account` property instead.
- `sessions` - if `account` field is set *and the account is online*, this is a list of all active
sessions (server connections) to contact them through (it may be more than one if multiple
connections are allowed in settings).
- `has_account` - a shorthand for checking if an *online* account is currently connected to this
object.
- `contents` - this returns a list referencing all objects 'inside' this object (i,e. which has this
object set as their `location`).
- `exits` - this returns all objects inside this object that are *Exits*, that is, has the
`destination` property set.
The last two properties are special:
- `cmdset` - this is a handler that stores all [command sets](Commands#Command_Sets) defined on the
object (if any).
- `scripts` - this is a handler that manages [Scripts](Scripts) attached to the object (if any).
The Object also has a host of useful utility functions. See the function headers in
`src/objects/objects.py` for their arguments and more details.
- `msg()` - this function is used to send messages from the server to an account connected to this
object.
- `msg_contents()` - calls `msg` on all objects inside this object.
- `search()` - this is a convenient shorthand to search for a specific object, at a given location
or globally. It's mainly useful when defining commands (in which case the object executing the
command is named `caller` and one can do `caller.search()` to find objects in the room to operate
on).
- `execute_cmd()` - Lets the object execute the given string as if it was given on the command line.
- `move_to` - perform a full move of this object to a new location. This is the main move method
and will call all relevant hooks, do all checks etc.
- `clear_exits()` - will delete all [Exits](Objects#Exits) to *and* from this object.
- `clear_contents()` - this will not delete anything, but rather move all contents (except Exits) to
their designated `Home` locations.
- `delete()` - deletes this object, first calling `clear_exits()` and
`clear_contents()`.
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](api: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 are objects controlled by [Accounts](Accounts). 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](Commands#Command_Sets) 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.
### Rooms
*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.
### 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) 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) 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 process of traversing an exit is as follows:
1. The traversing `obj` sends a command that matches the Exit-command name on the Exit object. The
[cmdhandler](Commands) detects this and triggers the command defined on the Exit. Traversal always
involves the "source" (the current location) and the `destination` (this is stored on the Exit
object).
1. The Exit command checks the `traverse` lock on the Exit object
1. The Exit command triggers `at_traverse(obj, destination)` on the Exit object.
1. In `at_traverse`, `object.move_to(destination)` is triggered. This triggers the following hooks,
in order:
1. `obj.at_before_move(destination)` - if this returns False, move is aborted.
1. `origin.at_before_leave(obj, destination)`
1. `obj.announce_move_from(destination)`
1. Move is performed by changing `obj.location` from source location to `destination`.
1. `obj.announce_move_to(source)`
1. `destination.at_object_receive(obj, source)`
1. `obj.at_after_move(source)`
1. On the Exit object, `at_after_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.

View file

@ -0,0 +1,15 @@
# 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 [here](Start-Stop-Reload).
If you are new to the concept, the main purpose of separating the two is to have accounts connect to
the Portal but keep the MUD running on the Server. This way one can restart/reload the game (the
Server part) without Accounts getting disconnected.
![portal and server layout](https://474a3b9f-a-62cb3a1a-s-
sites.googlegroups.com/site/evenniaserver/file-cabinet/evennia_server_portal.png)
The Server and Portal are glued together via an AMP (Asynchronous Messaging Protocol) connection.
This allows the two programs to communicate seamlessly.

View file

@ -0,0 +1,366 @@
# Scripts
*Scripts* are the out-of-character siblings to the in-character
[Objects](Objects). Scripts are so flexible that the "Script" is a bit limiting
- we had to pick something to name them after all. Other possible names
(depending on what you'd use them for) would be `OOBObjects`,
`StorageContainers` or `TimerObjects`.
Scripts can be used for many different things in Evennia:
- They can attach to Objects to influence them in various ways - or exist
independently of any one in-game entity (so-called *Global Scripts*).
- They can work as timers and tickers - anything that may change with Time. But
they can also have no time dependence at all. Note though that if all you want
is just to have an object method called repeatedly, you should consider using
the [TickerHandler](TickerHandler) which is more limited but is specialized on
just this task.
- They can describe State changes. A Script is an excellent platform for
hosting a persistent, but unique system handler. For example, a Script could be
used as the base to track the state of a turn-based combat system. Since
Scripts can also operate on a timer they can also update themselves regularly
to perform various actions.
- They can act as data stores for storing game data persistently in the database
(thanks to its ability to have [Attributes](Attributes)).
- They can be used as OOC stores for sharing data between groups of objects, for
example for tracking the turns in a turn-based combat system or barter exchange.
Scripts are [Typeclassed](Typeclasses) entities and are manipulated in a similar
way to how it works for other such Evennia entities:
```python
# create a new script
new_script = evennia.create_script(key="myscript", typeclass=...)
# search (this is always a list, also if there is only one match)
list_of_myscript = evennia.search_script("myscript")
```
## Defining new Scripts
A Script is defined as a class and is created in the same way as other
[typeclassed](Typeclasses) entities. The class has several properties
to control the timer-component of the scripts. These are all _optional_ -
leaving them out will just create a Script with no timer components (useful to act as
a database store or to hold a persistent game system, for example).
This you can do for example in the module
`evennia/typeclasses/scripts.py`. Below is an example Script
Typeclass.
```python
from evennia import DefaultScript
class MyScript(DefaultScript):
def at_script_creation(self):
self.key = "myscript"
self.interval = 60 # 1 min repeat
def at_repeat(self):
# do stuff every minute
```
In `mygame/typeclasses/scripts.py` is the `Script` class which inherits from `DefaultScript`
already. This is provided as your own base class to do with what you like: You can tweak `Script` if
you want to change the default behavior and it is usually convenient to inherit from this instead.
Here's an example:
```python
# for example in mygame/typeclasses/scripts.py
# Script class is defined at the top of this module
import random
class Weather(Script):
"""
A timer script that displays weather info. Meant to
be attached to a room.
"""
def at_script_creation(self):
self.key = "weather_script"
self.desc = "Gives random weather messages."
self.interval = 60 * 5 # every 5 minutes
self.persistent = True # will survive reload
def at_repeat(self):
"called every self.interval seconds."
rand = random.random()
if rand < 0.5:
weather = "A faint breeze is felt."
elif rand < 0.7:
weather = "Clouds sweep across the sky."
else:
weather = "There is a light drizzle of rain."
# send this message to everyone inside the object this
# script is attached to (likely a room)
self.obj.msg_contents(weather)
```
If we put this script on a room, it will randomly report some weather
to everyone in the room every 5 minutes.
To activate it, just add it to the script handler (`scripts`) on an
[Room](Objects). That object becomes `self.obj` in the example above. Here we
put it on a room called `myroom`:
```
myroom.scripts.add(scripts.Weather)
```
> Note that `typeclasses` in your game dir is added to the setting `TYPECLASS_PATHS`.
> Therefore we don't need to give the full path (`typeclasses.scripts.Weather`
> but only `scripts.Weather` above.
You can also create scripts using the `evennia.create_script` function:
```python
from evennia import create_script
create_script('typeclasses.weather.Weather', obj=myroom)
```
Note that if you were to give a keyword argument to `create_script`, that would
override the default value in your Typeclass. So for example, here is an instance
of the weather script that runs every 10 minutes instead (and also not survive
a server reload):
```python
create_script('typeclasses.weather.Weather', obj=myroom,
persistent=False, interval=10*60)
```
From in-game you can use the `@script` command to launch the Script on things:
```
@script here = typeclasses.scripts.Weather
```
You can conveniently view and kill running Scripts by using the `@scripts`
command in-game.
## Properties and functions defined on Scripts
A Script has all the properties of a typeclassed object, such as `db` and `ndb`(see
[Typeclasses](Typeclasses)). Setting `key` is useful in order to manage scripts (delete them by name
etc). These are usually set up in the Script's typeclass, but can also be assigned on the fly as
keyword arguments to `evennia.create_script`.
- `desc` - an optional description of the script's function. Seen in script listings.
- `interval` - how often the script should run. If `interval == 0` (default), this script has no
timing component, will not repeat and will exist forever. This is useful for Scripts used for
storage or acting as bases for various non-time dependent game systems.
- `start_delay` - (bool), if we should wait `interval` seconds before firing for the first time or
not.
- `repeats` - How many times we should repeat, assuming `interval > 0`. If repeats is set to `<= 0`,
the script will repeat indefinitely. Note that *each* firing of the script (including the first one)
counts towards this value. So a `Script` with `start_delay=False` and `repeats=1` will start,
immediately fire and shut down right away.
- `persistent`- if this script should survive a server *reset* or server *shutdown*. (You don't need
to set this for it to survive a normal reload - the script will be paused and seamlessly restart
after the reload is complete).
There is one special property:
- `obj` - the [Object](Objects) this script is attached to (if any). You should not need to set
this manually. If you add the script to the Object with `myobj.scripts.add(myscriptpath)` or give
`myobj` as an argument to the `utils.create.create_script` function, the `obj` property will be set
to `myobj` for you.
It's also imperative to know the hook functions. Normally, overriding
these are all the customization you'll need to do in Scripts. You can
find longer descriptions of these in `src/scripts/scripts.py`.
- `at_script_creation()` - this is usually where the script class sets things like `interval` and
`repeats`; things that control how the script runs. It is only called once - when the script is
first created.
- `is_valid()` - determines if the script should still be running or not. This is called when
running `obj.scripts.validate()`, which you can run manually, but which is also called by Evennia
during certain situations such as reloads. This is also useful for using scripts as state managers.
If the method returns `False`, the script is stopped and cleanly removed.
- `at_start()` - this is called when the script starts or is unpaused. For persistent scripts this
is at least once ever server startup. Note that this will *always* be called right away, also if
`start_delay` is `True`.
- `at_repeat()` - this is called every `interval` seconds, or not at all. It is called right away at
startup, unless `start_delay` is `True`, in which case the system will wait `interval` seconds
before calling.
- `at_stop()` - this is called when the script stops for whatever reason. It's a good place to do
custom cleanup.
- `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.
Running methods (usually called automatically by the engine, but possible to also invoke manually)
- `start()` - this will start the script. This is called automatically whenever you add a new script
to a handler. `at_start()` will be called.
- `stop()` - this will stop the script and delete it. Removing a script from a handler will stop it
automatically. `at_stop()` will be called.
- `pause()` - this pauses a running script, rendering it inactive, but not deleting it. All
properties are saved and timers can be resumed. This is called automatically when the server reloads
and will *not* lead to the *at_stop()* hook being called. This is a suspension of the script, not a
change of state.
- `unpause()` - resumes a previously paused script. The `at_start()` hook *will* be called to allow
it to reclaim its internal state. Timers etc are restored to what they were before pause. The server
automatically unpauses all paused scripts after a server reload.
- `force_repeat()` - this will forcibly step the script, regardless of when it would otherwise have
fired. The timer will reset and the `at_repeat()` hook is called as normal. This also counts towards
the total number of repeats, if limited.
- `time_until_next_repeat()` - for timed scripts, this returns the time in seconds until it next
fires. Returns `None` if `interval==0`.
- `remaining_repeats()` - if the Script should run a limited amount of times, this tells us how many
are currently left.
- `reset_callcount(value=0)` - this allows you to reset the number of times the Script has fired. It
only makes sense if `repeats > 0`.
- `restart(interval=None, repeats=None, start_delay=None)` - this method allows you to restart the
Script in-place with different run settings. If you do, the `at_stop` hook will be called and the
Script brought to a halt, then the `at_start` hook will be called as the Script starts up with your
(possibly changed) settings. Any keyword left at `None` means to not change the original setting.
## Global Scripts
A script does not have to be connected to an in-game object. If not it is
called a *Global script*. You can create global scripts by simply not supplying an object to store
it on:
```python
# adding a global script
from evennia import create_script
create_script("typeclasses.globals.MyGlobalEconomy",
key="economy", persistent=True, obj=None)
```
Henceforth you can then get it back by searching for its key or other identifier with
`evennia.search_script`. In-game, the `scripts` command will show all scripts.
Evennia supplies a convenient "container" called `GLOBAL_SCRIPTS` that can offer an easy
way to access global scripts. If you know the name (key) of the script you can get it like so:
```python
from evennia import GLOBAL_SCRIPTS
my_script = GLOBAL_SCRIPTS.my_script
# needed if there are spaces in name or name determined on the fly
another_script = GLOBAL_SCRIPTS.get("another script")
# get all global scripts (this returns a Queryset)
all_scripts = GLOBAL_SCRIPTS.all()
# you can operate directly on the script
GLOBAL_SCRIPTS.weather.db.current_weather = "Cloudy"
```
> 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_script` to get exactly the script you want.
There are two ways to make a script appear as a property on `GLOBAL_SCRIPTS`. The first is
to manually create a new global script with `create_script` as mentioned above. Often you want this
to happen automatically when the server starts though. For this you can use the setting
`GLOBAL_SCRIPTS`:
```python
GLOBAL_SCRIPTS = {
"my_script": {
"typeclass": "scripts.Weather",
"repeats": -1,
"interval": 50,
"desc": "Weather script"
"persistent": True
},
"storagescript": {
"typeclass": "scripts.Storage",
"persistent": True
}
}
```
Here the key (`myscript` and `storagescript` above) is required, all other fields are optional. If
`typeclass` is not given, a script of type `settings.BASE_SCRIPT_TYPECLASS` is assumed. The keys
related to timing and intervals are only needed if the script is timed.
Evennia will use the information in `settings.GLOBAL_SCRIPTS` to automatically create and start
these
scripts when the server starts (unless they already exist, based on their `key`). You need to reload
the server before the setting is read and new scripts become available. You can then find the `key`
you gave as properties on `evennia.GLOBAL_SCRIPTS`
(such as `evennia.GLOBAL_SCRIPTS.storagescript`).
> Note: Make sure that your Script typeclass does not have any critical errors. If so, 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:
```python
from evennia import GLOBAL_SCRIPTS
# first stop the script
GLOBAL_SCRIPTS.storagescript.stop()
# running the `scripts` command now will show no storagescript
# but below now it's recreated again!
storage = GLOBAL_SCRIPTS.storagescript
```
That is, if the script is deleted, next time you get it from `GLOBAL_SCRIPTS`, it will use the
information
in settings to recreate it for you.
> Note that if your goal with the Script is to store persistent data, you should set it as
`persistent=True`, either in `settings.GLOBAL_SCRIPTS` or in the Scripts typeclass. Otherwise any
data you wanted to store on it will be gone (since a new script of the same name is restarted
instead).
## Dealing with Errors
Errors inside an 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(DefaultScript):
# [...]
def at_repeat(self):
try:
# [...] code as above
except Exception:
# logs the error
logger.log_trace()
```
## Example of a timed script
In-game you can try out scripts using the `@script` command. In the
`evennia/contrib/tutorial_examples/bodyfunctions.py` is a little example script
that makes you do little 'sounds' at random intervals. Try the following to apply an
example time-based script to your character.
> @script self = bodyfunctions.BodyFunctions
> Note: Since `evennia/contrib/tutorial_examples` is in the default setting
> `TYPECLASS_PATHS`, we only need to specify the final part of the path,
> that is, `bodyfunctions.BodyFunctions`.
If you want to inflict your flatulence script on another person, place or
thing, try something like the following:
> @py self.location.search('matt').scripts.add('bodyfunctions.BodyFunctions')
Here's how you stop it on yourself.
> @script/stop self = bodyfunctions.BodyFunctions
This will kill the script again. You can use the `@scripts` command to list all
active scripts in the game, if any (there are none by default).
For another example of a Script in use, check out the [Turn Based Combat System
tutorial](https://github.com/evennia/evennia/wiki/Turn%20based%20Combat%20System).

View file

@ -0,0 +1,104 @@
# Server Conf
Evennia runs out of the box without any changes to its settings. But there are several important
ways to customize the server and expand it with your own plugins.
## Settings file
The "Settings" file referenced throughout the documentation is the file
`mygame/server/conf/settings.py`. This is automatically created on the first run of `evennia --init`
(see the [Getting Started](Getting-Started) page).
Your new `settings.py` is relatively bare out of the box. Evennia's core settings file is actually
[evennia/settings_default.py](https://github.com/evennia/evennia/blob/master/evennia/settings_default.py)
and is considerably more extensive (it is also heavily documented so you should refer to this file
directly for the available settings).
Since `mygame/server/conf/settings.py` is a normal Python module, it simply imports
`evennia/settings_default.py` into itself at the top.
This means that if any setting you want to change were to depend on some *other* default setting,
you might need to copy & paste both in order to change them and get the effect you want (for most
commonly changed settings, this is not something you need to worry about).
You should never edit `evennia/settings_default.py`. Rather you should copy&paste the select
variables you want to change into your `settings.py` and edit them there. This will overload the
previously imported defaults.
> Warning: It may be tempting to copy everything from `settings_default.py` into your own settings
file. There is a reason we don't do this out of the box though: it makes it directly clear what
changes you did. Also, if you limit your copying to the things you really need you will directly be
able to take advantage of upstream changes and additions to Evennia for anything you didn't
customize.
In code, the settings is accessed through
```python
from django.conf import settings
# or (shorter):
from evennia import settings
# example:
servername = settings.SERVER_NAME
```
Each setting appears as a property on the imported `settings` object. You can also explore all
possible options with `evennia.settings_full` (this also includes advanced Django defaults that are
not touched in default Evennia).
> It should be pointed out that when importing `settings` into your code like this, it will be *read
only*. You *cannot* edit your settings from your code! The only way to change an Evennia setting is
to edit `mygame/server/conf/settings.py` directly. You also generally need to restart the server
(possibly also the Portal) before a changed setting becomes available.
## Other files in the `server/conf` directory
Apart from the main `settings.py` file,
- `at_initial_setup.py` - this allows you to add a custom startup method to be called (only) the
very first time Evennia starts (at the same time as user #1 and Limbo is created). It can be made to
start your own global scripts or set up other system/world-related things your game needs to have
running from the start.
- `at_server_startstop.py` - this module contains two functions that Evennia will call every time
the Server starts and stops respectively - this includes stopping due to reloading and resetting as
well as shutting down completely. It's a useful place to put custom startup code for handlers and
other things that must run in your game but which has no database persistence.
- `connection_screens.py` - all global string variables in this module are interpreted by Evennia as
a greeting screen to show when an Account first connects. If more than one string variable is
present in the module a random one will be picked.
- `inlinefuncs.py` - this is where you can define custom [Inline functions](TextTags#inlinefuncs).
- `inputfuncs.py` - this is where you define custom [Input functions](Inputfuncs) to handle data
from the client.
- `lockfuncs.py` - this is one of many possible modules to hold your own "safe" *lock functions* to
make available to Evennia's [Locks](Locks).
- `mssp.py` - this holds meta information about your game. It is used by MUD search engines (which
you often have to register with) in order to display what kind of game you are running along with
statistics such as number of online accounts and online status.
- `oobfuncs.py` - in here you can define custom [OOB functions](OOB).
- `portal_services_plugin.py` - this allows for adding your own custom services/protocols to the
Portal. It must define one particular function that will be called by Evennia at startup. There can
be any number of service plugin modules, all will be imported and used if defined. More info can be
found [here](http://code.google.com/p/evennia/wiki/SessionProtocols#Adding_custom_Protocols).
- `server_services_plugin.py` - this is equivalent to the previous one, but used for adding new
services to the Server instead. More info can be found
[here](http://code.google.com/p/evennia/wiki/SessionProtocols#Adding_custom_Protocols).
Some other Evennia systems can be customized by plugin modules but has no explicit template in
`conf/`:
- *cmdparser.py* - a custom module can be used to totally replace Evennia's default command parser.
All this does is to split the incoming string into "command name" and "the rest". It also handles
things like error messages for no-matches and multiple-matches among other things that makes this
more complex than it sounds. The default parser is *very* generic, so you are most often best served
by modifying things further down the line (on the command parse level) than here.
- *at_search.py* - this allows for replacing the way Evennia handles search results. It allows to
change how errors are echoed and how multi-matches are resolved and reported (like how the default
understands that "2-ball" should match the second "ball" object if there are two of them in the
room).
## ServerConf
There is a special database model called `ServerConf` that stores server internal data and settings
such as current account count (for interfacing with the webserver), startup status and many other
things. It's rarely of use outside the server core itself but may be good to
know about if you are an Evennia developer.

View file

@ -0,0 +1,188 @@
# Sessions
An Evennia *Session* represents one single established connection to the server. Depending on the
Evennia session, it is possible for a person to connect multiple times, for example using different
clients in multiple windows. Each such connection is represented by a session object.
A session object has its own [cmdset](Command-Sets), usually the "unloggedin" cmdset. This is what
is used to show the login screen and to handle commands to create a new account (or
[Account](Accounts) in evennia lingo) read initial help and to log into the game with an existing
account. A session object can either be "logged in" or not. Logged in means that the user has
authenticated. When this happens the session is associated with an Account object (which is what
holds account-centric stuff). The account can then in turn puppet any number of objects/characters.
> Warning: A Session is not *persistent* - it is not a [Typeclass](Typeclasses) 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. You have been warned.
## 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) this Session is attached to. If not logged in yet, this is
`None`.
- `puppet` - The [Character/Object](Objects) currently puppeted by this Account/Session combo. If
not logged in or in OOC mode, this is `None`.
- `ndb` - The [Non-persistent Attribute](Attributes) handler.
- `db` - As noted above, Sessions don't have regular Attributes. This is an alias to `ndb`.
- `cmdset` - The Session's [CmdSetHandler](Command-Sets)
Session statistics are mainly used internally by Evennia.
- `conn_time` - How long this Session has been connected
- `cmd_last` - Last active time stamp. This will be reset by sending `idle` keepalives.
- `cmd_last_visible` - last active time stamp. This ignores `idle` keepalives and representes the
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. In default Evennia,
this mode also changes how the `create account` Command works - it will automatically create a
Character with the *same name* as the Account. When logging in, the login command is also modified
to have the player automatically puppet that Character. This makes the distinction between Account
and Character minimal from the player's perspective.
* `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. This mode will have the
Session(s) auto-create and puppet a Character in the same way as mode 0.
* `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.
This mode will *not* auto-create a Character and *not* auto-puppet on login like in modes 0 and 1.
Instead it changes how the account-cmdsets's `OOCLook` command works so as to show a simple
'character select' menu.
* `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.
> Note that even if multiple Sessions puppet one Character, there is only ever one instance of that
Character.
## 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.
For example, if you use `account.msg("hello")` there is no way for evennia to know which session it
should send the greeting to. In this case it will send it to all sessions. If you want a specific
session you need to supply its session to the `msg` call (`account.msg("hello",
session=mysession)`).
On the other hand, if you call the `msg()` message on a puppeted object, like
`character.msg("hello")`, the character already knows the session that controls it - it will
cleverly auto-add this for you (you can specify a different session if you specifically want to send
stuff to another session).
Finally, there is a wrapper for `msg()` on all command classes: `command.msg()`. This will
transparently detect which session was triggering the command (if any) and redirects to that session
(this is most often what you want). If you are having trouble redirecting to a given session,
`command.msg()` is often the safest bet.
You can get the `session` in two main ways:
* [Accounts](Accounts) and [Objects](Objects) (including Characters) have a `sessions` property.
This is a *handler* that tracks all Sessions attached to or puppeting them. Use e.g.
`accounts.sessions.get()` to get a list of Sessions attached to that entity.
* A Command instance has a `session` property that always points back to the Session that triggered
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
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
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.
## 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). 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.
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
its mirror Server session and vice versa.
During certain situations, the portal- and server-side sessions are
"synced" with each other:
- The Player closes their client, killing the Portal Session. The Portal syncs with the Server to
make sure the corresponding Server Session is also deleted.
- The Player quits from inside the game, killing the Server Session. The Server then syncs with the
Portal to make sure to close the Portal connection cleanly.
- The Server is rebooted/reset/shutdown - The Server Sessions are copied over ("saved") to the
Portal side. When the Server comes back up, this data is returned by the Portal so the two are again
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
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](Custom-Protocols) 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
```
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.
See the
[sessionhandler.py](https://github.com/evennia/evennia/blob/master/evennia/server/sessionhandler.py)
module for details on the capabilities of the `ServerSessionHandler`.

View file

@ -0,0 +1,121 @@
# Signals
_This is feature available from evennia 0.9 and onward_.
There are multiple ways for you to plug in your own functionality into Evennia.
The most common way to do so is through *hooks* - methods on typeclasses that
gets called at particular events. Hooks are great when you want a game entity
to behave a certain way when something happens to it. _Signals_ complements
hooks for cases when you want to easily attach new functionality without
overriding things on the typeclass.
When certain events happen in Evennia, a _Signal_ is fired. The idea is that
you can "attach" any number of event-handlers to these signals. You can attach
any number of handlers and they'll all fire whenever any entity triggers the
signal.
Evennia uses the [Django Signal system](https://docs.djangoproject.com/en/2.2/topics/signals/).
## Attaching a handler to a signal
First you create your handler
```python
def myhandler(sender, **kwargs):
# do stuff
```
The `**kwargs` is mandatory. Then you attach it to the signal of your choice:
```python
from evennia.server import signals
signals.SIGNAL_OBJECT_POST_CREATE.connect(myhandler)
```
This particular signal fires after (post) an Account has connected to the game.
When that happens, `myhandler` will fire with the `sender` being the Account that just connected.
If you want to respond only to the effects of a specific entity you can do so
like this:
```python
from evennia import search_account
from evennia import signals
account = search_account("foo")[0]
signals.SIGNAL_ACCOUNT_POST_CONNECT.connect(myhandler, account)
```
## Available signals
All signals (including some django-specific defaults) are available in the module
`evennia.server.signals`
(with a shortcut `evennia.signals`). Signals are named by the sender type. So `SIGNAL_ACCOUNT_*`
returns
`Account` instances as senders, `SIGNAL_OBJECT_*` returns `Object`s etc. Extra keywords (kwargs)
should
be extracted from the `**kwargs` dict in the signal handler.
- `SIGNAL_ACCOUNT_POST_CREATE` - this is triggered at the very end of `Account.create()`. Note that
calling `evennia.create.create_account` (which is called internally by `Account.create`) will
*not*
trigger this signal. This is because using `Account.create()` is expected to be the most commonly
used way for users to themselves create accounts during login. It passes and extra kwarg `ip` with
the client IP of the connecting account.
- `SIGNAL_ACCOUNT_POST_LOGIN` - this will always fire when the account has authenticated. Sends
extra kwarg `session` with the new [Session](Sessions) object involved.
- `SIGNAL_ACCCOUNT_POST_FIRST_LOGIN` - this fires just before `SIGNAL_ACCOUNT_POST_LOGIN` but only
if
this is the *first* connection done (that is, if there are no previous sessions connected). Also
passes the `session` along as a kwarg.
- `SIGNAL_ACCOUNT_POST_LOGIN_FAIL` - sent when someone tried to log into an account by failed.
Passes
the `session` as an extra kwarg.
- `SIGNAL_ACCOUNT_POST_LOGOUT` - always fires when an account logs off, no matter if other sessions
remain or not. Passes the disconnecting `session` along as a kwarg.
- `SIGNAL_ACCOUNT_POST_LAST_LOGOUT` - fires before `SIGNAL_ACCOUNT_POST_LOGOUT`, but only if this is
the *last* Session to disconnect for that account. Passes the `session` as a kwarg.
- `SIGNAL_OBJECT_POST_PUPPET` - fires when an account puppets this object. Extra kwargs `session`
and `account` represent the puppeting entities.
`SIGNAL_OBJECT_POST_UNPUPPET` - fires when the sending object is unpuppeted. Extra kwargs are
`session` and `account`.
- `SIGNAL_ACCOUNT_POST_RENAME` - triggered by the setting of `Account.username`. Passes extra
kwargs `old_name`, `new_name`.
- `SIGNAL_TYPED_OBJECT_POST_RENAME` - triggered when any Typeclassed entity's `key` is changed.
Extra
kwargs passed are `old_key` and `new_key`.
- `SIGNAL_SCRIPT_POST_CREATE` - fires when a script is first created, after any hooks.
- `SIGNAL_CHANNEL_POST_CREATE` - fires when a Channel is first created, after any hooks.
- `SIGNAL_HELPENTRY_POST_CREATE` - fires when a help entry is first created.
The `evennia.signals` module also gives you conveneient access to the default Django signals (these
use a
different naming convention).
- `pre_save` - fired when any database entitiy's `.save` method fires, before any saving has
happened.
- `post_save` - fires after saving a database entity.
- `pre_delete` - fires just before a database entity is deleted.
- `post_delete` - fires after a database entity was deleted.
- `pre_init` - fires before a typeclass' `__init__` method (which in turn
happens before the `at_init` hook fires).
- `post_init` - triggers at the end of `__init__` (still before the `at_init` hook).
These are highly specialized Django signals that are unlikely to be useful to most users. But
they are included here for completeness.
- `m2m_changed` - fires after a Many-to-Many field (like `db_attributes`) changes.
- `pre_migrate` - fires before database migration starts with `evennia migrate`.
- `post_migrate` - fires after database migration finished.
- `request_started` - sent when HTTP request begins.
- `request_finished` - sent when HTTP request ends.
- `settings_changed` - sent when changing settings due to `@override_settings`
decorator (only relevant for unit testing)
- `template_rendered` - sent when test system renders http template (only useful for unit tests).
- `connection_creation` - sent when making initial connection to database.

View file

@ -0,0 +1,327 @@
# Spawner and Prototypes
The *spawner* is a system for defining and creating individual objects from a base template called a
*prototype*. It is only designed for use with in-game [Objects](Objects), not any other type of
entity.
The normal way to create a custom object in Evennia is to make a [Typeclass](Typeclasses). If you
haven't read up on Typeclasses yet, think of them as normal Python classes that save to the database
behind the scenes. Say you wanted to create a "Goblin" enemy. A common way to do this would be to
first create a `Mobile` typeclass that holds everything common to mobiles in the game, like generic
AI, combat code and various movement methods. A `Goblin` subclass is then made to inherit from
`Mobile`. The `Goblin` class adds stuff unique to goblins, like group-based AI (because goblins are
smarter in a group), the ability to panic, dig for gold etc.
But now it's time to actually start to create some goblins and put them in the world. What if we
wanted those goblins to not all look the same? Maybe we want grey-skinned and green-skinned goblins
or some goblins that can cast spells or which wield different weapons? We *could* make subclasses of
`Goblin`, like `GreySkinnedGoblin` and `GoblinWieldingClub`. But that seems a bit excessive (and a
lot of Python code for every little thing). Using classes can also become impractical when wanting
to combine them - what if we want a grey-skinned goblin shaman wielding a spear - setting up a web
of classes inheriting each other with multiple inheritance can be tricky.
This is what the *prototype* is for. It is a Python dictionary that describes these per-instance
changes to an object. The prototype also has the advantage of allowing an in-game builder to
customize an object without access to the Python backend. Evennia also allows for saving and
searching prototypes so other builders can find and use (and tweak) them later. Having a library of
interesting prototypes is a good reasource for builders. The OLC system allows for creating, saving,
loading and manipulating prototypes using a menu system.
The *spawner* takes a prototype and uses it to create (spawn) new, custom objects.
## Using the OLC
Enter the `olc` command or `@spawn/olc` to enter the prototype wizard. This is a menu system for
creating, loading, saving and manipulating prototypes. It's intended to be used by in-game builders
and will give a better understanding of prototypes in general. Use `help` on each node of the menu
for more information. Below are further details about how prototypes work and how they are used.
## The prototype
The prototype dictionary can either be created for you by the OLC (see above), be written manually
in a Python module (and then referenced by the `@spawn` command/OLC), or created on-the-fly and
manually loaded into the spawner function or `@spawn` command.
The dictionary defines all possible database-properties of an Object. It has a fixed set of allowed
keys. When preparing to store the prototype in the database (or when using the OLC), some
of these keys are mandatory. When just passing a one-time prototype-dict to the spawner the system
is
more lenient and will use defaults for keys not explicitly provided.
In dictionary form, a prototype can look something like this:
```python
{
"prototype_key": "house"
"key": "Large house"
"typeclass": "typeclasses.rooms.house.House"
}
```
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", ...}
> Note that the prototype dict as given on the command line must be a valid Python structure -
so you need to put quotes around strings etc. For security reasons, a dict inserted from-in game
cannot have any
other advanced Python functionality, such as executable code, `lambda` etc. If builders are supposed
to be able to use such features, you need to offer them through [$protfuncs](Spawner-and-
Prototypes#protfuncs), embedded runnable functions that you have full control to check and vet
before running.
### Prototype keys
All keys starting with `prototype_` are for book keeping.
- `prototype_key` - the 'name' of the prototype. While this can sometimes be skipped (such as when
defining a prototype in a module or feeding a prototype-dict manually to the spawner function),
it's good
practice to try to include this. It is used for book-keeping and storing of the prototype so you
can find it later.
- `prototype_parent` - If given, this should be the `prototype_key` of another prototype stored in
the system or available in a module. This makes this prototype *inherit* the keys from the
parent and only override what is needed. Give a tuple `(parent1, parent2, ...)` for multiple
left-right inheritance. If this is not given, a `typeclass` should usually be defined (below).
- `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
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.
- `location` - this should be a `#dbref`.
- `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) like `"edit:all();control:perm(Builder)"`
- `aliases` - list of strings for use as aliases
- `tags` - list [Tags](Tags). These are given as tuples `(tag, category, data)`.
- `attrs` - list of [Attributes](Attributes). These are given as tuples `(attrname, value,
category, lockstring)`
- Any other keywords are interpreted as non-category [Attributes](Attributes) and their values.
This is
convenient for simple Attributes - use `attrs` for full control of Attributes.
Deprecated as of Evennia 0.8:
- `ndb_<name>` - sets the value of a non-persistent attribute (`"ndb_"` is stripped from the name).
This is simply not useful in a prototype and is deprecated.
- `exec` - This accepts a code snippet or a list of code snippets to run. This should not be used -
use callables or [$protfuncs](Spawner-and-Prototypes#protfuncs) instead (see below).
### Prototype values
The prototype supports values of several different types.
It can be a hard-coded value:
```python
{"key": "An ugly goblin", ...}
```
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:
```python
{"key": lambda: random.choice(("Urfgar", "Rick the smelly", "Blargh the foul", ...)), ...}
```
#### Protfuncs
Finally, the value can be a *prototype function* (*Protfunc*). These look like simple function calls
that you embed in strings and that has a `$` in front, like
```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 works in much the same way as an
[InlineFunc](TextTags#inline-functions) - they are actually
parsed using the same parser - except protfuncs are run every time the prototype is used to spawn a
new object (whereas an inlinefunc is called when a text is returned to the user).
Here is how a protfunc is defined (same as an inlinefunc).
```python
# this is a silly example, you can just color the text red with |r directly!
def red(*args, **kwargs):
"""
Usage: $red(<text>)
Returns the same text you entered, but red.
"""
if not args or len(args) > 1:
raise ValueError("Must have one argument, the text to color red!")
return "|r{}|n".format(args[0])
```
> 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`:
```python
# in mygame/server/conf/settings.py
PROT_FUNC_MODULES += ["world.myprotfuncs"]
```
All *global callables* in your added module will be considered a new protfunc. To avoid this (e.g.
to have helper functions that are not protfuncs on their own), name your function something starting
with `_`.
The default protfuncs available out of the box are defined in `evennia/prototypes/profuncs.py`. To
override the ones available, just add the same-named function in your own protfunc module.
| Protfunc | Description |
| `$random()` | Returns random value in range [0, 1) |
| `$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. |
| `$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.
## Storing prototypes
A prototype can be defined and stored in two ways, either in the database or as a dict in a module.
### Database prototypes
Stored as [Scripts](Scripts) in the database. These are sometimes referred to as *database-
prototypes* This is the only way for in-game builders to modify and add prototypes. They have the
advantage of being easily modifiable and sharable between builders but you need to work with them
using in-game tools.
### Module-based prototypes
These prototypes are defined as dictionaries assigned to global variables in one of the modules
defined in `settings.PROTOTYPE_MODULES`. They can only be modified from outside the game so they are
are necessarily "read-only" from in-game and cannot be modified (but copies of them could be made
into database-prototypes). These were the only prototypes available before Evennia 0.8. Module based
prototypes can be useful in order for developers to provide read-only "starting" or "base"
prototypes to build from or if they just prefer to work offline in an external code editor.
By default `mygame/world/prototypes.py` is set up for you to add your own prototypes. *All global
dicts* in this module will be considered by Evennia to be a prototype. You could also tell Evennia
to look for prototypes in more modules if you want:
```python
# in mygame/server/conf.py
PROTOTYPE_MODULES = += ["world.myownprototypes", "combat.prototypes"]
```
Here is an example of a prototype defined in a module:
```python
# in a module Evennia looks at for prototypes,
# (like mygame/world/prototypes.py)
ORC_SHAMAN = {"key":"Orc shaman",
"typeclass": "typeclasses.monsters.Orc",
"weapon": "wooden staff",
"health": 20}
```
> Note that in the example above, `"ORC_SHAMAN"` will become the `prototype_key` of this prototype.
> It's the only case when `prototype_key` can be skipped in a prototype. However, if `prototype_key`
> was given explicitly, that would take precedence. This is a legacy behavior and it's recommended
> that you always add `prototype_key` to be consistent.
## Using @spawn
The spawner can be used from inside the game through the Builder-only `@spawn` command. Assuming the
"goblin" typeclass is available to the system (either as a database-prototype or read from module),
you can spawn a new goblin with
@spawn goblin
You can also specify the prototype directly as a valid Python dictionary:
@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.
## Using evennia.prototypes.spawner()
In code you access the spawner mechanism directly via the call
```python
new_objects = evennia.prototypes.spawner.spawn(*prototypes)
```
All arguments are prototype dictionaries. The function will return a
matching list of created objects. Example:
```python
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.
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).

View file

@ -0,0 +1,169 @@
# Tags
A common task of a game designer is to organize and find groups of objects and do operations on
them. A classic example is to have a weather script affect all "outside" rooms. Another would be for
a player casting a magic spell that affects every location "in the dungeon", but not those
"outside". Another would be to quickly find everyone joined with a particular guild or everyone
currently dead.
*Tags* are short text labels that you attach to objects so as to easily be able to retrieve and
group them. An Evennia entity can be tagged with any number of Tags. On the database side, Tag
entities are *shared* between all objects with that tag. This makes them very efficient but also
fundamentally different from [Attributes](Attributes), each of which always belongs to one *single*
object.
In Evennia, Tags are technically also used to implement `Aliases` (alternative names for objects)
and `Permissions` (simple strings for [Locks](Locks) to check for).
## 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.
> Not specifying a category (default) gives the tag a category of `None`, which is also considered a
unique key + category combination.
When Tags are assigned to game entities, these entities are actually sharing the same Tag. This
means that Tags are not suitable for storing information about a single object - use an
[Attribute](Attributes) for this instead. Tags are a lot more limited than Attributes but this also
makes them very quick to lookup in the database - this is the whole point.
Tags have the following properties, stored in the database:
- **key** - the name of the Tag. This is the main property to search for when looking up a Tag.
- **category** - this category allows for retrieving only specific subsets of tags used for
different purposes. You could have one category of tags for "zones", another for "outdoor
locations", for example. If not given, the category will be `None`, which is also considered a
separate, default, category.
- **data** - this is an optional text field with information about the tag. Remember that Tags are
shared between entities, so this field cannot hold any object-specific information. Usually it would
be used to hold info about the group of entities the Tag is tagging - possibly used for contextual
help like a tool tip. It is not used by default.
There are also two special properties. These should usually not need to be changed or set, it is
used internally by Evennia to implement various other uses it makes of the `Tag` object:
- **model** - this holds a *natural-key* description of the model object that this tag deals with,
on the form *application.modelclass*, for example `objects.objectdb`. It used by the TagHandler of
each entity type for correctly storing the data behind the scenes.
- **tagtype** - this is a "top-level category" of sorts for the inbuilt children of Tags, namely
*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
You can tag any *typeclassed* object, namely [Objects](Objects), [Accounts](Accounts),
[Scripts](Scripts) and [Channels](Communications). 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")
mychair.tags.add("furniture", category="luxurious")
myroom.tags.add("dungeon#01")
myscript.tags.add("weather", category="climate")
myaccount.tags.add("guestaccount")
mychair.tags.all() # returns a list of Tags
mychair.tags.remove("furniture")
mychair.tags.clear()
```
Adding a new tag will either create a new Tag or re-use an already existing one. Note that there are
_two_ "furniture" tags, one with a `None` category, and one with the "luxurious" category.
When using `remove`, the `Tag` is not deleted but are just disconnected from the tagged object. This
makes for very quick operations. The `clear` method removes (disconnects) all Tags from the object.
You can also use the default `@tag` command:
@tag mychair = furniture
This tags the chair with a 'furniture' Tag (the one with a `None` category).
## 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:
```python
import evennia
# all methods return Querysets
# search for objects
objs = evennia.search_tag("furniture")
objs2 = evennia.search_tag("furniture", category="luxurious")
dungeon = evennia.search_tag("dungeon#01")
forest_rooms = evennia.search_tag(category="forest")
forest_meadows = evennia.search_tag("meadow", category="forest")
magic_meadows = evennia.search_tag("meadow", category="magical")
# search for scripts
weather = evennia.search_tag_script("weather")
climates = evennia.search_tag_script(category="climate")
# search for accounts
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.
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.
```python
import evennia
myobj1.tags.add("foo") # implies category=None
myobj2.tags.add("foo", category="bar")
# this returns a queryset with *only* myobj1
objs = evennia.search_tag("foo")
# these return a queryset with *only* myobj2
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)) tags:
@tag/search furniture
## Using 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
used in the same way as Tags above:
```python
boy.aliases.add("rascal")
boy.permissions.add("Builders")
boy.permissions.remove("Builders")
all_aliases = boy.aliases.all()
```
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](Zones).

View file

@ -0,0 +1,136 @@
# TickerHandler
One way to implement a dynamic MUD is by using "tickers", also known as "heartbeats". A ticker is a
timer that fires ("ticks") at a given interval. The tick triggers updates in various game systems.
## About Tickers
Tickers are very common or even unavoidable in other mud code bases. Certain code bases are even
hard-coded to rely on the concept of the global 'tick'. Evennia has no such notion - the decision to
use tickers is very much up to the need of your game and which requirements you have. The "ticker
recipe" is just one way of cranking the wheels.
The most fine-grained way to manage the flow of time is of course to use [Scripts](Scripts). Many
types of operations (weather being the classic example) are however done on multiple objects in the
same way at regular intervals, and for this, storing separate Scripts on each object is inefficient.
The way to do this is to use a ticker with a "subscription model" - let objects sign up to be
triggered at the same interval, unsubscribing when the updating is no longer desired.
Evennia offers an optimized implementation of the subscription model - the *TickerHandler*. This is
a singleton global handler reachable from `evennia.TICKER_HANDLER`. You can assign any *callable* (a
function or, more commonly, a method on a database object) to this handler. The TickerHandler will
then call this callable at an interval you specify, and with the arguments you supply when adding
it. This continues until the callable un-subscribes from the ticker. The handler survives a reboot
and is highly optimized in resource usage.
Here is an example of importing `TICKER_HANDLER` and using it:
```python
# we assume that obj has a hook "at_tick" defined on itself
from evennia import TICKER_HANDLER as tickerhandler
tickerhandler.add(20, obj.at_tick)
```
That's it - from now on, `obj.at_tick()` will be called every 20 seconds.
You can also import function and tick that:
```python
from evennia import TICKER_HANDLER as tickerhandler
from mymodule import myfunc
tickerhandler.add(30, myfunc)
```
Removing (stopping) the ticker works as expected:
```python
tickerhandler.remove(20, obj.at_tick)
tickerhandler.remove(30, myfunc)
```
Note that you have to also supply `interval` to identify which subscription to remove. This is
because the TickerHandler maintains a pool of tickers and a given callable can subscribe to be
ticked at any number of different intervals.
The full definition of the `tickerhandler.add` method is
```python
tickerhandler.add(interval, callback,
idstring="", persistent=True, *args, **kwargs)
```
Here `*args` and `**kwargs` will be passed to `callback` every `interval` seconds. If `persistent`
is `False`, this subscription will not survive a server reload.
Tickers are identified and stored by making a key of the callable itself, the ticker-interval, the
`persistent` flag and the `idstring` (the latter being an empty string when not given explicitly).
Since the arguments are not included in the ticker's identification, the `idstring` must be used to
have a specific callback triggered multiple times on the same interval but with different arguments:
```python
tickerhandler.add(10, obj.update, "ticker1", True, 1, 2, 3)
tickerhandler.add(10, obj.update, "ticker2", True, 4, 5)
```
> Note that, when we want to send arguments to our callback within a ticker handler, we need to
specify `idstring` and `persistent` before, unless we call our arguments as keywords, which would
often be more readable:
```python
tickerhandler.add(10, obj.update, caller=self, value=118)
```
If you add a ticker with exactly the same combination of callback, interval and idstring, it will
overload the existing ticker. This identification is also crucial for later removing (stopping) the
subscription:
```python
tickerhandler.remove(10, obj.update, idstring="ticker1")
tickerhandler.remove(10, obj.update, idstring="ticker2")
```
The `callable` can be on any form as long as it accepts the arguments you give to send to it in
`TickerHandler.add`.
> Note that everything you supply to the TickerHandler will need to be pickled at some point to be
saved into the database. Most of the time the handler will correctly store things like database
objects, but the same restrictions as for [Attributes](Attributes) apply to what the TickerHandler
may store.
When testing, you can stop all tickers in the entire game with `tickerhandler.clear()`. You can also
view the currently subscribed objects with `tickerhandler.all()`.
See the [Weather Tutorial](Weather-Tutorial) for an example of using the TickerHandler.
### When *not* to use TickerHandler
Using the TickerHandler may sound very useful but it is important to consider when not to use it.
Even if you are used to habitually relying on tickers for everything in other code bases, stop and
think about what you really need it for. This is the main point:
> You should *never* use a ticker to catch *changes*.
Think about it - you might have to run the ticker every second to react to the change fast enough.
Most likely nothing will have changed at a given moment. So you are doing pointless calls (since
skipping the call gives the same result as doing it). Making sure nothing's changed might even be
computationally expensive depending on the complexity of your system. Not to mention that you might
need to run the check *on every object in the database*. Every second. Just to maintain status quo
...
Rather than checking over and over on the off-chance that something changed, consider a more
proactive approach. Could you implement your rarely changing system to *itself* report when its
status changes? It's almost always much cheaper/efficient if you can do things "on demand". Evennia
itself uses hook methods for this very reason.
So, if you consider a ticker that will fire very often but which you expect to have no effect 99% of
the time, consider handling things things some other way. A self-reporting on-demand solution is
usually cheaper also for fast-updating properties. Also remember that some things may not need to be
updated until someone actually is examining or using them - any interim changes happening up to that
moment are pointless waste of computing time.
The main reason for needing a ticker is when you want things to happen to multiple objects at the
same time without input from something else.

View file

@ -0,0 +1,353 @@
# Typeclasses
*Typeclasses* form the core of Evennia 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), [Objects](Objects),
[Scripts](Scripts) and [Channels](Communications#Channels) 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
```
- **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.
### 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".
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#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):
```python
from evennia import DefaultObject as BaseObject
class DefaultObject(BaseObject):
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::
```python
def __init__(self, **kwargs):
# my content
super().__init__(**kwargs)
# my content
```
Apart from this, a typeclass works like any normal Python class and you can
treat it as such.
## 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:
```python
from evennia import DefaultObject
class Furniture(DefaultObject):
# this defines what 'furniture' is, like
# storing who sits on it or something.
pass
```
You can now create a new `Furniture` object in two ways. First (and usually not the most
convenient) way is to create an instance of the class and then save it manually to the database:
```python
chair = Furniture(db_key="Chair")
chair.save()
```
To use this you must give the database field names as keywords to the call. Which are available
depends on the entity you are creating, but all start with `db_*` in Evennia. This is a method you
may be familiar with if you know Django from before.
It is recommended that you instead use the `create_*` functions to create typeclassed entities:
```python
from evennia import create_object
chair = create_object(Furniture, key="Chair")
# or (if your typeclass is in a module furniture.py)
chair = create_object("furniture.Furniture", key="Chair")
```
The `create_object` (`create_account`, `create_script` etc) takes the typeclass as its first
argument; this can both be the actual class or the python path to the typeclass as found under your
game directory. So if your `Furniture` typeclass sits in `mygame/typeclasses/furniture.py`, you
could point to it as `typeclasses.furniture.Furniture`. Since Evennia will itself look in
`mygame/typeclasses`, you can shorten this even further to just `furniture.Furniture`. The create-
functions take a lot of extra keywords allowing you to set things like [Attributes](Attributes) and
[Tags](Tags) 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`:
```python
chair.db_key = "Table"
chair.save()
print(chair.db_key)
<<< Table
```
That is, we change the chair object to have the `db_key` "Table", then save this to the database.
However, you almost never do things this way; Evennia defines property wrappers for all the database
fields. These are named the same as the field, but without the `db_` part:
```python
chair.key = "Table"
print(chair.key)
<<< Table
```
The `key` wrapper is not only shorter to write, it will make sure to save the field for you, and
does so more efficiently by levering sql update mechanics under the hood. So whereas it is good to
be aware that the field is named `db_key` you should use `key` as much as you can.
Each typeclass entity has some unique fields relevant to that type. But all also share the
following fields (the wrapper name without `db_` is given):
- `key` (str): The main identifier for the entity, like "Rose", "myscript" or "Paul". `name` is an
alias.
- `date_created` (datetime): Time stamp when this object was created.
- `typeclass_path` (str): A python path pointing to the location of this (type)class
There is one special field that doesn't use the `db_` prefix (it's defined by Django):
- `id` (int): the database id (database ref) of the object. This is an ever-increasing, unique
integer. It can also be accessed as `dbid` (database ID) or `pk` (primary key). The `dbref` property
returns the string form "#id".
The typeclassed entity has several common handlers:
- `tags` - the [TagHandler](Tags) that handles tagging. Use `tags.add()` , `tags.get()` etc.
- `locks` - the [LockHandler](Locks) that manages access restrictions. Use `locks.add()`,
`locks.get()` etc.
- `attributes` - the [AttributeHandler](Attributes) that manages Attributes on the object. Use
`attributes.add()`
etc.
- `db` (DataBase) - a shortcut property to the AttributeHandler; allowing `obj.db.attrname = value`
- `nattributes` - the [Non-persistent AttributeHandler](Attributes) for attributes not saved in the
database.
- `ndb` (NotDataBase) - a shortcut property to the Non-peristent AttributeHandler. Allows
`obj.ndb.attrname = value`
Each of the typeclassed entities then extend this list with their own properties. Go to the
respective pages for [Objects](Objects), [Scripts](Scripts), [Accounts](Accounts) and
[Channels](Communications) for more info. It's also recommended that you explore the available
entities using [Evennia's flat API](Evennia-API) to explore which properties and methods they have
available.
### 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.
### 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) or the search functions like `evennia.search_objects`.
You can however also query for them directly using [Django's query
language](https://docs.djangoproject.com/en/1.7/topics/db/queries/). This makes use of a _database
manager_ that sits on all typeclasses, named `objects`. This manager holds methods that allow
database searches against that particular type of object (this is the way Django normally works
too). When using Django queries, you need to use the full field names (like `db_key`) to search:
```python
matches = Furniture.objects.get(db_key="Chair")
```
It is important that this will *only* find objects inheriting directly from `Furniture` in your
database. If there was a subclass of `Furniture` named `Sitables` you would not find any chairs
derived from `Sitables` with this query (this is not a Django feature but special to Evennia). To
find objects from subclasses Evennia instead makes the `get_family` and `filter_family` query
methods available:
```python
# search for all furnitures and subclasses of furnitures
# whose names starts with "Chair"
matches = Furniture.objects.filter_family(db_key__startswith="Chair")
```
To make sure to search, say, all `Scripts` *regardless* of typeclass, you need to query from the
database model itself. So for Objects, this would be `ObjectDB` in the diagram above. Here's an
example for Scripts:
```python
from evennia import ScriptDB
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
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.
However, database-saved data, like `db_*` fields, [Attributes](Attributes), [Tags](Tags) etc, are
not themselves embedded into the class and will *not* be updated automatically. This you need to
manage yourself, by searching for all relevant objects and updating or adding the data:
```python
# add a worth Attribute to all existing Furniture
for obj in Furniture.objects.all():
# this will loop over all Furniture instances
obj.db.worth = 100
```
A common use case is putting all Attributes in the `at_*_creation` hook of the entity, such as
`at_object_creation` for `Objects`. This is called every time an object is created - and only then.
This is usually what you want but it does mean already existing objects won't get updated if you
change the contents of `at_object_creation` later. You can fix this in a similar way as above
(manually setting each Attribute) or with something like this:
```python
# Re-run at_object_creation only on those objects not having the new Attribute
for obj in Furniture.objects.all():
if not obj.db.worth:
obj.at_object_creation()
```
The above examples can be run in the command prompt created by `evennia shell`. You could also run
it all in-game using `@py`. That however requires you to put the code (including imports) as one
single line using `;` and [list
comprehensions](http://www.secnetix.de/olli/Python/list_comprehensions.hawk), like this (ignore the
line break, that's only for readability in the wiki):
```
@py from typeclasses.furniture import Furniture;
[obj.at_object_creation() for obj in Furniture.objects.all() if not obj.db.worth]
```
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
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:
```
@typeclass objname = path.to.new.typeclass
```
There are two important switches to this command:
- `/reset` - This will purge all existing Attributes on the object and re-run the creation hook
(like `at_object_creation` for Objects). This assures you get an object which is purely of this new
class.
- `/force` - This is required if you are changing the class to be *the same* class the object
already has - it's a safety check to avoid user errors. This is usually used together with `/reset`
to re-run the creation hook on an existing class.
In code you instead use the `swap_typeclass` method which you can find on all typeclassed entities:
```python
obj_to_change.swap_typeclass(new_typeclass_path, clean_attributes=False,
run_start_hooks="all", no_default=True, clean_cmdsets=False)
```
The arguments to this method are described [in the API docs
here](github:evennia.typeclasses.models#typedobjectswap_typeclass).
## How typeclasses actually work
*This is considered an advanced section.*
Technically, typeclasses are [Django proxy
models](https://docs.djangoproject.com/en/1.7/topics/db/models/#proxy-models). The only database
models that are "real" in the typeclass system (that is, are represented by actual tables in the
database) are `AccountDB`, `ObjectDB`, `ScriptDB` and `ChannelDB` (there are also
[Attributes](Attributes) and [Tags](Tags) but they are not typeclasses themselves). All the
subclasses of them are "proxies", extending them with Python code without actually modifying the
database layout.
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
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.
There is one caveat to consider with this, and that relates to [making your own models](New-
Models): Foreign relationships to typeclasses are cached by Django and that means that if you were
to change an object in a foreign relationship via some other means than via that relationship, the
object seeing the relationship may not reliably update but will still see its old cached version.
Due to typeclasses staying so long in memory, stale caches of such relationships could be more
visible than common in Django. See the [closed issue #1098 and its
comments](https://github.com/evennia/evennia/issues/1098) for examples and solutions.

View file

@ -0,0 +1,344 @@
# Webclient
# **Web client**
Evennia comes with a MUD client accessible from a normal web browser. During development you can try
it at `http://localhost:4001/webclient`. The client consists of several parts, all under
`evennia/web/webclient/`:
`templates/webclient/webclient.html` and `templates/webclient/base.html` are the very simplistic
django html templates describing the webclient layout.
`static/webclient/js/evennia.js` is the main evennia javascript library. This handles all
communication between Evennia and the client over websockets and via AJAX/COMET if the browser can't
handle websockets. It will make the Evennia object available to the javascript namespace, which
offers methods for sending and receiving data to/from the server transparently. This is intended to
be used also if swapping out the gui front end.
`static/webclient/js/webclient_gui.js` is the default plugin manager. It adds the `plugins` and
`plugin_manager` objects to the javascript namespace, coordinates the GUI operations between the
various plugins, and uses the Evennia object library for all in/out.
`static/webclient/js/plugins` provides a default set of plugins that implement a "telnet-like"
interface.
`static/webclient/css/webclient.css` is the CSS file for the client; it also defines things like how
to display ANSI/Xterm256 colors etc.
The server-side webclient protocols are found in `evennia/server/portal/webclient.py` and
`webclient_ajax.py` for the two types of connections. You can't (and should not need to) modify
these.
## Customizing the web client
Like was the case for the website, you override the webclient from your game directory. You need to
add/modify a file in the matching directory location within one of the _overrides directories.
These _override directories are NOT directly used by the web server when the game is running, the
server copies everything web related in the Evennia folder over to `mygame/web/static/` and then
copies in all of your _overrides. This can cause some cases were you edit a file, but it doesn't
seem to make any difference in the servers behavior. **Before doing anything else, try shutting
down the game and running `evennia collectstatic` from the command line then start it back up, clear
your browser cache, and see if your edit shows up.**
Example: To change the utilized plugin list, you need to override base.html by copying
`evennia/web/webclient/templates/webclient/base.html` to
`mygame/web/template_overrides/webclient/base.html` and editing it to add your new plugin.
# Evennia Web Client API (from evennia.js)
* `Evennia.init( opts )`
* `Evennia.connect()`
* `Evennia.isConnected()`
* `Evennia.msg( cmdname, args, kwargs, callback )`
* `Evennia.emit( cmdname, args, kwargs )`
* `log()`
# Plugin Manager API (from webclient_gui.js)
* `options` Object, Stores key/value 'state' that can be used by plugins to coordinate behavior.
* `plugins` Object, key/value list of the all the loaded plugins.
* `plugin_handler` Object
* `plugin_handler.add("name", plugin)`
* `plugin_handler.onSend(string)`
# Plugin callbacks API
* `init()` -- The only required callback
* `boolean onKeydown(event)` This plugin listens for Keydown events
* `onBeforeUnload()` This plugin does something special just before the webclient page/tab is
closed.
* `onLoggedIn(args, kwargs)` This plugin does something when the webclient first logs in.
* `onGotOptions(args, kwargs)` This plugin does something with options sent from the server.
* `boolean onText(args, kwargs)` This plugin does something with messages sent from the server.
* `boolean onPrompt(args, kwargs)` This plugin does something when the server sends a prompt.
* `boolean onUnknownCmd(cmdname, args, kwargs)` This plugin does something with "unknown commands".
* `onConnectionClose(args, kwargs)` This plugin does something when the webclient disconnects from
the server.
* `newstring onSend(string)` This plugin examines/alters text that other plugins generate. **Use
with caution**
The order of the plugins defined in `base.html` is important. All the callbacks for each plugin
will be executed in that order. Functions marked "boolean" above must return true/false. Returning
true will short-circuit the execution, so no other plugins lower in the base.html list will have
their callback for this event called. This enables things like the up/down arrow keys for the
history.js plugin to always occur before the default_in.js plugin adds that key to the current input
buffer.
# Example/Default Plugins (plugins/*.js)
* `clienthelp.js` Defines onOptionsUI from the options2 plugin. This is a mostly empty plugin to
add some "How To" information for your game.
* `default_in.js` Defines onKeydown. <enter> key or mouse clicking the arrow will send the currently
typed text.
* `default_out.js` Defines onText, onPrompt, and onUnknownCmd. Generates HTML output for the user.
* `default_unload.js` Defines onBeforeUnload. Prompts the user to confirm that they meant to
leave/close the game.
* `font.js` Defines onOptionsUI. The plugin adds the ability to select your font and font size.
* `goldenlayout_default_config.js` Not actually a plugin, defines a global variable that
goldenlayout uses to determine its window layout, known tag routing, etc.
* `goldenlayout.js` Defines onKeydown, onText and custom functions. A very powerful "tabbed" window
manager for drag-n-drop windows, text routing and more.
* `history.js` Defines onKeydown and onSend. Creates a history of past sent commands, and uses arrow
keys to peruse.
* `hotbuttons.js` Defines onGotOptions. A Disabled-by-default plugin that defines a button bar with
user-assignable commands.
* `iframe.js` Defines onOptionsUI. A goldenlayout-only plugin to create a restricted browsing sub-
window for a side-by-side web/text interface, mostly an example of how to build new HTML
"components" for goldenlayout.
* `message_routing.js` Defines onOptionsUI, onText, onKeydown. This goldenlayout-only plugin
implements regex matching to allow users to "tag" arbitrary text that matches, so that it gets
routed to proper windows. Similar to "Spawn" functions for other clients.
* `multimedia.js` An basic plugin to allow the client to handle "image" "audio" and "video" messages
from the server and display them as inline HTML.
* `notifications.js` Defines onText. Generates browser notification events for each new message
while the tab is hidden.
* `oob.js` Defines onSend. Allows the user to test/send Out Of Band json messages to the server.
* `options.js` Defines most callbacks. Provides a popup-based UI to coordinate options settings with
the server.
* `options2.js` Defines most callbacks. Provides a goldenlayout-based version of the
options/settings tab. Integrates with other plugins via the custom onOptionsUI callback.
* `popups.js` Provides default popups/Dialog UI for other plugins to use.
* `splithandler.js` Defines onText. Provides an older, less-flexible alternative to goldenlayout for
multi-window UI to automatically separate out screen real-estate by type of message.
# Writing your own Plugins
So, you love the functionality of the webclient, but your game has specific types of text that need
to be separated out into their own space, visually. There are two plugins to help with this. The
Goldenlayout plugin framework, and the older Splithandler framework.
## GoldenLayout
GoldenLayout is a web framework that allows web developers and their users to create their own
tabbed/windowed layouts. Windows/tabs can be click-and-dragged from location to location by
clicking on their titlebar and dragging until the "frame lines" appear. Dragging a window onto
another window's titlebar will create a tabbed "Stack". The Evennia goldenlayout plugin defines 3
basic types of window: The Main window, input windows and non-main text output windows. The Main
window and the first input window are unique in that they can't be "closed".
The most basic customization is to provide your users with a default layout other than just one Main
output and the one starting input window. This is done by modifying your server's
goldenlayout_default_config.js.
Start by creating a new
`mygame/web/static_overrides/webclient/js/plugins/goldenlayout_default_config.js` file, and adding
the following JSON variable:
```
var goldenlayout_config = {
content: [{
type: 'column',
content: [{
type: 'row',
content: [{
type: 'column',
content: [{
type: 'component',
componentName: 'Main',
isClosable: false,
tooltip: 'Main - drag to desired position.',
componentState: {
cssClass: 'content',
types: 'untagged',
updateMethod: 'newlines',
},
}, {
type: 'component',
componentName: 'input',
id: 'inputComponent',
height: 10,
tooltip: 'Input - The last input in the layout is always the default.',
}, {
type: 'component',
componentName: 'input',
id: 'inputComponent',
height: 10,
isClosable: false,
tooltip: 'Input - The last input in the layout is always the default.',
}]
},{
type: 'column',
content: [{
type: 'component',
componentName: 'evennia',
componentId: 'evennia',
title: 'example',
height: 60,
isClosable: false,
componentState: {
types: 'some-tag-here',
updateMethod: 'newlines',
},
}, {
type: 'component',
componentName: 'evennia',
componentId: 'evennia',
title: 'sheet',
isClosable: false,
componentState: {
types: 'sheet',
updateMethod: 'replace',
},
}],
}],
}]
}]
};
```
This is a bit ugly, but hopefully, from the indentation, you can see that it creates a side-by-side
(2-column) interface with 3 windows down the left side (The Main and 2 inputs) and a pair of windows
on the right side for extra outputs. Any text tagged with "some-tag-here" will flow to the bottom
of the "example" window, and any text tagged "sheet" will replace the text already in the "sheet"
window.
Note: GoldenLayout gets VERY confused and will break if you create two windows with the "Main"
componentName.
Now, let's say you want to display text on each window using different CSS. This is where new
goldenlayout "components" come in. Each component is like a blueprint that gets stamped out when
you create a new instance of that component, once it is defined, it won't be easily altered. You
will need to define a new component, preferably in a new plugin file, and then add that into your
page (either dynamically to the DOM via javascript, or by including the new plugin file into the
base.html).
First up, follow the directions in Customizing the Web Client section above to override the
base.html.
Next, add the new plugin to your copy of base.html:
```
<script src={% static "webclient/js/plugins/myplugin.js" %} language="javascript"
type="text/javascript"></script>
```
Remember, plugins are load-order dependent, so make sure the new `<script>` tag comes before the
goldenlayout.js
Next, create a new plugin file `mygame/web/static_overrides/webclient/js/plugins/myplugin.js` and
edit it.
```
let myplugin = (function () {
//
//
var postInit = function() {
var myLayout = window.plugins['goldenlayout'].getGL();
// register our component and replace the default messagewindow
myLayout.registerComponent( 'mycomponent', function (container, componentState) {
let mycssdiv = $('<div>').addClass('myCSS');
mycssdiv.attr('types', 'mytag');
mycssdiv.attr('update_method', 'newlines');
mycssdiv.appendTo( container.getElement() );
});
console.log("MyPlugin Initialized.");
}
return {
init: function () {},
postInit: postInit,
}
})();
window.plugin_handler.add("myplugin", myplugin);
```
You can then add "mycomponent" to an item's componentName in your goldenlayout_default_config.js.
Make sure to stop your server, evennia collectstatic, and restart your server. Then make sure to
clear your browser cache before loading the webclient page.
## Older Splithandler
The splithandler.js plugin provides a means to do this, but you don't want to have to force every
player to set up their own layout every time they use the client.
Let's create a `mygame/web/static_overrides/webclient/js/plugins/layout.js` plugin!
First up, follow the directions in Customizing the Web Client section above to override the
base.html.
Next, add the new plugin to your copy of base.html:
```
<script src={% static "webclient/js/plugins/layout.js" %} language="javascript"
type="text/javascript"></script>
```
Remember, plugins are load-order dependent, so make sure the new `<script>` tag comes after the
splithandler.js
And finally create the layout.js file and add the minimum skeleton of a plugin to it:
```
// my new plugin
var my_plugin = (function () {
let init = function () {
console.log("myplugin! Hello World!");
}
return {
init: init,
}
})();
plugin_handler.add("myplugin", my_plugin);
```
Now, `evennia stop`, `evennia collectstatic`, and `evennia start` and then load the webclient up in
your browser.
Enable developer options and look in the console, and you should see the message 'myplugin! Hello
World!'
Since our layout.js plugin is going to use the splithandler, let's enhance this by adding a check to
make sure the splithandler.js plugin has been loaded:
change the above init function to:
```
let init = function () {
let splithandler = plugins['splithandler'];
if( splithandler ) {
console.log("MyPlugin initialized");
} else {
alert('MyPlugin requires the splithandler.js plugin. Please contact the game maintainer to
correct this');
}
}
```
And finally, the splithandler.js provides provides two functions to cut up the screen real-estate:
`dynamic_split( pane_name_to_cut_apart, direction_of_split, new_pane_name1, new_pane_name2,
text_flow_pane1, text_flow_pane2, array_of_split_percentages )`
and
`set_pane_types( pane_to_set, array_of_known_message_types_to_assign)`
In this case, we'll cut it into 3 panes, 1 bigger, two smaller, and assign 'help' messages to the
top-right pane:
```
let init = function () {
let splithandler = plugins['splithandler'];
if( splithandler ) {
splithandler.dynamic_split("main","horizontal","left","right","linefeed","linefeed",[50,50]);
splithandler.dynamic_split("right","vertical","help","misc","replace","replace",[50,50]);
splithandler.set_pane_types('help', ['help']);
console.log("MyPlugin initialized");
} else {
alert('MyPlugin requires the splithandler.js plugin. Please contact the game maintainer to
correct this');
}
}
```
`evennia stop`, `evennia collectstatic`, and `evennia start` once more, and force-reload your
browser page to clear any cached version. You should now have a nicely split layout.