# evennia.contrib.rpg.traits.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).

## Installation

Traits are always added to a typeclass, such as the Character class.

There are two ways to set up Traits on a typeclass. The first sets up the TraitHandler as a property .traits on your class and you then access traits as e.g. .traits.strength. The other alternative uses a TraitProperty, which makes the trait available directly as e.g. .strength. This solution also uses the TraitHandler, but you don’t need to define it explicitly. You can combine both styles if you like.

### Traits with TraitHandler

Here’s an example for adding the TraitHandler to the Character class:

    # mygame/typeclasses/objects.py

    from evennia import DefaultCharacter
    from evennia.utils import lazy_property
    from evennia.contrib.rpg.traits import TraitHandler

    # ...

    class Character(DefaultCharacter):
        ...
        @lazy_property
        def traits(self):
            # this adds the handler as .traits
            return TraitHandler(self)

        def at_object_creation(self):
            # (or wherever you want)
            self.traits.add("str", "Strength", trait_type="static", base=10, mod=2, mult=2.0)
            self.traits.add("hp", "Health", trait_type="gauge", min=0, max=100)
            self.traits.add("hunting", "Hunting Skill", trait_type="counter",
                            base=10, mod=1, min=0, max=100)

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 (see below).

### TraitProperties

Using TraitProperties makes the trait available directly on the class, much like Django model fields. The drawback is that you must make sure that the name of your Traits don’t collide with any other properties/methods on your class.

    # mygame/typeclasses/objects.py

    from evennia import DefaultObject
    from evennia.utils import lazy_property
    from evennia.contrib.rpg.traits import TraitProperty

    # ...

    class Object(DefaultObject):
        ...
        strength = TraitProperty("Strength", trait_type="static", base=10, mod=2, mult=1.5)
        health = TraitProperty("Health", trait_type="gauge", min=0, base=100, mod=2)
        hunting = TraitProperty("Hunting Skill", trait_type="counter", base=10, mod=1, mult=2.0, min=0, max=100)

> Note that the property-name will become the name of the trait and you don’t supply trait_key > separately.

