mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Updated HTML docs.
This commit is contained in:
parent
59e50f3fa5
commit
06bc3c8bcd
663 changed files with 2 additions and 61705 deletions
|
|
@ -1,447 +0,0 @@
|
|||
# Traits
|
||||
|
||||
Contribution by Griatch 2020, based on code by Whitenoise and Ainneve contribs, 2014
|
||||
|
||||
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 differ from normal Attributes in that they track their changes and limit
|
||||
themselves to particular value-ranges. One can add/subtract from them easily and
|
||||
they can even change dynamically at a particular rate (like you being poisoned or
|
||||
healed).
|
||||
|
||||
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:
|
||||
|
||||
```python
|
||||
# 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)
|
||||
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.
|
||||
|
||||
```python
|
||||
# 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)
|
||||
health = TraitProperty("Health", trait_type="gauge", min=0, base=100, mod=2)
|
||||
hunting = TraitProperty("Hunting Skill", trait_type="counter", base=10, mod=1, 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.
|
||||
|
||||
```python
|
||||
> obj.traits.strength.value
|
||||
12 # base + mod
|
||||
|
||||
> obj.traits.strength.base += 5
|
||||
obj.traits.strength.value
|
||||
17
|
||||
|
||||
> obj.traits.hp.value
|
||||
102 # base + mod
|
||||
|
||||
> 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
|
||||
12
|
||||
|
||||
> obj.strength.value += 5
|
||||
> obj.strength.value
|
||||
17
|
||||
|
||||
```
|
||||
|
||||
## 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.
|
||||
|
||||
```python
|
||||
> 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.
|
||||
|
||||
```python
|
||||
|
||||
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.
|
||||
|
||||
```python
|
||||
> 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. A suggested use for a Counter Trait would be to track skill values.
|
||||
|
||||
```python
|
||||
> 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
|
||||
|
||||
# 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`.
|
||||
|
||||
```python
|
||||
# (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.
|
||||
|
||||
```python
|
||||
|
||||
> 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.
|
||||
|
||||
```python
|
||||
> 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.
|
||||
|
||||
```python
|
||||
> 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.
|
||||
|
||||
|
||||
```python
|
||||
> 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).
|
||||
|
||||
```python
|
||||
# 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:
|
||||
|
||||
```python
|
||||
> 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')
|
||||
|
||||
```
|
||||
|
||||
|
||||
----
|
||||
|
||||
<small>This document page is generated from `evennia/contrib/rpg/traits/README.md`. Changes to this
|
||||
file will be overwritten, so edit that file rather than this one.</small>
|
||||
Loading…
Add table
Add a link
Reference in a new issue