mirror of
https://github.com/evennia/evennia.git
synced 2026-04-01 13:37:17 +02:00
Add TagProperty, AliasProperty, PermissionProperty. Default autocreate=True for AttributeProperty.
This commit is contained in:
parent
8f1f604708
commit
ef7280f55a
14 changed files with 334 additions and 160 deletions
|
|
@ -5,21 +5,31 @@
|
|||
> 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
|
||||
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
|
||||
siblings of Attributes.
|
||||
|
|
@ -29,7 +39,7 @@ store (references to) database objects like characters and rooms.
|
|||
|
||||
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
|
||||
[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,7 +48,7 @@ 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
|
||||
|
|
@ -78,9 +88,8 @@ 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")
|
||||
|
|
@ -92,8 +101,7 @@ 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
|
||||
# store (let's say we have gold_necklace and ringmail_armor from before)
|
||||
|
|
@ -105,11 +113,7 @@ 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.
|
||||
|
||||
> When using `.db`, you will always use the `None` category.
|
||||
If you don't specify a category, the Attribute's `category` will be `None` and can thus also be found via `.db`. `None` is considered a category of its own, so you won't find `None`-category Attributes mixed with Attributes having categories.
|
||||
|
||||
Here are the methods of the `AttributeHandler`. See
|
||||
the [AttributeHandler API](evennia.typeclasses.attributes.AttributeHandler) for more details.
|
||||
|
|
@ -151,9 +155,8 @@ all_clothes = obj.attributes.all(category="clothes")
|
|||
|
||||
### 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
|
||||
# mygame/typeclasses/characters.py
|
||||
|
|
@ -163,133 +166,62 @@ 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)
|
||||
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):
|
||||
# ...
|
||||
```
|
||||
|
||||
These "Attribute-properties" will be made available to all instances of the class.
|
||||
When a new instance of the class is created, new `Attributes` will be created with the value and category given.
|
||||
|
||||
```{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:
|
||||
With `AttributeProperty`'s set up like this, one can access the underlying `Attribute` like a regular property on the created object:
|
||||
|
||||
```python
|
||||
# in mygame/typeclasses/objects.py
|
||||
char = create_object(Character)
|
||||
|
||||
from evennia import create_object
|
||||
from evennia import DefaultObject
|
||||
from evennia.typeclasses.attributes import AttributeProperty
|
||||
char.strength # returns 10
|
||||
char.agility = 15 # assign a new value (category remains 'stat')
|
||||
|
||||
class Object(DefaultObject):
|
||||
|
||||
value_a = AttributeProperty(default="foo")
|
||||
value_b = AttributeProperty(default="bar", autocreate=True)
|
||||
|
||||
obj = evennia.create_object(key="Dummy")
|
||||
char.db.magic # returns None (wrong category)
|
||||
char.attributes.get("agility", category="stat") # returns 15
|
||||
|
||||
# these will find NO Attributes!
|
||||
obj.db.value_a
|
||||
obj.attributes.get("value_a")
|
||||
obj.db.value_b
|
||||
obj.attributes.get("value_b")
|
||||
char.db.sleepy # returns None because autocreate=False (see below)
|
||||
|
||||
# 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:
|
||||
```{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
|
||||
# ...
|
||||
class Character(DefaultCharacter):
|
||||
char.sleepy # returns False, no db access
|
||||
|
||||
strength = AttributeProperty(12, autocreate=True)
|
||||
agility = AttributeProperty(12, autocreate=True)
|
||||
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
|
||||
|
||||
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`.
|
||||
```
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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) 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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue