evennia.contrib.traits¶
Traits
Whitenoise 2014, Ainneve contributors, Griatch 2020
A Trait represents a modifiable property on (usually) a Character. They can be used to represent everything from attributes (str, agi etc) to skills (hunting 10, swords 14 etc) and dynamically changing things like HP, XP etc.
Traits use Evennia Attributes under the hood, making them persistent (they survive a server reload/reboot).
Adding Traits to a typeclass¶
To access and manipulate traits on an object, its Typeclass needs to have a TraitHandler assigned it. Usually, the handler is made available as .traits (in the same way as .tags or .attributes).
Here’s an example for adding the TraitHandler to the base Object class:
# mygame/typeclasses/objects.py from evennia import DefaultObject from evennia.utils import lazy_property from evennia.contrib.traits import TraitHandler # ... class Object(DefaultObject): ... @lazy_property def traits(self): # this adds the handler as .traits return TraitHandler(self)
After a reload you can now try adding some example traits:
Using traits¶
A trait is added to the traithandler, after which one can access it as a property on the handler (similarly to how you can do .db.attrname for Attributes in Evennia).
# this is an example using the "static" trait, described below
>>> obj.traits.add("hunting", "Hunting Skill", trait_type="static", base=4)
>>> obj.traits.hunting.value
4
>>> obj.traits.hunting.value += 5
>>> obj.traits.hunting.value
9
>>> obj.traits.add("hp", "Health", trait_type="gauge", min=0, max=100)
>>> obj.traits.hp.value
100
>>> obj.traits.hp -= 200
>>> obj.traits.hp.value
0
>>> obj.traits.hp.reset()
>>> obj.traits.hp.value
100
# you can also access property with getitem
>>> obj.traits.hp["value"]
100
# you can store arbitrary data persistently as well
>>> obj.traits.hp.effect = "poisoned!"
>>> obj.traits.hp.effect
"poisoned!"
When adding the trait, you supply the name of the property (hunting) along with a more human-friendly name (“Hunting Skill”). The latter will show if you print the trait etc. The trait_type is important, this specifies which type of trait this is.
Trait types¶
All default traits have a read-only .value property that shows the relevant or ‘current’ value of the trait. Exactly what this means depends on the type of trait.
Traits can also be combined to do arithmetic with their .value, if both have a compatible type.
>>> trait1 + trait2
54
>>> trait1.value
3
>>> trait1 + 2
>>> trait1.value
5
Two numerical traits can also be compared (bigger-than etc), which is useful in all sorts of rule-resolution.
if trait1 > trait2:
# do stuff
Static trait¶
value = base + mod
The static trait has a base value and an optional mod-ifier. A typical use of a static trait would be a Strength stat or Skill value. That is, something that varies slowly or not at all, and which may be modified in-place.
>>> obj.traits.add("str", "Strength", trait_type="static", base=10, mod=2)
>>> obj.traits.mytrait.value
12 # base + mod
>>> obj.traits.mytrait.base += 2
>>> obj.traits.mytrait.mod += 1
>>> obj.traits.mytrait.value
15
>>> obj.traits.mytrait.mod = 0
>>> obj.traits.mytrait.value
12
Counter¶
- min/unset base base+mod max/unset
- |--------------|——–|---------X--------X------------|
- current value
= current + mod
A counter describes a value that can move from a base. The current property is the thing usually modified. It starts at the base. One can also add a modifier, which will both be added to the base and to current (forming .value). The min/max of the range are optional, a boundary set to None will remove it.
>>> obj.traits.add("hunting", "Hunting Skill", trait_type="counter",
base=10, mod=1, min=0, max=100)
>>> obj.traits.hunting.value
11 # current starts at base + mod
>>> obj.traits.hunting.current += 10
>>> obj.traits.hunting.value
21
# reset back to base+mod by deleting current
>>> del obj.traits.hunting.current
>>> obj.traits.hunting.value
11
>>> obj.traits.hunting.max = None # removing upper bound
Counters have some extra properties:
descs is a dict {upper_bound:text_description}. This allows for easily storing a more human-friendly description of the current value in the interval. Here is an example for skill values between 0 and 10:
{0: “unskilled”, 1: “neophyte”, 5: “trained”, 7: “expert”, 9: “master”}
The keys must be supplied from smallest to largest. Any values below the lowest and above the highest description will be considered to be included in the closest description slot. By calling .desc() on the Counter, will you get the text matching the current value value.
# (could also have passed descs= to traits.add())
>>> obj.traits.hunting.descs = {
0: "unskilled", 10: "neophyte", 50: "trained", 70: "expert", 90: "master"}
>>> obj.traits.hunting.value
11
>>> obj.traits.hunting.desc()
"neophyte"
>>> obj.traits.hunting.current += 60
>>> obj.traits.hunting.value
71
>>> obj.traits.hunting.desc()
"expert"
.rate¶
The rate property defaults to 0. If set to a value different from 0, it allows the trait to change value dynamically. This could be used for example for an attribute that was temporarily lowered but will gradually (or abruptly) recover after a certain time. The rate is given as change of the current per-second, and the .value will still be restrained by min/max boundaries, if those are set.
It is also possible to set a “.ratetarget”, for the auto-change to stop at (rather than at the min/max boundaries). This allows the value to return to a previous value.
>>> obj.traits.hunting.value
71
>>> obj.traits.hunting.ratetarget = 71
# debuff hunting for some reason
>>> obj.traits.hunting.current -= 30
>>> obj.traits.hunting.value
41
>>> obj.traits.hunting.rate = 1 # 1/s increase
# Waiting 5s
>>> obj.traits.hunting.value
46
# Waiting 8s
>>> obj.traits.hunting.value
54
# Waiting 100s
>>> obj.traits.hunting.value
71 # we have stopped at the ratetarget
>>> obj.traits.hunting.rate = 0 # disable auto-change
Note that if rate is a non-integer, the resulting .value (at least until it reaches the boundary) will likely also come out a float. If you expect an integer, you must run run int() on the result yourself.
.percentage()¶
If both min and max are defined, the .percentage() method of the trait will return the value as a percentage.
>>> obj.traits.hunting.percentage()
"71.0%"
Gauge¶
This emulates a [fuel-] gauge that empties from a base+mod value.
- min/0 max=base+mod
- |-----------------------X---------------------------|
value
= current
The ‘current’ value will start from a full gauge. The .max property is read-only and is set by .base + .mod. So contrary to a Counter, the modifier only applies to the max value of the gauge and not the current value. The minimum bound defaults to 0. This trait is useful for showing resources that can deplete, like health, stamina and the like.
>>> obj.traits.add("hp", "Health", trait_type="gauge", base=100)
>>> obj.traits.hp.value # (or .current)
100
>>> obj.traits.hp.mod = 10
>>> obj.traits.hp.value
110
>>> obj.traits.hp.current -= 30
>>> obj.traits.hp.value
80
Same as Counters, Gauges can also have descs to describe the interval and can also have rate and ratetarget to auto-update the value. The rate is particularly useful for gauges, for everything from poison slowly draining your health, to resting gradually increasing it. You can also use the .percentage() function to show the current value as a percentage.
Trait¶
A single value of any type.
This is the ‘base’ Trait, meant to inherit from if you want to make your own trait-types (see below). Its .value can be anything (that can be stored in an Attribute) and if it’s a integer/float you can do arithmetic with it, but otherwise it acts just like a glorified Attribute.
>>> obj.traits.add("mytrait", "My Trait", trait_type="trait", value=30)
>>> obj.traits.mytrait.value
30
>>> obj.traits.mytrait.value = "stringvalue"
>>> obj.traits.mytrait.value
"stringvalue"
Expanding with your own Traits¶
A Trait is a class inhering from evennia.contrib.traits.Trait (or from one of the existing Trait classes).
# in a file, say, 'mygame/world/traits.py'
from evennia.contrib.traits import Trait
class RageTrait(Trait):
trait_type = "rage"
default_keys = {
"rage": 0
}
Above is an example custom-trait-class “rage” that stores a property “rage” on itself, with a default value of 0. This has all the functionality of a Trait - for example, if you do del on the rage property, it will be set back to its default (0). If you wanted to customize what it does, you just add rage property get/setters/deleters on the class.
To add your custom RageTrait to Evennia, add the following to your settings file (assuming your class is in mygame/world/traits.py):
TRAIT_CLASS_PATHS = [“world.traits.RageTrait”]
Reload the server and you should now be able to use your trait:
>>> obj.traits.add("mood", "A dark mood", rage=30)
>>> obj.traits.mood.rage
30
-
exception
evennia.contrib.traits.TraitException(msg)[source]¶ Bases:
RuntimeErrorBase exception class raised by Trait objects.
- Parameters
msg (str) – informative error message
-
class
evennia.contrib.traits.MandatoryTraitKey[source]¶ Bases:
objectThis represents a required key that must be supplied when a Trait is initialized. It’s used by Trait classes when defining their required keys.
-
class
evennia.contrib.traits.TraitHandler(obj, db_attribute_key='traits', db_attribute_category='traits')[source]¶ Bases:
objectFactory class that instantiates Trait objects.
-
__init__(obj, db_attribute_key='traits', db_attribute_category='traits')[source]¶ Initialize the handler and set up its internal Attribute-based storage.
- Parameters
obj (Object) – Parent Object typeclass for this TraitHandler
db_attribute_key (str) – Name of the DB attribute for trait data storage
-
property
all¶ Get all trait keys in this handler.
- Returns
list – All Trait keys.
-
get(trait_key)[source]¶ - Parameters
trait_key (str) – key from the traits dict containing config data.
- Returns
(Trait or None) – named Trait class or None if trait key is not found in traits collection.
-
add(trait_key, name=None, trait_type='static', force=True, **trait_properties)[source]¶ Create a new Trait and add it to the handler.
- Parameters
trait_key (str) – This is the name of the property that will be made available on this handler (example ‘hp’).
name (str, optional) – Name of the Trait, like “Health”. If not given, will use trait_key starting with a capital letter.
trait_type (str, optional) – One of ‘static’, ‘counter’ or ‘gauge’.
force_add (bool) – If set, create a new Trait even if a Trait with the same trait_key already exists.
trait_properties (dict) – These will all be use to initialize the new trait. See the properties class variable on each Trait class to see which are required.
- Raises
TraitException – If specifying invalid values for the given Trait, the trait_type is not recognized, or an existing trait already exists (and force is unset).
-
-
class
evennia.contrib.traits.Trait(trait_data)[source]¶ Bases:
objectRepresents an object or Character trait. This simple base is just storing anything in it’s ‘value’ property, so it’s pretty much just a different wrapper to an Attribute. It does no type-checking of what is stored.
Note
See module docstring for configuration details.
value
-
trait_type= 'trait'¶
-
default_keys= {'value': None}¶
-
allow_extra_properties= True¶
-
__init__(trait_data)[source]¶ This both initializes and validates the Trait on creation. It must raise exception if validation fails. The TraitHandler will call this when the trait is furst added, to make sure it validates before storing.
- Parameters
trait_data (any) – Any pickle-able values to store with this trait. This must contain any cls.default_keys that do not have a default value in cls.data_default_values. Any extra kwargs will be made available as extra properties on the Trait, assuming the class variable allow_extra_properties is set.
- Raises
TraitException – If input-validation failed.
-
static
validate_input(cls, trait_data)[source]¶ Validate input
- Parameters
trait_data (dict or _SaverDict) – Data to be used for initialization of this trait.
- Returns
dict –
- Validated data, possibly complemented with default
values from default_keys.
- Raises
TraitException – If finding unset keys without a default.
-
property
name¶ Display name for the trait.
-
property
key¶ Display name for the trait.
-
property
value¶ Store a value
-
-
class
evennia.contrib.traits.StaticTrait(trait_data)[source]¶ Bases:
evennia.contrib.traits.TraitStatic Trait. This is a single value with a modifier, with no concept of a ‘current’ value.
value = base + mod
-
trait_type= 'static'¶
-
default_keys= {'base': 0, 'mod': 0}¶
-
property
mod¶ The trait’s modifier.
-
property
value¶ The value of the Trait
-
-
class
evennia.contrib.traits.CounterTrait(trait_data)[source]¶ Bases:
evennia.contrib.traits.TraitCounter Trait.
This includes modifications and min/max limits as well as the notion of a current value. The value can also be reset to the base value.
- min/unset base base+mod max/unset
- |--------------|——–|---------X--------X------------|
- current value
= current + mod
value = current + mod, starts at base + mod
if min or max is None, there is no upper/lower bound (default)
if max is set to “base”, max will be equal ot base+mod
descs are used to optionally describe each value interval. The desc of the current value value can then be retrieved with .desc(). The property is set as {lower_bound_inclusive:desc} and should be given smallest-to-biggest. For example, for a skill rating between 0 and 10:
- {0: “unskilled”,
1: “neophyte”, 5: “traited”, 7: “expert”, 9: “master”}
rate/ratetarget are optional settings to include a rate-of-change of the current value. This is calculated on-demand and allows for describing a value that is gradually growing smaller/bigger. The increase will stop when either reaching a boundary (if set) or ratetarget. Setting the rate to 0 (default) stops any change.
-
trait_type= 'counter'¶
-
default_keys= {'base': 0, 'descs': None, 'max': None, 'min': None, 'mod': 0, 'rate': 0, 'ratetarget': None}¶
-
property
base¶
-
property
mod¶
-
property
min¶
-
property
max¶
-
property
current¶ The current value of the Trait. This does not have .mod added.
-
property
value¶ The value of the Trait (current + mod)
-
property
ratetarget¶
-
percent(formatting='{:3.1f}%')[source]¶ Return the current value as a percentage.
- Parameters
formatting (str, optional) – Should contain a format-tag which will receive the value. If this is set to None, the raw float will be returned.
- Returns
float or str –
- Depending of if a formatting string
is supplied or not.
-
desc()[source]¶ Retrieve descriptions of the current value, if available.
This must be a mapping {upper_bound_inclusive: text}, ordered from small to big. Any value above the highest upper bound will be included as being in the highest bound. rely on Python3.7+ dicts retaining ordering to let this describe the interval.
- Returns
str –
- The description describing the value value.
If not found, returns the empty string.
-
class
evennia.contrib.traits.GaugeTrait(trait_data)[source]¶ Bases:
evennia.contrib.traits.CounterTraitGauge Trait.
This emulates a gauge-meter that empties from a base+mod value.
- min/0 max=base+mod
- |-----------------------X---------------------------|
value
= current
min defaults to 0
max value is always base + mad
.max is an alias of .base
value = current and varies from min to max.
- descs is a mapping {upper_bound_inclusive: desc}. These
are checked with .desc() and can be retrieve a text description for a given current value.
For example, this could be used to describe health values between 0 and 100:
- {0: “Dead”
10: “Badly hurt”, 30: “Bleeding”, 50: “Hurting”, 90: “Healthy”}
-
trait_type= 'gauge'¶
-
default_keys= {'base': 0, 'descs': None, 'min': 0, 'mod': 0, 'rate': 0, 'ratetarget': None}¶
-
property
base¶
-
property
mod¶
-
property
min¶
-
property
max¶ The max is always base + mod.
-
property
current¶ The current value of the gauge.
-
property
value¶ The value of the trait
-
percent(formatting='{:3.1f}%')[source]¶ Return the current value as a percentage.
- Parameters
formatting (str, optional) – Should contain a format-tag which will receive the value. If this is set to None, the raw float will be returned.
- Returns
float or str –
- Depending of if a formatting string
is supplied or not.