> The .traits TraitHandler will still be created (it’s used under the > hood. But it will only be created when the TraitProperty has been accessed at least once, > so be careful if mixing the two styles. If you want to make sure .traits is always available, > add the TraitHandler manually like shown earlier - the TraitProperty will by default use > the same handler (.traits).

## Using traits

A trait is added to the traithandler (if you use TraitProperty the handler is just created under the hood) after which one can access it as a property on the handler (similarly to how you can do .db.attrname for Attributes in Evennia).

All traits have a _read-only_ field .value. This is only used to read out results, you never manipulate it directly (if you try, it will just remain unchanged). The .value is calculated based on combining fields, like .base and .mod - which fields are available and how they relate to each other depends on the trait type.

    > obj.traits.strength.value
    18                                  # (base + mod) * mult

    > obj.traits.strength.base += 6
    obj.traits.strength.value
    27

    > obj.traits.hp.value
    102                                 # (base + mod) * mult

    > obj.traits.hp.base -= 200
    > obj.traits.hp.value
    0                                   # min of 0

    > obj.traits.hp.reset()
    > obj.traits.hp.value
    100

    # you can also access properties like a dict
    > obj.traits.hp["value"]
    100

    # you can store arbitrary data persistently for easy reference
    > obj.traits.hp.effect = "poisoned!"
    > obj.traits.hp.effect
    "poisoned!"

    # with TraitProperties:

    > obj.hunting.value
    22

    > obj.strength.value += 5
    > obj.strength.value
    32

## 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) * mult

The static trait has a base value and an optional mod-ifier and ‘mult’-iplier. The modifier defaults to 0, and the multiplier to 1.0, for no change in value. A typical use of a static trait would be a Strength stat or Skill value. That is, somethingthat 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.mult = 2.0
    > obj.traits.mytrait.value
    20

### Counter

    min/unset     base    base+mod                       max/unset
    |--------------|--------|---------X--------X------------|
                                  current    value
                                             = (current
                                             + mod)
                                             * mult

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 is added to both the base and to current. ‘.value’ is then formed by multiplying by the multiplier, which defaults to 1.0 for no change. The min/max of the range are optional, a boundary set to None will remove it. A suggested use for a Counter Trait would be to track skill values.

    > obj.traits.add("hunting", "Hunting Skill", trait_type="counter",
                       base=10, mod=1, mult=1.0, 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
    > obj.traits.hunting.mult = 100.0
    1100

    # for TraitProperties, pass the args/kwargs of traits.add() to the
    # TraitProperty constructor instead.

Counters have some extra properties:

#### .descs

The descs property 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, you will get the text matching the current 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 .value per-second, and this 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 when retrieving the current, the result will always be of the same type as the .base even rate is a non-integer value. So if base is an int (default)**, the current value will also be rounded the closest full integer. If you want to see the exact current value, set base to a float - you will then need to use round() yourself on the result if you want integers.

#### .percent()

If both min and max are defined, the .percent() method of the trait will return the value as a percentage.

    > obj.traits.hunting.percent()
    "71.0%"

    > obj.traits.hunting.percent(formatting=None)
    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 .mod modifier only applies to the max value of the gauge and not the current value. The minimum bound defaults to 0 if not set explicitly.

This trait is useful for showing commonly depletable resources 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

The Gauge trait is subclass of the Counter, so you have access to the same methods and properties where they make sense. So gauges can also have a .descs dict to describe the intervals in text, and can use .percent() to get how filled it is as a percentage etc.

The .rate is particularly relevant for gauges - useful for everything from poison slowly draining your health, to resting gradually increasing it.

### Trait

A single value of any type.

This is the ‘base’ Trait, meant to inherit from if you want to invent trait-types from scratch (most of the time you’ll probably inherit from some of the more advanced trait-type classes though).

Unlike other Trait-types, the single .value property of the base Trait can be editied. The value can hold any data that can be stored in an Attribute. If it’s an integer/float you can do arithmetic with it, but otherwise this 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.rpg.traits.Trait (or from one of the existing Trait classes).

    # in a file, say, 'mygame/world/traits.py'

    from evennia.contrib.rpg.traits import StaticTrait

    class RageTrait(StaticTrait):

        trait_type = "rage"
        default_keys = {
            "rage": 0
        }

        def berserk(self):
            self.mod = 100

        def sedate(self):
            self.mod = 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). Above we also added some helper methods.

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, trait_type='rage')
    > obj.traits.mood.rage
    30

    # as TraitProperty

    class Character(DefaultCharacter):
        rage = TraitProperty("A dark mood", rage=30, trait_type='rage')

exception `evennia.contrib.rpg.traits.traits.``TraitException`(msg)

  Bases: `RuntimeError`

  Base exception class raised by Trait objects.

  Parameters

    msg (str) – informative error message

  `__init__`(msg)

    Initialize self. See help(type(self)) for accurate signature.

