Updated HTML docs

This commit is contained in:
Evennia docbuilder action 2022-06-22 06:30:53 +00:00
parent bd82579bfa
commit 70b4caedb6
105 changed files with 2389 additions and 2138 deletions

View file

@ -160,6 +160,14 @@ Up requirements to Django 4.0+, Twisted 22+, Python 3.9 or 3.10
- Attribute storage support defaultdics (Hendher)
- Add ObjectParent mixin to default game folder template as an easy, ready-made
way to override features on all ObjectDB-inheriting objects easily.
- Add `TagProperty`, `AliasProperty` and `PermissionProperty` to assign these
data in a similar way to django fields.
- The db pickle-serializer now checks for methods `__serialize_dbobjs__` and `__deserialize_dbobjs__`
to allow custom packing/unpacking of nested dbobjs, to allow storing in Attribute.
- Optimizations to rpsystem contrib performance. Breaking change: `.get_sdesc()` will
now return `None` instead of `.db.desc` if no sdesc is set; fallback in hook (inspectorCaracal)
- Reworked text2html parser to avoid problems with stateful color tags (inspectorCaracal)
- Simplified `EvMenu.options_formatter` hook to use `EvColumn` and f-strings (inspectorcaracal)
## Evennia 0.9.5

View file

@ -3,33 +3,43 @@
```{code-block}
:caption: In-game
> set obj/myattr = "test"
```
```
```{code-block} python
:caption: In-code
obj.db.foo = [1,2,3, "bar"]
:caption: In-code, using the .db wrapper
obj.db.foo = [1, 2, 3, "bar"]
value = obj.db.foo
```
```{code-block} python
:caption: In-code, using the .attributes handler
obj.attributes.add("myattr", 1234, category="bar")
value = attributes.get("myattr", category="bar")
```
```{code-block} python
:caption: In-code, using `AttributeProperty` at class level
from evennia import DefaultObject
from evennia import AttributeProperty
_Attributes_ allow you to to store arbitrary data on objects and make sure the data survives a
server reboot. An Attribute can store pretty much any
Python data structure and data type, like numbers, strings, lists, dicts etc. You can also
class MyObject(DefaultObject):
foo = AttributeProperty(default=[1, 2, 3, "bar"])
myattr = AttributeProperty(100, category='bar')
```
_Attributes_ allow you to to store arbitrary data on objects and make sure the data survives a server reboot. An Attribute can store pretty much any
Python data structure and data type, like numbers, strings, lists, dicts etc. You can also
store (references to) database objects like characters and rooms.
- [What can be stored in an Attribute](#what-types-of-data-can-i-save-in-an-attribute) is a must-read
also for experienced developers, to avoid getting surprised. Attributes can store _almost_ everything
- [What can be stored in an Attribute](#what-types-of-data-can-i-save-in-an-attribute) is a must-read to avoid being surprised, also for experienced developers. Attributes can store _almost_ everything
but you need to know the quirks.
- [NAttributes](#in-memory-attributes-nattributes) are the in-memory, non-persistent
- [NAttributes](#in-memory-attributes-nattributes) are the in-memory, non-persistent
siblings of Attributes.
- [Managing Attributes In-game](#managing-attributes-in-game) for in-game builder commands.
## Managing Attributes in Code
## Managing Attributes in Code
Attributes are usually handled in code. All [Typeclassed](./Typeclasses.md) entities
([Accounts](./Accounts.md), [Objects](./Objects.md), [Scripts](./Scripts.md) and
[Channels](./Channels.md)) all can (and usually do) have Attributes associated with them. There
Attributes are usually handled in code. All [Typeclassed](./Typeclasses.md) entities
([Accounts](./Accounts.md), [Objects](./Objects.md), [Scripts](./Scripts.md) and
[Channels](./Channels.md)) can (and usually do) have Attributes associated with them. There
are three ways to manage Attributes, all of which can be mixed.
- [Using the `.db` property shortcut](#using-db)
@ -38,10 +48,10 @@ are three ways to manage Attributes, all of which can be mixed.
### Using .db
The simplest way to get/set Attributes is to use the `.db` shortcut:
The simplest way to get/set Attributes is to use the `.db` shortcut. This allows for setting and getting Attributes that lack a _category_ (having category `None`)
```python
import evennia
```python
import evennia
obj = evennia.create_object(key="Foo")
@ -54,10 +64,10 @@ obj.db.self_reference = obj # stores a reference to the obj
rose = evennia.search_object(key="rose")[0] # returns a list, grab 0th element
rose.db.has_thorns = True
# retrieving
# retrieving
val1 = obj.db.foo1
val2 = obj.db.foo2
weap = obj.db.weapon
weap = obj.db.weapon
myself = obj.db.self_reference # retrieve reference from db, get object back
is_ouch = rose.db.has_thorns
@ -65,26 +75,25 @@ is_ouch = rose.db.has_thorns
# this will return None, not AttributeError!
not_found = obj.db.jiwjpowiwwerw
# returns all Attributes on the object
obj.db.all
# returns all Attributes on the object
obj.db.all
# delete an Attribute
del obj.db.foo2
```
Trying to access a non-existing Attribute will never lead to an `AttributeError`. Instead
you will get `None` back. The special `.db.all` will return a list of all Attributes on
the object. You can replace this with your own Attribute `all` if you want, it will replace the
Trying to access a non-existing Attribute will never lead to an `AttributeError`. Instead
you will get `None` back. The special `.db.all` will return a list of all Attributes on
the object. You can replace this with your own Attribute `all` if you want, it will replace the
default `all` functionality until you delete it again.
### Using .attributes
If you don't know the name of the Attribute beforehand you can also use
the `AttributeHandler`, available as `.attributes`. With no extra keywords this is identical
to using the `.db` shortcut (`.db` is actually using the `AttributeHandler` internally):
If you want to group your Attribute in a category, or don't know the name of the Attribute beforehand, you can make use of
the [AttributeHandler](evennia.typeclasses.attributes.AttributeHandler), available as `.attributes` on all typeclassed entities. With no extra keywords, this is identical to using the `.db` shortcut (`.db` is actually using the `AttributeHandler` internally):
```python
is_ouch = rose.attributes.get("has_thorns")
```python
is_ouch = rose.attributes.get("has_thorns")
obj.attributes.add("helmet", "Knight's helmet")
helmet = obj.attributes.get("helmet")
@ -92,10 +101,9 @@ helmet = obj.attributes.get("helmet")
obj.attributes.add("my game log", "long text about ...")
```
With the `AttributeHandler` you can also give Attributes a `category`. By using a category you can
separate same-named Attributes on the same object which can help organization:
By using a category you can separate same-named Attributes on the same object to help organization.
```python
```python
# store (let's say we have gold_necklace and ringmail_armor from before)
obj.attributes.add("neck", gold_necklace, category="clothing")
obj.attributes.add("neck", ringmail_armor, category="armor")
@ -105,23 +113,19 @@ neck_clothing = obj.attributes.get("neck", category="clothing")
neck_armor = obj.attributes.get("neck", category="armor")
```
If you don't specify a category, the Attribute's `category` will be `None`. Note that
`None` is also considered a category of its own, so you won't find `None`-category Attributes mixed
with Attributes having categories.
If you don't specify a category, the Attribute's `category` will be `None` and can thus also be found via `.db`. `None` is considered a category of its own, so you won't find `None`-category Attributes mixed with Attributes having categories.
> When using `.db`, you will always use the `None` category.
Here are the methods of the `AttributeHandler`. See
Here are the methods of the `AttributeHandler`. See
the [AttributeHandler API](evennia.typeclasses.attributes.AttributeHandler) for more details.
- `has(...)` - this checks if the object has an Attribute with this key. This is equivalent
to doing `obj.db.attrname` except you can also check for a specific `category.
- `get(...)` - this retrieves the given Attribute. You can also provide a `default` value to return
- `get(...)` - this retrieves the given Attribute. You can also provide a `default` value to return
if the Attribute is not defined (instead of None). By supplying an
`accessing_object` to the call one can also make sure to check permissions before modifying
anything. The `raise_exception` kwarg allows you to raise an `AttributeError` instead of returning
`None` when you access a non-existing `Attribute`. The `strattr` kwarg tells the system to store
the Attribute as a raw string rather than to pickle it. While an optimization this should usually
anything. The `raise_exception` kwarg allows you to raise an `AttributeError` instead of returning
`None` when you access a non-existing `Attribute`. The `strattr` kwarg tells the system to store
the Attribute as a raw string rather than to pickle it. While an optimization this should usually
not be used unless the Attribute is used for some particular, limited purpose.
- `add(...)` - this adds a new Attribute to the object. An optional [lockstring](./Locks.md) can be
supplied here to restrict future access and also the call itself may be checked against locks.
@ -131,31 +135,30 @@ the [AttributeHandler API](evennia.typeclasses.attributes.AttributeHandler) for
Examples:
```python
```python
try:
# raise error if Attribute foo does not exist
# raise error if Attribute foo does not exist
val = obj.attributes.get("foo", raise_exception=True):
except AttributeError:
# ...
# return default value if foo2 doesn't exist
val2 = obj.attributes.get("foo2", default=[1, 2, 3, "bar"])
val2 = obj.attributes.get("foo2", default=[1, 2, 3, "bar"])
# delete foo if it exists (will silently fail if unset, unless
# raise_exception is set)
obj.attributes.remove("foo")
# view all clothes on obj
all_clothes = obj.attributes.all(category="clothes")
all_clothes = obj.attributes.all(category="clothes")
```
### Using AttributeProperty
### Using AttributeProperty
There is a third way to set up an Attribute, and that is by setting up an `AttributeProperty`. This
is done on the _class level_ of your typeclass and allows you to treat Attributes a bit like Django
database Fields.
The third way to set up an Attribute is to use an `AttributeProperty`. This
is done on the _class level_ of your typeclass and allows you to treat Attributes a bit like Django database Fields. Unlike using `.db` and `.attributes`, an `AttributeProperty` can't be created on the fly, you must assign it in the class code.
```python
```python
# mygame/typeclasses/characters.py
from evennia import DefaultCharacter
@ -163,161 +166,90 @@ from evennia.typeclasses.attributes import AttributeProperty
class Character(DefaultCharacter):
strength = AttributeProperty(default=10, category='stat', autocreate=True)
constitution = AttributeProperty(default=10, category='stat', autocreate=True)
agility = AttributeProperty(default=10, category='stat', autocreate=True)
magic = AttributeProperty(default=10, category='stat', autocreate=True)
sleepy = AttributeProperty(default=False)
poisoned = AttributeProperty(default=False)
def at_object_creation(self):
# ...
```
These "Attribute-properties" will be made available to all instances of the class.
```{important}
If you change the `default` of an `AttributeProperty` (and reload), it will
change the default for _all_ instances of that class (it will not override
explicitly changed values).
```
```python
char = evennia.search_object(Character, key="Bob")[0] # returns list, get 0th element
# get defaults
strength = char.strength # will get the default value 10
# assign new values (this will create/update new Attributes)
char.strength = 12
char.constitution = 16
char.agility = 8
char.magic = 2
# you can also do arithmetic etc
char.magic += 2 # char.magic is now 4
# check Attributes
strength = char.strength # this is now 12
is_sleepy = char.sleepy
is_poisoned = char.poisoned
del char.strength # wipes the Attribute
strength = char.strengh # back to the default (10) again
```
See the [AttributeProperty](evennia.typeclasses.attributes.AttributeProperty) docs for more
details on arguments.
An `AttributeProperty` will _not_ create an `Attribute` by default. A new `Attribute` will be created
(or an existing one retrieved/updated) will happen differently depending on how the `autocreate`
keyword:
- If `autocreate=False` (default), an `Attribute` will be created only if the field is explicitly
assigned a value (even if the value is the same as the default, such as `char.strength = 10`).
- If `autocreate=True`, an `Attribute` will be created as soon as the field is _accessed_ in
any way (So both `strength = char.strength` and `char.strength = 10` will both make sure that
an `Attribute` exists.
Example:
```python
# in mygame/typeclasses/objects.py
from evennia import create_object
from evennia import DefaultObject
from evennia.typeclasses.attributes import AttributeProperty
class Object(DefaultObject):
value_a = AttributeProperty(default="foo")
value_b = AttributeProperty(default="bar", autocreate=True)
obj = evennia.create_object(key="Dummy")
# these will find NO Attributes!
obj.db.value_a
obj.attributes.get("value_a")
obj.db.value_b
obj.attributes.get("value_b")
# get data from attribute-properties
vala = obj.value_a # returns "foo"
valb = obj.value_b # return "bar" AND creates the Attribute (autocreate)
# the autocreate property will now be found
obj.db.value_a # still not found
obj.attributes.get("value_a") # ''
obj.db.value_b # now returns "bar"
obj.attributes.get("value_b") # ''
# assign new values
obj.value_a = 10 # will now create a new Attribute
obj.value_b = 12 # will update the existing Attribute
# both are now found as Attributes
obj.db.value_a # now returns 10
obj.attributes.get("value_a") # ''
obj.db.value_b # now returns 12
obj.attributes.get("value_b") # ''
```
If you always access your Attributes via the `AttributeProperty` this does not matter that much
(it's also a bit of an optimization to not create an actual database `Attribute` unless the value changed).
But until an `Attribute` has been created, `AttributeProperty` fields will _not_ show up with the
`examine` command or by using the `.db` or `.attributes` handlers - so this is a bit inconsistent.
If this is important, you need to 'initialize' them by accessing them at least once ... something
like this:
```python
# ...
class Character(DefaultCharacter):
strength = AttributeProperty(12, autocreate=True)
agility = AttributeProperty(12, autocreate=True)
strength = AttributeProperty(10, category='stat')
constitution = AttributeProperty(11, category='stat')
agility = AttributeProperty(12, category='stat')
magic = AttributeProperty(13, category='stat')
sleepy = AttributeProperty(False, autocreate=False)
poisoned = AttributeProperty(False, autocreate=False)
def at_object_creation(self):
# initializing
self.strength # by accessing it, the Attribute is auto-created
self.agility # ''
# ...
```
```{important}
If you created your `AttributeProperty` with a `category`, you *must* specify the
category in `.attributes.get()` if you want to find it this way. Remember that
`.db` always uses a `category` of `None`.
When a new instance of the class is created, new `Attributes` will be created with the value and category given.
With `AttributeProperty`'s set up like this, one can access the underlying `Attribute` like a regular property on the created object:
```python
char = create_object(Character)
char.strength # returns 10
char.agility = 15 # assign a new value (category remains 'stat')
char.db.magic # returns None (wrong category)
char.attributes.get("agility", category="stat") # returns 15
char.db.sleepy # returns None because autocreate=False (see below)
```
```{warning}
Be careful to not assign AttributeProperty's to names of properties and methods already existing on the class, like 'key' or 'at_object_creation'. That could lead to very confusing errors.
```
The `autocreate=False` (default is `True`) used for `sleepy` and `poisoned` is worth a closer explanation. When `False`, _no_ Attribute will be auto-created for these AttributProperties unless they are _explicitly_ set.
The advantage of not creating an Attribute is that the default value given to `AttributeProperty` is returned with no database access unless you change it. This also means that if you want to change the default later, all entities previously create will inherit the new default.
The drawback is that without a database precense you can't find the Attribute via `.db` and `.attributes.get` (or by querying for it in other ways in the database):
```python
char.sleepy # returns False, no db access
char.db.sleepy # returns None - no Attribute exists
char.attributes.get("sleepy") # returns None too
char.sleepy = True # now an Attribute is created
char.db.sleepy # now returns True!
char.attributes.get("sleepy") # now returns True
char.sleepy # now returns True, involves db access
```
You can e.g. `del char.strength` to set the value back to the default (the value defined
in the `AttributeProperty`).
See the [AttributeProperty API](evennia.typeclasses.attributes.AttributeProperty) for more details on how to create it with special options, like giving access-restrictions.
## Managing Attributes in-game
Attributes are mainly used by code. But one can also allow the builder to use Attributes to
'turn knobs' in-game. For example a builder could want to manually tweak the "level" Attribute of an
Attributes are mainly used by code. But one can also allow the builder to use Attributes to
'turn knobs' in-game. For example a builder could want to manually tweak the "level" Attribute of an
enemy NPC to lower its difficuly.
When setting Attributes this way, you are severely limited in what can be stored - this is because
When setting Attributes this way, you are severely limited in what can be stored - this is because
giving players (even builders) the ability to store arbitrary Python would be a severe security
problem.
problem.
In game you can set an Attribute like this:
In game you can set an Attribute like this:
set myobj/foo = "bar"
To view, do
To view, do
set myobj/foo
set myobj/foo
or see them together with all object-info with
or see them together with all object-info with
examine myobj
The first `set`-example will store a new Attribute `foo` on the object `myobj` and give it the
The first `set`-example will store a new Attribute `foo` on the object `myobj` and give it the
value "bar".
You can store numbers, booleans, strings, tuples, lists and dicts this way. But if
You can store numbers, booleans, strings, tuples, lists and dicts this way. But if
you store a list/tuple/dict they must be proper Python structures and may _only_ contain strings
or numbers. If you try to insert an unsupported structure, the input will be converted to a
or numbers. If you try to insert an unsupported structure, the input will be converted to a
string.
set myobj/mybool = True
@ -331,8 +263,8 @@ For the last line you'll get a warning and the value instead will be saved as a
## Locking and checking Attributes
While the `set` command is limited to builders, individual Attributes are usually not
locked down. You may want to lock certain sensitive Attributes, in particular for games
While the `set` command is limited to builders, individual Attributes are usually not
locked down. You may want to lock certain sensitive Attributes, in particular for games
where you allow player building. You can add such limitations by adding a [lock string](./Locks.md)
to your Attribute. A NAttribute have no locks.
@ -341,7 +273,7 @@ The relevant lock types are
- `attrread` - limits who may read the value of the Attribute
- `attredit` - limits who may set/change this Attribute
You must use the `AttributeHandler` to assign the lockstring to the Attribute:
You must use the `AttributeHandler` to assign the lockstring to the Attribute:
```python
lockstring = "attread:all();attredit:perm(Admins)"
@ -349,7 +281,7 @@ obj.attributes.add("myattr", "bar", lockstring=lockstring)"
```
If you already have an Attribute and want to add a lock in-place you can do so
by having the `AttributeHandler` return the `Attribute` object itself (rather than
by having the `AttributeHandler` return the `Attribute` object itself (rather than
its value) and then assign the lock to it directly:
```python
@ -361,8 +293,8 @@ Note the `return_obj` keyword which makes sure to return the `Attribute` object
could be accessed.
A lock is no good if nothing checks it -- and by default Evennia does not check locks on Attributes.
To check the `lockstring` you provided, make sure you include `accessing_obj` and set
`default_access=False` as you make a `get` call.
To check the `lockstring` you provided, make sure you include `accessing_obj` and set
`default_access=False` as you make a `get` call.
```python
# in some command code where we want to limit
@ -396,13 +328,13 @@ values into a string representation before storing it to the database. This is d
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.
* Single database objects/typeclasses can be stored, despite them normally not being possible
to pickle. Evennia wil convert them to an internal representation using their classname,
database-id and creation-date with a microsecond precision. When retrieving, the object
* You can generally store any non-iterable Python entity that can be _pickled_.
* Single database objects/typeclasses can be stored, despite them normally not being possible
to pickle. Evennia will convert them to an internal representation using theihr classname,
database-id and creation-date with a microsecond precision. When retrieving, the object
instance will be re-fetched from the database using this information.
* To convert the database object, Evennia must know it's there. If you *hide* a database object
inside a non-iterable class, you will run into errors - this is not supported!
* If you 'hide' a db-obj as a property on a custom class, Evennia will not be
able to find it to serialize it. For that you need to help it out (see below).
```{code-block} python
:caption: Valid assignments
@ -413,16 +345,55 @@ obj.db.test1 = False
# a database object (will be stored as an internal representation)
obj.db.test2 = myobj
```
As mentioned, Evennia will not be able to automatically serialize db-objects
'hidden' in arbitrary properties on an object. This will lead to an error
when saving the Attribute.
```{code-block} python
:caption: Invalid, 'hidden' dbobject
# example of an invalid, "hidden" dbobject
# example of storing an invalid, "hidden" dbobject in Attribute
class Container:
def __init__(self, mydbobj):
# no way for Evennia to know this is a database object!
self.mydbobj = mydbobj
# let's assume myobj is a db-object
container = Container(myobj)
obj.db.invalid = container # will cause error!
obj.db.mydata = container # will raise error!
```
By adding two methods `__serialize_dbobjs__` and `__deserialize_dbobjs__` to the
object you want to save, you can pre-serialize and post-deserialize all 'hidden'
objects before Evennia's main serializer gets to work. Inside these methods, use Evennia's
[evennia.utils.dbserialize.dbserialize](evennia.utils.dbserialize.dbserialize) and
[dbunserialize](evennia.utils.dbserialize.dbunserialize) functions to safely
serialize the db-objects you want to store.
```{code-block} python
:caption: Fixing an invalid 'hidden' dbobj for storing in Attribute
from evennia.utils import dbserialize # important
class Container:
def __init__(self, mydbobj):
# A 'hidden' db-object
self.mydbobj = mydbobj
def __serialize_dbobjs__(self):
"""This is called before serialization and allows
us to custom-handle those 'hidden' dbobjs"""
self.mydbobj = dbserialize.dbserialize(self.mydbobj
def __deserialize_dbobjs__(self):
"""This is called after deserialization and allows you to
restore the 'hidden' dbobjs you serialized before"""
self.mydbobj = dbserialize.dbunserialize(self.mydbobj)
# let's assume myobj is a db-object
container = Container(myobj)
obj.db.mydata = container # will now work fine!
```
### Storing multiple objects
@ -472,6 +443,12 @@ obj.db.test8[2]["test"] = 5
# test8 is now [4,2,{"test":5}]
```
Note that if make some advanced iterable object, and store an db-object on it in
a way such that it is _not_ returned by iterating over it, you have created a
'hidden' db-object. See [the previous section](#storing-single-objects) for how
to tell Evennia how to serialize such hidden objects safely.
### Retrieving Mutable objects
A side effect of the way Evennia stores Attributes is that *mutable* iterables (iterables that can
@ -497,41 +474,41 @@ print(obj.db.mylist) # now also [1, 2, 3, 5]
```
When you extract your mutable Attribute data into a variable like `mylist`, think of it as getting a _snapshot_
of the variable. If you update the snapshot, it will save to the database, but this change _will not propagate to
of the variable. If you update the snapshot, it will save to the database, but this change _will not propagate to
any other snapshots you may have done previously_.
```python
```python
obj.db.mylist = [1, 2, 3, 4]
mylist1 = obj.db.mylist
mylist2 = obj.db.mylist
mylist1[3] = 5
mylist1 = obj.db.mylist
mylist2 = obj.db.mylist
mylist1[3] = 5
print(mylist1) # this is now [1, 2, 3, 5]
print(obj.db.mylist) # also updated to [1, 2, 3, 5]
print(obj.db.mylist) # also updated to [1, 2, 3, 5]
print(mylist2) # still [1, 2, 3, 4] !
print(mylist2) # still [1, 2, 3, 4] !
```
```{sidebar}
Remember, the complexities of this section only relate to *mutable* iterables - things you can update
in-place, like lists and dicts. [Immutable](https://en.wikipedia.org/wiki/Immutable) objects (strings,
in-place, like lists and dicts. [Immutable](https://en.wikipedia.org/wiki/Immutable) objects (strings,
numbers, tuples etc) are already disconnected from the database from the onset.
```
To avoid confusion with mutable Attributes, only work with one variable (snapshot) at a time and save
back the results as needed.
To avoid confusion with mutable Attributes, only work with one variable (snapshot) at a time and save
back the results as needed.
You can also choose to "disconnect" the Attribute entirely from the
database with the help of the `.deserialize()` method:
```python
obj.db.mylist = [1, 2, 3, 4, {1: 2}]
mylist = obj.db.mylist.deserialize()
mylist = obj.db.mylist.deserialize()
```
The result of this operation will be a structure only consisting of normal Python mutables (`list`
instead of `_SaverList`, `dict` instead of `_SaverDict` and so on). If you update it, you need to
instead of `_SaverList`, `dict` instead of `_SaverDict` and so on). If you update it, you need to
explicitly save it back to the Attribute for it to save.
## Properties of Attributes
@ -586,7 +563,7 @@ are **non-persistent** - they will _not_ survive a server reload.
Differences between `Attributes` and `NAttributes`:
- `NAttribute`s are always wiped on a server reload.
- They only exist in memory and never involve the database at all, making them faster to
- They only exist in memory and never involve the database at all, making them faster to
access and edit than `Attribute`s.
- `NAttribute`s can store _any_ Python structure (and database object) without limit.
- They can _not_ be set with the standard `set` command (but they are visible with `examine`)
@ -594,10 +571,10 @@ Differences between `Attributes` and `NAttributes`:
There are some important reasons we recommend using `ndb` to store temporary data rather than
the simple alternative of just storing a variable directly on an object:
- NAttributes are tracked by Evennia and will not be purged in various cache-cleanup operations
the server may do. So using them guarantees that they'll remain available at least as long as
- NAttributes are tracked by Evennia and will not be purged in various cache-cleanup operations
the server may do. So using them guarantees that they'll remain available at least as long as
the server lives.
- It's a consistent style - `.db/.attributes` and `.ndb/.nattributes` makes for clean-looking code
- It's a consistent style - `.db/.attributes` and `.ndb/.nattributes` makes for clean-looking code
where it's clear how long-lived (or not) your data is to be.
### Persistent vs non-persistent
@ -625,4 +602,4 @@ useful in a few situations though.
- `NAttribute`s have no restrictions at all on what they can store, 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!
grand vision!

View file

@ -10,14 +10,15 @@ the return from the function.
from evennia.utils.funcparser import FuncParser
def _power_callable(*args, **kwargs):
"""This will be callable as $square(number, power=<num>) in string"""
"""This will be callable as $pow(number, power=<num>) in string"""
pow = int(kwargs.get('power', 2))
return float(args[0]) ** pow
# create a parser and tell it that '$pow' means using _power_callable
parser = FuncParser({"pow": _power_callable})
```
Next, just pass a string into the parser, optionally containing `$func(...)` markers:
Next, just pass a string into the parser, containing `$func(...)` markers:
```python
parser.parse("We have that 4 x 4 x 4 is $pow(4, power=3).")
@ -71,7 +72,7 @@ You can apply inline function parsing to any string. The
from evennia.utils import funcparser
parser = FuncParser(callables, **default_kwargs)
parsed_string = parser.parser(input_string, raise_errors=False,
parsed_string = parser.parse(input_string, raise_errors=False,
escape=False, strip=False,
return_str=True, **reserved_kwargs)
@ -90,8 +91,12 @@ available to the parser as you parse strings with it. It can either be
an underscore `_`) will be considered a suitable callable. The name of the function will be the `$funcname`
by which it can be called.
- A `list` of modules/paths. This allows you to pull in modules from many sources for your parsing.
- The `**default` kwargs are optional kwargs that will be passed to _all_
callables every time this parser is used - unless the user overrides it explicitly in
their call. This is great for providing sensible standards that the user can
tweak as needed.
The other arguments to the parser:
`FuncParser.parse` takes further arguments, and can vary for every string parsed.
- `raise_errors` - By default, any errors from a callable will be quietly ignored and the result
will be that the failing function call will show verbatim. If `raise_errors` is set,
@ -102,12 +107,14 @@ The other arguments to the parser:
- `return_str` - When `True` (default), `parser` always returns a string. If `False`, it may return
the return value of a single function call in the string. This is the same as using the `.parse_to_any`
method.
- The `**default/reserved_keywords` are optional and allow you to pass custom data into _every_ function
call. This is great for including things like the current session or config options. Defaults can be
replaced if the user gives the same-named kwarg in the string's function call. Reserved kwargs are always passed,
ignoring defaults or what the user passed. In addition, the `funcparser` and `raise_errors`
reserved kwargs are always passed - the first is a back-reference to the `FuncParser` instance and the second
is the `raise_errors` boolean passed into `FuncParser.parse`.
- The `**reserved_keywords` are _always_ passed to every callable in the string.
They override any `**defaults` given when instantiating the parser and cannot
be overridden by the user - if they enter the same kwarg it will be ignored.
This is great for providing the current session, settings etc.
- The `funcparser` and `raise_errors`
are always added as reserved keywords - the first is a
back-reference to the `FuncParser` instance and the second
is the `raise_errors` boolean given to `FuncParser.parse`.
Here's an example of using the default/reserved keywords:
@ -158,7 +165,8 @@ created the parser.
However, if you _nest_ functions, the return of the innermost function may be something other than
a string. Let's introduce the `$eval` function, which evaluates simple expressions using
Python's `literal_eval` and/or `simple_eval`.
Python's `literal_eval` and/or `simple_eval`. It returns whatever data type it
evaluates to.
"There's a $toint($eval(10 * 2.2))% chance of survival."
@ -177,23 +185,66 @@ will be a string:
"There's a 22% chance of survival."
```
However, if you use the `parse_to_any` (or `parse(..., return_str=True)`) and _don't add any extra string around the outermost function call_,
However, if you use the `parse_to_any` (or `parse(..., return_str=False)`) and
_don't add any extra string around the outermost function call_,
you'll get the return type of the outermost callable back:
```python
parser.parse_to_any("$toint($eval(10 * 2.2)%")
"22%"
parser.parse_to_any("$toint($eval(10 * 2.2)")
22
parser.parse_to_any("the number $toint($eval(10 * 2.2).")
"the number 22"
parser.parse_to_any("$toint($eval(10 * 2.2)%")
"22%"
```
### Escaping special character
When entering funcparser callables in strings, it looks like a regular
function call inside a string:
```python
"This is a $myfunc(arg1, arg2, kwarg=foo)."
```
Commas (`,`) and equal-signs (`=`) are considered to separate the arguments and
kwargs. In the same way, the right parenthesis (`)`) closes the argument list.
Sometimes you want to include commas in the argument without it breaking the
argument list.
```python
"There is a $format(beautiful meadow, with dandelions) to the west."
```
You can escape in various ways.
- Prepending with the escape character `\`
```python
"There is a $format(beautiful meadow\, with dandelions) to the west."
```
- Wrapping your strings in quotes. This works like Python, and you can nest
double and single quotes inside each other if so needed. The result will
be a verbatim string that contains everything but the outermost quotes.
```python
"There is a $format('beautiful meadow, with dandelions') to the west."
```
- If you want verbatim quotes in your string, you can escape them too.
```python
"There is a $format('beautiful meadow, with \'dandelions\'') to the west."
```
### Safe convertion of inputs
Since you don't know in which order users may use your callables, they should always check the types
of its inputs and convert to the type the callable needs. Note also that when converting from strings,
there are limits what inputs you can support. This is because FunctionParser strings are often used by
non-developer players/builders and some things (such as complex classes/callables etc) are just not
safe/possible to convert from string representation.
Since you don't know in which order users may use your callables, they should
always check the types of its inputs and convert to the type the callable needs.
Note also that when converting from strings, there are limits what inputs you
can support. This is because FunctionParser strings can be used by
non-developer players/builders and some things (such as complex
classes/callables etc) are just not safe/possible to convert from string
representation.
In `evennia.utils.utils` is a helper called
[safe_convert_to_types](evennia.utils.utils.safe_convert_to_types). This function
@ -204,19 +255,24 @@ from evennia.utils.utils import safe_convert_to_types
def _process_callable(*args, **kwargs):
"""
A callable with a lot of custom options
$process(expression, local, extra=34, extra2=foo)
$process(expression, local, extra1=34, extra2=foo)
"""
args, kwargs = safe_convert_to_type(
(('py', 'py'), {'extra1': int, 'extra2': str}),
(('py', str), {'extra1': int, 'extra2': str}),
*args, **kwargs)
# args/kwargs should be correct types now
```
In other words, in the callable `$process(expression, local, extra1=..,
extra2=...)`, the first argument will be handled by the 'py' converter
(described below), the second will passed through regular Python `str`,
kwargs will be handled by `int` and `str` respectively. You can supply
your own converter function as long as it takes one argument and returns
the converted result.
In other words,
```python
@ -224,8 +280,7 @@ args, kwargs = safe_convert_to_type(
(tuple_of_arg_converters, dict_of_kwarg_converters), *args, **kwargs)
```
Each converter should be a callable taking one argument - this will be the arg/kwarg-value to convert. The
special converter `"py"` will try to convert a string argument to a Python structure with the help of the
The special converter `"py"` will try to convert a string argument to a Python structure with the help of the
following tools (which you may also find useful to experiment with on your own):
- [ast.literal_eval](https://docs.python.org/3.8/library/ast.html#ast.literal_eval) is an in-built Python
@ -339,12 +394,12 @@ references to other objects accessible via these callables.
result of `you_obj.get_display_name(looker=receiver)`. This allows for a single string to echo differently
depending on who sees it, and also to reference other people in the same way.
- `$You([key])` - same as `$you` but always capitalized.
- `$conj(verb)` ([code](evennia.utils.funcparser.funcparser_callable_conjugate)) - conjugates a verb
- `$conj(verb)` ([code](evennia.utils.funcparser.funcparser_callable_conjugate)) - conjugates a verb
between 2nd person presens to 3rd person presence depending on who
sees the string. For example `"$You() $conj(smiles)".` will show as "You smile." and "Tom smiles." depending
on who sees it. This makes use of the tools in [evennia.utils.verb_conjugation](evennia.utils.verb_conjugation)
to do this, and only works for English verbs.
- `$pron(pronoun [,options])` ([code](evennia.utils.funcparser.funcparser_callable_pronoun)) - Dynamically
- `$pron(pronoun [,options])` ([code](evennia.utils.funcparser.funcparser_callable_pronoun)) - Dynamically
map pronouns (like his, herself, you, its etc) between 1st/2nd person to 3rd person.
### Example

View file

@ -17,7 +17,7 @@ All new accounts are given a default set of permissions defined by
## Managing Permissions
In-game, you use the `perm` command to add and remove permissions
j
perm/account Tommy = Builders
perm/account/del Tommy = Builders

View file

@ -1,20 +1,33 @@
# Tags
```{code-block}
:caption: In game
> tag obj = tagname
```
```{code-block} python
:caption: In code, using .tags (TagHandler)
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.
obj.tags.add("mytag", category="foo")
obj.tags.get("mytag", category="foo")
```
*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.md), each of which always belongs to one *single*
object.
```{code-block} python
:caption: In code, using TagProperty (auto-assign tag to all instances of the class)
In Evennia, Tags are technically also used to implement `Aliases` (alternative names for objects)
and `Permissions` (simple strings for [Locks](./Locks.md) to check for).
from evennia import DefaultObject
from evennia import TagProperty
class Sword(DefaultObject):
can_be_wielded = TagProperty(category='combat')
has_sharp_edge = TagProperty(category='combat')
```
_Tags_ are short text lables one can 'hang' on objects in order to organize, group and quickly find out their properties. An Evennia entity can be tagged by any number of tags. They are more efficient than [Attributes](./Attributes.md) since on the database-side, Tags are _shared_ between all objects with that particular tag. A tag does not carry a value in itself; it either sits on the entity
Above, the tags inform us that the `Sword` is both sharp and can be wielded. If that's all they do, they could just be a normal Python flag. When tags become important is if there are a lot of objects with different combinations of tags. Maybe you have a magical spell that dulls _all_ sharp-edged objects in the castle - whether sword, dagger, spear or kitchen knife! You can then just grab all objects with the `has_sharp_edge` tag.
Another example would be a weather script affecting all rooms tagged as `outdoors` or finding all characters tagged with `belongs_to_fighter_guild`.
In Evennia, Tags are technically also used to implement `Aliases` (alternative names for objects) and `Permissions` (simple strings for [Locks](./Locks.md) to check for).
## Properties of Tags (and Aliases and Permissions)

View file

@ -17,7 +17,7 @@ updated after May 2021 will be missing some translations.
+===============+======================+==============+
| es | Spanish | Aug 2019 |
+---------------+----------------------+--------------+
| fr | French | Nov 2018 |
| fr | French | Mar 2022 |
+---------------+----------------------+--------------+
| it | Italian | Feb 2015 |
+---------------+----------------------+--------------+

View file

@ -39,7 +39,7 @@ class Health(Component):
Components may define DBFields or NDBFields at the class level.
DBField will store its values in the host's DB with a prefixed key.
NDBField will store its values in the host's NDB and will not persist.
The key used will be 'component_name__field_name'.
The key used will be 'component_name::field_name'.
They use AttributeProperty under the hood.
Example:
@ -50,7 +50,29 @@ class Health(Component):
health = DBField(default=1)
```
Note that default is optional and will default to None
Note that default is optional and will default to None.
Adding a component to a host will also a similarly named tag with 'components' as category.
A Component named health will appear as key="health, category="components".
This allows you to retrieve objects with specific components by searching with the tag.
It is also possible to add Component Tags the same way, using TagField.
TagField accepts a default value and can be used to store a single or multiple tags.
Default values are automatically added when the component is added.
Component Tags are cleared from the host if the component is removed.
Example:
```python
from evennia.contrib.base_systems.components import Component, TagField
class Health(Component):
resistances = TagField()
vulnerability = TagField(default="fire", enforce_single=True)
```
The 'resistances' field in this example can be set to multiple times and it will keep the added tags.
The 'vulnerability' field in this example will override the previous tag with the new one.
Each typeclass using the ComponentHolderMixin can declare its components

View file

@ -115,7 +115,7 @@ Try to `look` at the box to see the (default) description.
The description you get is not very exciting. Let's add some flavor.
describe box = This is a large and very heavy box.
desc box = This is a large and very heavy box.
If you try the `get` command we will pick up the box. So far so good, but if we really want this to
be a large and heavy box, people should _not_ be able to run off with it that easily. To prevent
@ -155,10 +155,10 @@ later, in the [Commands tutorial](./Adding-Commands.md).
[Scripts](../../../Components/Scripts.md) are powerful out-of-character objects useful for many "under the hood" things.
One of their optional abilities is to do things on a timer. To try out a first script, let's put one
on ourselves. There is an example script in `evennia/contrib/tutorial_examples/bodyfunctions.py`
on ourselves. There is an example script in `evennia/contrib/tutorials/bodyfunctions/bodyfunctions.py`
that is called `BodyFunctions`. To add this to us we will use the `script` command:
script self = tutorial_examples.bodyfunctions.BodyFunctions
script self = tutorials.bodyfunctions.BodyFunctions
This string will tell Evennia to dig up the Python code at the place we indicate. It already knows
to look in the `contrib/` folder, so we don't have to give the full path.
@ -179,7 +179,7 @@ output every time it fires.
When you are tired of your character's "insights", kill the script with
script/stop self = tutorial_examples.bodyfunctions.BodyFunctions
script/stop self = tutorials.bodyfunctions.BodyFunctions
You create your own scripts in Python, outside the game; the path you give to `script` is literally
the Python path to your script file. The [Scripts](../../../Components/Scripts.md) page explains more details.
@ -199,7 +199,7 @@ named simply `Object`. Let's create an object that is a little more interesting.
Let's make us one of _those_!
create/drop button:tutorial_examples.red_button.RedButton
create/drop button:tutorials.red_button.RedButton
The same way we did with the Script Earler, we specify a "Python-path" to the Python code we want Evennia
to use for creating the object. There you go - one red button.
@ -301,7 +301,7 @@ The Command-help is something you modify in Python code. We'll get to that when
add Commands. But you can also add regular help entries, for example to explain something about
the history of your game world:
sethelp/add History = At the dawn of time ...
sethelp History = At the dawn of time ...
You will now find your new `History` entry in the `help` list and read your help-text with `help History`.

View file

@ -2,137 +2,130 @@
*A list of resources that may be useful for Evennia users and developers.*
## Official Evennia links
## Official Evennia resources
- [evennia.com](https://www.evennia.com) - Main Evennia portal page. Links to all corners of Evennia.
- [Evennia github page](https://github.com/evennia/evennia) - Download code and read documentation.
- [Evennia official chat
channel](https://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb)
- Our official IRC chat #evennia at irc.freenode.net:6667.
- [Evennia forums/mailing list](https://groups.google.com/group/evennia) - Web interface to our
google group.
- [Evennia development blog](https://evennia.blogspot.com/) - Musings from the lead developer.
- [Evennia's manual on ReadTheDocs](https://readthedocs.org/projects/evennia/) - Read and download
offline in html, PDF or epub formats.
- [Evennia Game Index](http://games.evennia.com/) - An automated listing of Evennia games.
----
- [Evennia development blog](https://evennia.blogspot.com/) - Musings from the lead developer.
- [Evennia on GitHub](https://github.com/evennia/evennia) - Download code and read documentation.
- [Evennia on Open Hub](https://www.openhub.net/p/6906)
- [Evennia on OpenHatch](https://openhatch.org/projects/Evennia)
- [Evennia on PyPi](https://pypi.python.org/pypi/Evennia-MUD-Server/)
- [Evennia subreddit](https://www.reddit.com/r/Evennia/) (not much there yet though)
## Third-party Evennia utilities and resources
## Evennia Community
*For publicly available games running on Evennia, add and find those in the [Evennia game
index](http://games.evennia.com) instead!*
- [Evennia Game Index](http://games.evennia.com/) - An automated listing of Evennia games.
- [Evennia official Discord channel](https://discord.gg/AJJpcRUhtF)
- [Evennia official forums](https://github.com/evennia/evennia/discussions) on Github Discussions.
- [Evennia subreddit](https://www.reddit.com/r/Evennia/)
- [Discord Evennia channel](https://discord.gg/NecFePw) - This is a fan-driven Discord channel with
a bridge to the official Evennia IRC channel.
## Third-party Evennia tools
---
- [Discord live blog](https://discordapp.com/channels/517176782357528616/517176782781415434) of the
_Blackbirds_ Evennia game project.
- [Unreal Engine Evennia plugin](https://www.unrealengine.com/marketplace/en-US/slug/evennia-plugin)
an in-progress Unreal plugin for integrating Evennia with Epic Games' Unreal Engine.
- [The dark net/March Hare MUD](https://github.com/thedarknet/evennia) from the 2019 [DEF CON
27](https://www.defcon.org/html/defcon-27/dc-27-index.html) hacker conference in Paris. This is an
Evennia game dir with batchcode to build the custom _Hackers_ style cyberspace zone with puzzles and
challenges [used during the conference](https://dcdark.net/home#).
- [Arx sources](https://github.com/Arx-Game/arxcode) - Open-source code release of the very popular
[Arx](https://play.arxmush.org/) Evennia game. [Here are instructions for installing](Arxcode-
installing-help)
- [Evennia-wiki](https://github.com/vincent-lg/evennia-wiki) - An Evennia-specific Wiki for your
website.
- [Evcolor](https://github.com/taladan/Pegasus/blob/origin/world/utilities/evcolor) - Optional
coloration for Evennia unit-test output.
- [Paxboards](https://github.com/aurorachain/paxboards) - Evennia bulletin board system (both for
telnet/web).
- [Encarnia sources](https://github.com/whitehorse-io/encarnia) - An open-sourced game dir for
Evennia with things like races, combat etc. [Summary
here](https://www.reddit.com/r/MUD/comments/6z6s3j/encarnia_an_evennia_python_mud_code_base_with/).
- [The world of Cool battles sources](https://github.com/FlutterSprite/coolbattles) - Open source
turn-based battle system for Evennia. It also has a [live demo](http://wcb.battlestudio.com/).
- [nextRPI](https://github.com/cluebyte/nextrpi) - A github project for making a toolbox for people
to make [RPI](https://www.topmudsites.com/forums/showthread.php?t=4804)-style Evennia games.
- [Muddery](https://github.com/muddery/muddery) - A mud framework under development, based on an
older fork of Evennia. It has some specific design goals for building and extending the game based
on input files.
- [vim-evennia](https://github.com/amfl/vim-evennia) - A mode for editing batch-build files (`.ev`)
files in the [vim](https://www.vim.org/) text editor (Emacs users can use [evennia-
- [Discord relay](https://github.com/InspectorCaracal/evennia-things/tree/main/discord_relay) - Two-way chat relays between Evennia channels and Discord channels.
- [docker-compose for Evennia](https://github.com/gtaylor/evennia-docker) - A quick-install setup for running Evennia in a [Docker container](https://www.docker.com/). (See [the official Evennia docs](https://www.evennia.com/docs/latest/Running-Evennia-in-Docker.html) for more details on running Evennia with Docker.)
- [Evcolor](https://github.com/taladan/Pegasus/blob/origin/world/utilities/evcolor) - Optional coloration for Evennia unit-test output.
- [Evennia-wiki](https://github.com/vincent-lg/evennia-wiki) - An Evennia-specific Wiki for your website.
- [nextRPI](https://github.com/cluebyte/nextrpi) - A github project for making a toolbox for people to make [RPI](https://www.topmudsites.com/forums/showthread.php?t=4804)-style Evennia games.
- [Paxboards](https://github.com/aurorachain/paxboards) - Evennia bulletin board system (both for telnet/web).
- [Unreal Engine Evennia plugin](https://www.unrealengine.com/marketplace/en-US/slug/evennia-plugin) an in-progress Unreal plugin for integrating Evennia with Epic Games' Unreal Engine.
- [vim-evennia](https://github.com/amfl/vim-evennia) - A mode for editing batch-build files (`.ev`) files in the [vim](https://www.vim.org/) text editor (Emacs users can use [evennia-
mode.el](https://github.com/evennia/evennia/blob/master/evennia/utils/evennia-mode.el)).
- [The world of Cool battles sources](https://github.com/FlutterSprite/coolbattles) - Open source turn-based battle system for an older version of Evennia.
- [Other Evennia-related repos on github](https://github.com/search?p=1&q=evennia)
----
- [EvCast video series](https://www.youtube.com/playlist?list=PLyYMNttpc-SX1hvaqlUNmcxrhmM64pQXl) -
Tutorial videos explaining installing Evennia, basic Python etc.
- [Evennia-docker](https://github.com/gtaylor/evennia-docker) - Evennia in a [Docker
container](https://www.docker.com/) for quick install and deployment in just a few commands.
- [Evennia's docs in Chinese](http://www.evenniacn.com/) - A translated mirror of a slightly older
Evennia version. Announcement [here](https://groups.google.com/forum/#!topic/evennia/3AXS8ZTzJaA).
- [Evennia for MUSHers](https://musoapbox.net/topic/1150/evennia-for-mushers) - An article describing
Evennia for those used to the MUSH way of doing things.
- *[Language Understanding for Text games using Deep reinforcement
learning](http://news.mit.edu/2015/learning-language-playing-computer-games-0924#_msocom_1)*
([PDF](https://people.csail.mit.edu/karthikn/pdfs/mud-play15.pdf)) - MIT research paper using Evennia
to train AIs.
## Other useful mud development resources
## Evennia-Based Projects
- [ROM area reader](https://github.com/ctoth/area_reader) - Parser for converting ROM area files to
Python objects.
- [Gossip MUD chat network](https://gossip.haus/)
### Code bases
- [Arx sources](https://github.com/Arx-Game/arxcode) - Open-source code release of the very popular [Arx](https://play.arxmush.org/) Evennia game. [Here are instructions for installing](https://www.evennia.com/docs/1.0-dev/Howtos/Arxcode-Installation.html)
- [Encarnia sources](https://github.com/whitehorse-io/encarnia) - An open-sourced game dir for an older version of Evennia with things like races, combat etc. [Summary here](https://www.reddit.com/r/MUD/comments/6z6s3j/encarnia_an_evennia_python_mud_code_base_with/).
- [The dark net/March Hare MUD](https://github.com/thedarknet/evennia) from the 2019 [DEF CON 27](https://www.defcon.org/html/defcon-27/dc-27-index.html) hacker conference in Paris. This is an Evennia game dir with batchcode to build the custom _Hackers_ style cyberspace zone with puzzles and challenges [used during the conference](https://dcdark.net/home#).
- [Muddery](https://github.com/muddery/muddery) - A mud framework under development, based on an older fork of Evennia. It has some specific design goals for building and extending the game based on input files.
## General MUD forums and discussions
### Other
- [MUD Coder's Guild](https://mudcoders.com/) - A blog and [associated Slack
channel](https://slack.mudcoders.com/) with discussions on MUD development.
- [MuSoapbox](https://www.musoapbox.net/) - Very active Mu* game community mainly focused on MUSH-type gaming.
- [Imaginary Realities](http://journal.imaginary-realities.com/) - An e-magazine on game and MUD
design that has several articles about Evennia. There is also an
[archive of older issues](http://disinterest.org/resource/imaginary-realities/)
from 1998-2001 that are still very relevant.
- [Optional Realities](http://optionalrealities.com/) - Mud development discussion forums that has
regular articles on MUD development focused on roleplay-intensive games. After a HD crash it's not
as content-rich as it once was.
- [MudLab](http://mudlab.org/) - Mud design discussion forum
- [MudConnector](http://www.mudconnect.com/) - Mud listing and forums
- [MudBytes](http://www.mudbytes.net/) - Mud listing and forums
- [Top Mud Sites](http://www.topmudsites.com/) - Mud listing and forums
- [Planet Mud-Dev](http://planet-muddev.disinterest.org/) - A blog aggregator following blogs of
current MUD development (including Evennia) around the 'net. Worth to put among your RSS
subscriptions.
- Mud Dev mailing list archive ([mirror](http://www.disinterest.org/resource/MUD-Dev/)) -
Influential mailing list active 1996-2004. Advanced game design discussions.
- [Discord live blog](https://discordapp.com/channels/517176782357528616/517176782781415434) of the _Blackbirds_ Evennia game project.
- [Evennia for MUSHers](https://musoapbox.net/topic/1150/evennia-for-mushers) - An article describing Evennia for those used to the MUSH way of doing things.
- *[Language Understanding for Text games using Deep reinforcement learning](http://news.mit.edu/2015/learning-language-playing-computer-games-0924#_msocom_1)*
([PDF](https://people.csail.mit.edu/karthikn/pdfs/mud-play15.pdf)) - MIT research paper using Evennia to train AIs.
----
## General MU* resources
### Tools
- [ROM area reader](https://github.com/ctoth/area_reader) - Parser for converting ROM area files to Python objects.
### Informational
- [Imaginary Realities unofficial archive](http://tharsis-gate.org/articles/imaginary.html) - An e-magazine on game and MUD design that has several articles about Evennia.
- [Lost Library of MOO](https://www.hayseed.net/MOO/) - Archive of scientific articles on mudding (in particular moo).
- [Mud Client/Server Interaction](http://cryosphere.net/mud-protocol.html) - A page on classic MUD telnet protocols.
- [Mud-dev wiki](http://mud-dev.wikidot.com/) - A (very) slowly growing resource on MUD creation.
- [Mud Client/Server Interaction](http://cryosphere.net/mud-protocol.html) - A page on classic MUD
telnet protocols.
- [Mud Tech's fun/cool but ...](https://gc-taylor.com/blog/2013/01/08/mud-tech-funcool-dont-forget-ship-damned-thing/) -
Greg Taylor gives good advice on mud design.
- [Lost Library of MOO](https://www.hayseed.net/MOO/) - Archive of scientific articles on mudding (in
particular moo).
- [Nick Gammon's hints thread](http://www.gammon.com.au/forum/bbshowpost.php?bbsubject_id=5959) -
Contains a very useful list of things to think about when starting your new MUD.
- [Lost Garden](http://www.lostgarden.com/) - A game development blog with long and interesting
articles (not MUD-specific)
- [What Games Are](http://whatgamesare.com/) - A blog about general game design (not MUD-specific)
- [The Alexandrian](https://thealexandrian.net/) - A blog about tabletop roleplaying and board games,
but with lots of general discussion about rule systems and game balance that could be applicable
also for MUDs.
- [Raph Koster's laws of game design](https://www.raphkoster.com/games/laws-of-online-world-design/the-laws-of-online-world-design/) -
thought-provoking guidelines and things to think about when designing a virtual multiplayer world
(Raph is known for *Ultima Online* among other things).
- [Mud Tech's fun/cool but ...](https://gc-taylor.com/blog/2013/01/08/mud-tech-funcool-dont-forget-ship-damned-thing/) - Greg Taylor gives good advice on mud design.
- [Nick Gammon's hints thread](http://www.gammon.com.au/forum/bbshowpost.php?bbsubject_id=5959) - Contains a very useful list of things to think about when starting your new MUD.
- [Raph Koster's laws of game design](https://www.raphkoster.com/games/laws-of-online-world-design/the-laws-of-online-world-design/) - thought-provoking guidelines and things to think about when designing a virtual multiplayer world (Raph is known for *Ultima Online* among other things).
## Literature
### Community
- [Grapevine](https://grapevine.haus/) - MUD listings and inter-game chat network
- [MUD Coder's Guild](https://mudcoders.com/) - A blog and [associated Slack channel](https://slack.mudcoders.com/) with discussions on MUD development.
- [MudBytes](http://www.mudbytes.net/) - MUD listing and forums
- [MudConnector](http://www.mudconnect.com/) - MUD listing and forums
- [MudLab](http://mudlab.org/) - MUD design discussion forum
- [MuSoapbox](https://musoapbox.net/) - MU* forum mainly focused on MUSH-type gaming.
- [Top Mud Sites](http://www.topmudsites.com/) - MUD listing and forums
----
## General Game-Dev Resources
### Tools
- [GIT](https://git-scm.com/)
- [Documentation](https://git-scm.com/documentation)
- [Learn GIT in 15 minutes](https://try.github.io/levels/1/challenges/1) (interactive tutorial)
### Frameworks
- [Django's homepage](https://www.djangoproject.com/)
- [Documentation](https://docs.djangoproject.com/en)
- [Code](https://code.djangoproject.com/)
- [Twisted homepage](https://twistedmatrix.com/)
- [Documentation](https://twistedmatrix.com/documents/current/core/howto/index.html)
- [Code](https://twistedmatrix.com/trac/browser)
### Learning Python
- [Python Website](https://www.python.org/)
- [Documentation](https://www.python.org/doc/)
- [Tutorial](https://docs.python.org/tut/tut.html)
- [Library Reference](https://docs.python.org/lib/lib.html)
- [Language Reference](https://docs.python.org/ref/ref.html)
- [Python tips and tricks](https://www.siafoo.net/article/52)
- [Jetbrains Python academy](https://hyperskill.org/onboarding?track=python) - free online programming curriculum for different skill levels
### Blogs
- [Lost Garden](https://lostgarden.home.blog/) - A game development blog with long and interesting articles (not MUD-specific)
- [What Games Are](http://whatgamesare.com/) - A blog about general game design (not MUD-specific)
- [The Alexandrian](https://thealexandrian.net/) - A blog about tabletop roleplaying and board games, but with lots of general discussion about rule systems and game balance that could be applicable also for MUDs.
### Literature
- Richard Bartle *Designing Virtual Worlds*
([amazon page](https://www.amazon.com/Designing-Virtual-Worlds-Richard-Bartle/dp/0131018167)) -
Essential reading for the design of any persistent game
world, written by the co-creator of the original game *MUD*. Published in 2003 but it's still as
relevant now as when it came out. Covers everything you need to know and then some.
- Zed A. Shaw *Learn Python the Hard way* ([homepage](https://learnpythonthehardway.org/)) - Despite
the imposing name this book is for the absolute Python/programming beginner. One learns the language
by gradually creating a small text game! It has been used by multiple users before moving on to
Evennia. *Update: This used to be free to read online, this is no longer the case.*
When the rights to Designing Virtual Worlds returned to him, Richard Bartle
made the PDF of his Designing Virtual Worlds freely available through his own
website ([Designing Virtual Worlds](https://mud.co.uk/dvw/)). A direct link to
the PDF can be found [here](https://mud.co.uk/richard/DesigningVirtualWorlds.pdf).
- David M. Beazley *Python Essential Reference (4th ed)*
([amazon page](https://www.amazon.com/Python-Essential-Reference-David-Beazley/dp/0672329786/)) -
Our recommended book on Python; it not only efficiently summarizes the language but is also
@ -147,29 +140,3 @@ Contains a very useful list of things to think about when starting your new MUD.
economic theory. Written in 1730 but the translation is annotated and the essay is actually very
easy to follow also for a modern reader. Required reading if you think of implementing a sane game
economic system.
## Frameworks
- [Django's homepage](https://www.djangoproject.com/)
- [Documentation](https://docs.djangoproject.com/en)
- [Code](https://code.djangoproject.com/)
- [Twisted homepage](https://twistedmatrix.com/)
- [Documentation](https://twistedmatrix.com/documents/current/core/howto/index.html)
- [Code](https://twistedmatrix.com/trac/browser)
## Tools
- [GIT](https://git-scm.com/)
- [Documentation](https://git-scm.com/documentation)
- [Learn GIT in 15 minutes](https://try.github.io/levels/1/challenges/1) (interactive tutorial)
## Python Info
- [Python Website](https://www.python.org/)
- [Documentation](https://www.python.org/doc/)
- [Tutorial](https://docs.python.org/tut/tut.html)
- [Library Reference](https://docs.python.org/lib/lib.html)
- [Language Reference](https://docs.python.org/ref/ref.html)
- [Python tips and tricks](https://www.siafoo.net/article/52)
- [Jetbrains Python academy](https://hyperskill.org/onboarding?track=python) -
free online programming curriculum for different skill levels

View file

@ -14,6 +14,7 @@ evennia.contrib.base\_systems
evennia.contrib.base_systems.awsstorage
evennia.contrib.base_systems.building_menu
evennia.contrib.base_systems.color_markups
evennia.contrib.base_systems.components
evennia.contrib.base_systems.custom_gametime
evennia.contrib.base_systems.email_login
evennia.contrib.base_systems.ingame_python