diff --git a/evennia/contrib/test_traits.py b/evennia/contrib/test_traits.py index 6a1e99c7b1..1b4aeedde2 100644 --- a/evennia/contrib/test_traits.py +++ b/evennia/contrib/test_traits.py @@ -399,7 +399,13 @@ class TestTraitCounter(_TraitHandlerBase): min=0, max=10, extra_val1="xvalue1", - extra_val2="xvalue2" + extra_val2="xvalue2", + descs={ + 0: "range0", + 2: "range1", + 5: "range3", + 7: "range4", + } ) self.trait = self.traithandler.get("test1") @@ -419,6 +425,12 @@ class TestTraitCounter(_TraitHandlerBase): "max": 10, "extra_val1": "xvalue1", "extra_val2": "xvalue2" + }, + "descs": { + 0: "range0", + 2: "range1", + 5: "range3", + 7: "range4", } ) @@ -535,6 +547,22 @@ class TestTraitCounter(_TraitHandlerBase): del self.trait.min self.assertEqual(self.trait.percent(), "100.0%") + def test_descs(self): + """Test descriptions""" + self.trait.mod = 0 + self.current = 0 + self.assertEqual(self.trait.desc(), "range0") + self.current = 1 + self.assertEqual(self.trait.desc(), "range0") + self.current = 3 + self.assertEqual(self.trait.desc(), "range1") + self.current = 5 + self.assertEqual(self.trait.desc(), "range3") + self.current = 9 + self.assertEqual(self.trait.desc(), "range4") + self.current = 100 + self.assertEqual(self.trait.desc(), "range4") + class TestTraitGauge(_TraitHandlerBase): @@ -565,7 +593,8 @@ class TestTraitGauge(_TraitHandlerBase): "mod": 2, "min": 0, "extra_val1": "xvalue1", - "extra_val2": "xvalue2" + "extra_val2": "xvalue2", + "descs": None } ) def test_actual(self): diff --git a/evennia/contrib/traits.py b/evennia/contrib/traits.py index 1a071c83ec..c463f856ad 100644 --- a/evennia/contrib/traits.py +++ b/evennia/contrib/traits.py @@ -5,17 +5,16 @@ Whitenoise 2014, Ainneve contributors, Griatch 2020 -A `Trait` represents a modifiable property of (usually) a Character. They can +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, swords etc) or effects (poisoned, rested etc) and has extra -functionality beyond using plain Attributes for this. +(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 tragts on an object, its Typeclass needs to have a +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`). @@ -39,6 +38,27 @@ Here's an example for adding the TraitHandler to the base Object class: ``` +After a reload you can now try adding some example traits: + +```python +>>> obj.traits.add("hunting", "Hunting Skill", trait_type="static", value=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.current +100 +>>> obj.traits.hp -= 200 +>>> obj.traits.hp.current +0 +>>> obj.traits.hp.reset() +>>> obj.traits.hp.current +100 + +``` + ### Trait Configuration A single Trait can be one of three basic types: @@ -738,7 +758,6 @@ class NumericTrait(Trait): actual = base - """ trait_type = "numeric" @@ -916,6 +935,17 @@ class CounterTrait(NumericTrait): - actual = 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 `actual` 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"} + """ @@ -927,8 +957,20 @@ class CounterTrait(NumericTrait): "mod": 0, "min": None, "max": None, + "descs": None: } + @classmethod + def validate(cls, trait_data): + """Add extra validation for descs""" + trait_data = Trait.validate_input(trait_data) + descs = trait_data['descs'] + if isinstance(descs, dict): + if any(not (isinstance(key, (int, float)) and isinstance(value, str)) + for key in descs.items()): + raise TraitException("Trait descs must be defined on the form {number:str}") + return trait_data + # Helpers def _enforce_boundaries(self, value): """Ensures that incoming value falls within trait's range.""" @@ -1040,6 +1082,28 @@ class CounterTrait(NumericTrait): """Resets `current` property equal to `base` value.""" del self.current + def desc(self): + """ + Retrieve descriptions of the current value, if available. + + This must be a mapping {upper_bound_inclusive: text}, + ordered from small to big. + rely on Python3.7+ dicts retaining ordering to let this + describe the interval. + + Returns: + str: The description describing the `actual` value. + If not found, returns the empty string. + """ + descs = self._data["descs"] + if descs is None: + return "" + value = self.actual + # we rely on Python3.7+ dicts retaining ordering + for bound, txt in descs.items(): + if bound >= value: + return txt + class GaugeTrait(CounterTrait): """ @@ -1056,6 +1120,17 @@ class GaugeTrait(CounterTrait): - max value is always base + mad - .max is an alias of .base - actual = 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"} """ @@ -1067,6 +1142,7 @@ class GaugeTrait(CounterTrait): "base": 0, "mod": 0, "min": 0, + "descs": None } def _enforce_boundaries(self, value): @@ -1171,3 +1247,17 @@ class GaugeTrait(CounterTrait): Fills the gauge to its maximum allowed by base + mod """ del self.current + + +class SequenceTrait(CounterTrait) + """ + A trait that stores an indexed array of strings to + represent distinct values in a sequence. Adding to the trait will + step back and forth in the sequence. + + This is useful for systems which don't use numbers as much + as discrete states to represent things, such as + + "Weak, " + + """