class `evennia.contrib.rpg.traits.traits.``MandatoryTraitKey`

  Bases: `object`

  This 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.rpg.traits.traits.``TraitHandler`(obj, db_attribute_key='traits', db_attribute_category='traits')

  Bases: `object`

  Factory class that instantiates Trait objects. Must be assigned as a property on the class, usually with lazy_property.

  Example:

      class Object(DefaultObject):
          ...
          @lazy_property
          def traits(self):
              # this adds the handler as .traits
              return TraitHandler(self)

  `__init__`(obj, db_attribute_key='traits', db_attribute_category='traits')

    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.

      - db_attribute_category (str) – Name of DB attribute’s category to trait data storage.

  property `all`

    Get all trait keys in this handler.

    Returns

      list – All Trait keys.

  `get`(trait_key)
    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)

    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 (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).

  `remove`(trait_key)

    Remove a Trait from the handler’s parent object.

    Parameters

      trait_key (str) – The name of the trait to remove.

  `clear`()

    Remove all Traits from the handler’s parent object.

class `evennia.contrib.rpg.traits.traits.``TraitProperty`(name=None, trait_type='static', force=True, **trait_properties)

  Bases: `object`

  Optional extra: Allows for applying traits as individual properties directly on the parent class instead for properties on the .traits handler. So with this you could access data e.g. as character.hp.value instead of character.traits.hp.value. This still uses the traitshandler under the hood.

  Example:

      from evennia.utils import lazy_property
      from evennia.contrib.rpg.traits import TraitProperty

      class Character(DefaultCharacter):

          strength = TraitProperty(name="STR", trait_type="static", base=10, mod=2)
          hunting = TraitProperty("Hunting Skill", trait_type="counter",
                                  base=10, mod=1, max=100)
          health = TraitProperty(trait_type="gauge", min=0, base=100)

  `__init__`(name=None, trait_type='static', force=True, **trait_properties)

    Initialize a TraitField. Mimics TraitHandler.add input except no trait_key.

    Parameters

      - 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 (bool) – If set, create a new Trait even if a Trait with the same trait_key already exists.

    Kwargs:
      traithandler_name (str): If given, this is used as the name of the TraitHandler created

        behind the scenes. If not set, this will be a property traits on the class.

      any: All other trait_properties are the same as for adding a new trait of the given type

        using the normal TraitHandler.

class `evennia.contrib.rpg.traits.traits.``Trait`(trait_data)

  Bases: `object`

  Represents 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)

    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)

    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.rpg.traits.traits.``StaticTrait`(trait_data)

  Bases: `evennia.contrib.rpg.traits.traits.Trait`

  Static Trait. This is a single value with a modifier, multiplier, and no concept of a ‘current’ value or min/max etc.

  value = (base + mod) * mult

  `trait_type` = 'static'
  `default_keys` = {'base': 0, 'mod': 0, 'mult': 1.0}
  property `base`
  property `mod`

    The trait’s modifier.

  property `mult`

    The trait’s multiplier.

  property `value`

    The value of the Trait.

class `evennia.contrib.rpg.traits.traits.``CounterTrait`(trait_data)

  Bases: `evennia.contrib.rpg.traits.traits.Trait`

  Counter 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)*mult max/unset
    |--------------|——–|---------X--------X------------|
      current value

        = (current + mod) * mult

  - value = (current + mod) * mult, starts at (base + mod) * mult

  - 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, 'mult': 1.0, 'rate': 0, 'ratetarget': None}
  static `validate_input`(cls, trait_data)

    Add extra validation for descs

  property `base`
  property `mod`
  property `mult`
  property `min`
  property `max`
  property `current`

    The current value of the Trait. This does not have .mod added and is not .mult-iplied.

  property `value`

    The value of the Trait. (current + mod) * mult

  property `ratetarget`
  `percent`(formatting='{:3.1f}%')

    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.

  `reset`()

    Resets current property equal to base value.

  `desc`()

    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.rpg.traits.traits.``GaugeTrait`(trait_data)

  Bases: `evennia.contrib.rpg.traits.traits.CounterTrait`

  Gauge Trait.

  This emulates a gauge-meter that empties from a (base+mod) * mult value.

  min/0 max=(base+mod)*mult
    |-----------------------X---------------------------|

      > value

      = current

  - min defaults to 0

  - max value is always (base + mod) * mult

  - .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, 'mult': 1.0, 'rate': 0, 'ratetarget': None}
  property `base`
  property `mod`
  property `mult`
  property `min`
  property `max`

    The max is always (base + mod) * mult.

  property `current`

    The current value of the gauge.

  property `value`

    The value of the trait

  `percent`(formatting='{:3.1f}%')

    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.

  `reset`()

    Fills the gauge to its maximum allowed by base + mod