mirror of
https://github.com/evennia/evennia.git
synced 2026-03-30 20:47:17 +02:00
fixing goofs exposed by conditional rework, readme tweaks (+8 squashed commit)
Squashed commit: [7d0ff84f5] more readme changes... i can't stop... [8259163dc] added new removers for parity with getters, altered conditional logic [d1db0e4a2] added getter/remover section [0bec38d51] misc fixes [614df9883] adding test for stack removal, fix to stack removal logic [77149aaaf] third readme edit, buff module docstring edit, tweak to modgen samplebuff [ca992fd1c] editing buff section of readme [f33eec3d8] first edit of readme
This commit is contained in:
parent
0dad659935
commit
c15f46045d
4 changed files with 375 additions and 287 deletions
|
|
@ -2,11 +2,15 @@
|
|||
|
||||
Contribution by Tegiminis 2022
|
||||
|
||||
A buff is a timed object, attached to a game entity, that modifies values, triggers
|
||||
code, or both. It is a common design pattern in RPGs, particularly action games.
|
||||
A buff is a timed object, attached to a game entity. It is capable of modifying values, triggering code, or both.
|
||||
It is a common design pattern in RPGs, particularly action games.
|
||||
|
||||
This contrib gives you a buff handler to apply to your objects, a buff class to extend them,
|
||||
and a sample property class to show how to automatically check modifiers.
|
||||
This contrib offers you:
|
||||
- A buff handler to apply to your objects (`BuffHandler`).
|
||||
- A buff class to extend from to create your own buffs (`BaseBuff`).
|
||||
- A sample property class to show how to automatically check modifiers (`BuffableProperty`).
|
||||
- A command which applies buffs (`CmdBuff`).
|
||||
- Some sample buffs to learn from (`samplebuffs.py`).
|
||||
|
||||
## Quick Start
|
||||
Assign the handler to a property on the object, like so.
|
||||
|
|
@ -25,7 +29,7 @@ If you want to customize the handler, you can feed the constructor two arguments
|
|||
- `dbkey`: The string you wish to use as a key for the buff database. Defaults to "buffs". This allows you to keep separate buff pools - for example, "buffs" and "perks".
|
||||
- `autopause`: If you want this handler to automatically pause certain buffs when its owning object is unpuppeted.
|
||||
|
||||
> IMPORTANT: If you enable autopausing, you MUST initialize the property in your object's
|
||||
> **Note**: If you enable autopausing, you MUST initialize the property in your object's
|
||||
> `at_init` hook to cache. Otherwise, a hot reload can cause playtime buffs to not update properly
|
||||
> on puppet/unpuppet. You have been warned!
|
||||
|
||||
|
|
@ -33,27 +37,27 @@ Let's say you want another handler for an object, `perks`, which has a separate
|
|||
respects playtime buffs. You'd assign this new property as so:
|
||||
|
||||
```python
|
||||
@lazy_property
|
||||
def perks(self) -> BuffHandler:
|
||||
return BuffHandler(self, dbkey='perks', autopause=True)
|
||||
@lazy_property
|
||||
def perks(self) -> BuffHandler:
|
||||
return BuffHandler(self, dbkey='perks', autopause=True)
|
||||
```
|
||||
|
||||
And add `self.perks` to the object's `at_init`.
|
||||
And initialize it by adding `self.perks` to the object's `at_init`.
|
||||
|
||||
### Using the Handler
|
||||
## Using the Handler
|
||||
|
||||
To actually make use of the handler, you still have to do some leg work.
|
||||
Here's how to make use of your new handler.
|
||||
|
||||
#### Apply a Buff
|
||||
### Apply a Buff
|
||||
|
||||
Call the handler `add(BuffClass)` method. This requires a class reference, and also contains a number of
|
||||
Call the handler `add` method. This requires a class reference, and also contains a number of
|
||||
optional arguments to customize the buff's duration, stacks, and so on. You can also store any arbitrary value
|
||||
in the buff's cache by passing a dictionary through the `to_cache` argument. This will not overwrite the normal
|
||||
values on the cache.
|
||||
|
||||
```python
|
||||
self.buffs.add(StrengthBuff) # A single stack of StrengthBuff with normal duration
|
||||
self.buffs.add(DexBuff, stacks=3, duration=60) # Three stacks of DexBuff, with a duration of 60 seconds
|
||||
self.buffs.add(StrengthBuff) # A single stack of StrengthBuff with normal duration
|
||||
self.buffs.add(DexBuff, stacks=3, duration=60) # Three stacks of DexBuff, with a duration of 60 seconds
|
||||
self.buffs.add(ReflectBuff, to_cache={'reflect': 0.5}) # A single stack of ReflectBuff, with an extra cache value
|
||||
```
|
||||
|
||||
|
|
@ -61,13 +65,63 @@ Two important attributes on the buff are checked when the buff is applied: `refr
|
|||
- `refresh` (default: True) determines if a buff's timer is refreshed when it is reapplied.
|
||||
- `unique` (default: True) determines if the buff uses the buff's normal key (True) or one created with the key and the applier's dbref (False)
|
||||
|
||||
#### Modify
|
||||
If both are `False`, the buff creates a random (rather than buff + source) key each time it is applied, and will never refresh.
|
||||
|
||||
### Get Buffs
|
||||
|
||||
The handler has several getter methods which return instanced buffs. You won't need to use these for basic functionality, but if you want to manipulate
|
||||
buffs after application, they are very useful. The handler's `check`/`trigger` methods utilize some of these getters, while others are just for developer convenience.
|
||||
|
||||
`get(key)` is the most basic getter. It returns a single buff instance, or `None` if the buff doesn't exist on the handler. It is also the only getter
|
||||
that returns a single buff instance, rather than a dictionary.
|
||||
|
||||
Grouped getters, listed below, return a dictionary of values in the format `{buffkey: instance}`. If you want to iterate over all these buffs,
|
||||
you should do so via the `dict.values()` method.
|
||||
|
||||
- `get_all()` returns all buffs on this handler. You can also use the `handler.all` property.
|
||||
- `get_by_type(BuffClass)` returns buffs of the specified type.
|
||||
- `get_by_stat(stat)` returns buffs with a `Mod` object of the specified `stat` string in their `mods` list. Used by `handler.check(stat)`.
|
||||
- `get_by_trigger(trigger)` returns buffs with the specified trigger in their `triggers` list. Used by `handler.trigger(key)`.
|
||||
- `get_by_source(Object)` returns buffs applied by the specified `source` object.
|
||||
- `get_by_cachevalue(key, value)` returns buffs with the matching `key: value` pair in their cache. `value` is optional.
|
||||
|
||||
All grouped getters besides `get_all()` can "slice" an existing dictionary through the optional `to_filter` argument.
|
||||
|
||||
```python
|
||||
dict1 = handler.get_by_type(Burned) # This finds all "Burned" buffs on the handler
|
||||
dict2 = handler.get_by_source(self, to_filter=dict1) # This filters dict1 to find buffs with the matching source
|
||||
```
|
||||
|
||||
> **Note**: Most of these getters also have an associated handler property. For example, `handler.effects` returns all buffs which can be triggered, which
|
||||
> is then iterated over by the `get_by_trigger` method.
|
||||
|
||||
### Remove Buffs
|
||||
|
||||
There are also a number of remover methods. Generally speaking, these follow the same format as the getters.
|
||||
|
||||
- `remove(key)` removes the buff with the specified key.
|
||||
- `clear()` removes all buffs.
|
||||
- `remove_by_type(BuffClass)` removes buffs of the specified type.
|
||||
- `remove_by_stat(stat)` removes buffs with a `Mod` object of the specified `stat` string in their `mods` list.
|
||||
- `remove_by_trigger(trigger)` removes buffs with the specified trigger in their `triggers` list.
|
||||
- `remove_by_source(Object)` removes buffs applied by the specified source
|
||||
- `remove_by_cachevalue(key, value)` removes buffs with the matching `key: value` pair in their cache. `value` is optional.
|
||||
|
||||
You can also remove a buff by calling the instance's `remove` helper method. You can do this on the dictionaries returned by the
|
||||
getters listed above.
|
||||
|
||||
```python
|
||||
to_remove = handler.get_by_trigger(trigger) # Finds all buffs with the specified trigger
|
||||
for buff in to_remove.values(): buff.remove() # Removes all buffs in the to_remove dictionary via helper methods
|
||||
```
|
||||
|
||||
### Check Modifiers
|
||||
|
||||
Call the handler `check(value, stat)` method wherever you want to see the modified value.
|
||||
This will return the value, modified by and relevant buffs on the handler's owner (identified by
|
||||
This will return the `value`, modified by any relevant buffs on the handler's owner (identified by
|
||||
the `stat` string).
|
||||
|
||||
For example, let's say you want to modify how much damage you take. That might look something like this
|
||||
For example, let's say you want to modify how much damage you take. That might look something like this:
|
||||
|
||||
```python
|
||||
# The method we call to damage ourselves
|
||||
|
|
@ -76,37 +130,64 @@ def take_damage(self, source, damage):
|
|||
self.db.health -= _damage
|
||||
```
|
||||
|
||||
#### Trigger
|
||||
This method call the `at_pre_check` and `at_post_check` methods at the relevant points in the process. You can use to this make
|
||||
buffs that are reactive to being checked; for example, removing themselves, altering their values, or interacting with the game state.
|
||||
|
||||
Call the handler `trigger(triggerstring)` method wherever you want an event call. This
|
||||
> **Note**: You can also trigger relevant buffs at the same time as you check them by ensuring the optional argument `trigger` is True in the `check` method.
|
||||
|
||||
### Trigger Buffs
|
||||
|
||||
Call the handler `trigger(string)` method wherever you want an event call. This
|
||||
will call the `at_trigger` hook method on all buffs with the relevant trigger.
|
||||
|
||||
For example, let's say you want to trigger a buff to "detonate" when you hit your target with an attack.
|
||||
You'd write a buff with at least the following stats:
|
||||
You'd write a buff that might look like this:
|
||||
|
||||
```python
|
||||
triggers = ['take_damage']
|
||||
def at_trigger(self, trigger, *args, **kwargs)
|
||||
self.owner.take_damage(100)
|
||||
def Detonate(BaseBuff):
|
||||
...
|
||||
triggers = ['take_damage']
|
||||
def at_trigger(self, trigger, *args, **kwargs)
|
||||
self.owner.take_damage(100)
|
||||
self.remove()
|
||||
```
|
||||
|
||||
And then call `handler.trigger('take_damage')` in the method you use to take damage.
|
||||
|
||||
#### Tick
|
||||
> **Note** You could also do this through mods and `at_post_check` if you like, depending on how to want to add the damage.
|
||||
|
||||
### Ticking
|
||||
|
||||
Ticking buffs are slightly special. They are similar to trigger buffs in that they run code, but instead of
|
||||
doing so on an event trigger, they do so are a periodic tick. A common use case for a buff like this is a poison,
|
||||
doing so on an event trigger, they do so on a periodic tick. A common use case for a buff like this is a poison,
|
||||
or a heal over time.
|
||||
|
||||
```python
|
||||
def Poison(BaseBuff):
|
||||
...
|
||||
tickrate = 5
|
||||
def at_tick(self, initial=True, *args, **kwargs):
|
||||
_dmg = self.dmg * self.stacks
|
||||
if not initial:
|
||||
self.owner.location.msg_contents(
|
||||
"Poison courses through {actor}'s body, dealing {damage} damage.".format(
|
||||
actor=self.owner.named, damage=_dmg
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
All you need to do to make a buff tick is ensure the `tickrate` is 1 or higher, and it has code in its `at_tick`
|
||||
method. Once you add it to the handler, it starts ticking!
|
||||
|
||||
> **Note**: Ticking buffs always tick on initial application, when `initial` is `True`. If you don't want your hook to fire at that time,
|
||||
> make sure to check the value of `initial` in your `at_tick` method.
|
||||
|
||||
### Context
|
||||
|
||||
You may have noticed that almost every important handler method optionally accepts a `context` dictionary.
|
||||
Every important handler method optionally accepts a `context` dictionary.
|
||||
|
||||
Context is an important concept for this handler. Every method which modifies, triggers, or checks a buff passes this
|
||||
dictionary (default: empty) to the buff hook methods as keyword arguments (**kwargs). It is used for nothing else. This allows you to make those
|
||||
dictionary (default: empty) to the buff hook methods as keyword arguments (`**kwargs`). It is used for nothing else. This allows you to make those
|
||||
methods "event-aware" by storing relevant data in the dictionary you feed to the method.
|
||||
|
||||
For example, let's say you want a "thorns" buff which damages enemies that attack you. Let's take our `take_damage` method
|
||||
|
|
@ -131,26 +212,40 @@ def ThornsBuff(BaseBuff):
|
|||
```
|
||||
Apply the buff, take damage, and watch the thorns buff do its work!
|
||||
|
||||
## Buffs
|
||||
## Creating New Buffs
|
||||
|
||||
But wait! You still have to actually create the buffs you're going to be applying.
|
||||
|
||||
Creating a buff is very easy: extend `BaseBuff` into a new class, and fill in all the relevant buff details.
|
||||
However, there are a lot of individual moving parts to a buff! Here's a step-through of the important stuff.
|
||||
Creating a new buff is very easy: extend `BaseBuff` into a new class, and fill in all the relevant buff details.
|
||||
However, there are a lot of individual moving parts to a buff. Here's a step-through of the important stuff.
|
||||
|
||||
### Basics
|
||||
|
||||
Regardless of any mods or hook methods, all buffs have the following qualities:
|
||||
Regardless of any other functionality, all buffs have the following class attributes:
|
||||
|
||||
- They have customizable `key`, `name`, and `flavor` strings.
|
||||
- They can stack, if `maxstacks` is not equal to 1. If it's 0, the buff stacks forever.
|
||||
- They have a `duration`, and automatically clean up at the end of it (-1 for infinite duration, 0 to cleanup immediately).
|
||||
- They have a `duration`, and automatically clean up at the end of it. Use -1 for infinite duration, and 0 to cleanup immediately. (default: -1)
|
||||
- They can stack, if `maxstacks` is not equal to 1. If it's 0, the buff stacks forever. (default: 1)
|
||||
- They can be `unique`, which determines if they have a unique namespace or not. (default: True)
|
||||
- They can `refresh`, which resets the duration when stacked or reapplied. (default: True)
|
||||
- They can be `playtime` buffs, where duration only counts down during active play. (default: False)
|
||||
|
||||
They also always store some useful mutable information about themselves in the cache:
|
||||
|
||||
- The `ref` class, which is the buff class path we use to construct the buff.
|
||||
- The `start` timestamp of when the buff was applied.
|
||||
- Their `source`, if specified; this allows you to track who or what applied the buff.
|
||||
- The `prevtick` timestamp of the previous time this buff ticked.
|
||||
- The current `duration`. This can vary from the class duration, as you might apply buffs with variable durations, or alter them.
|
||||
- The number of `stacks` they have.
|
||||
- Whether they are `paused` or not. Paused buffs do not clean up, modify values, tick, or fire any hook methods.
|
||||
|
||||
You can always access the raw cache dictionary through the `cache` attribute on an instanced buff. This is grabbed when you get the buff through
|
||||
a handler method, so it may not always reflect recent changes you've made, depending on how you structure your buff calls. All of the above
|
||||
mutable information can be found in this cache, as well as any arbitrary information you pass through the handler `add` method (via `to_cache`).
|
||||
|
||||
### Modifiers
|
||||
|
||||
Buffs which have one or more Mod objects in them can modify stats. You can use the handler method to check all
|
||||
mods of a specific stat string and apply their modifications to the value; however, you are encouraged to use
|
||||
`check` in a getter/setter, for easy access.
|
||||
Mods are stored in the `mods` list attribute. Buffs which have one or more Mod objects in them can modify stats. You can use the handler method to check all
|
||||
mods of a specific stat string and apply their modifications to the value; however, you are encouraged to use `check` in a getter/setter, for easy access.
|
||||
|
||||
Mod objects consist of only four values, assigned by the constructor in this order:
|
||||
|
||||
|
|
@ -159,24 +254,40 @@ Mod objects consist of only four values, assigned by the constructor in this ord
|
|||
- `value`: How much value the modifier gives regardless of stacks
|
||||
- `perstack`: How much value the modifier grants per stack, INCLUDING the first. (default: 0)
|
||||
|
||||
To add a mod to a buff, you do so in the buff definition, like this:
|
||||
The most basic way to add a Mod to a buff is to do so in the buff class definition, like this:
|
||||
|
||||
```python
|
||||
def DamageBuff(BaseBuff):
|
||||
mods = [Mod('damage', 'add', 10)]
|
||||
```
|
||||
|
||||
No mods applied to the value are permanent in any way. All calculations are done at
|
||||
runtime, and the mod values are never stored anywhere except on the buff in question. In
|
||||
other words: you don't need to track the origin of particular stat mods, and you
|
||||
will never permanently change a stat modified by a trait buff. To remove the modification, simply
|
||||
remove the buff off the object.
|
||||
No mods applied to the value are permanent in any way. All calculations are done at runtime, and the mod values are never stored
|
||||
anywhere except on the buff in question. In other words: you don't need to track the origin of particular stat mods, and you will
|
||||
never permanently change a stat modified by a trait buff. To remove the modification, simply remove the buff off the object.
|
||||
|
||||
> **Note**: You can add your own modifier types by overloading the `_calculate_mods` method, which contains the basic modifier application logic.
|
||||
|
||||
#### Generating Mods (Advanced)
|
||||
|
||||
An advanced way to do mods is to generate them when the buff is initialized. This lets you create mods on the fly that are reactive to the game state.
|
||||
|
||||
```python
|
||||
def GeneratedStatBuff(BaseBuff):
|
||||
...
|
||||
def __init__(self, handler, buffkey, cache={}) -> None:
|
||||
super().__init__(handler, buffkey, cache)
|
||||
# Finds our "modgen" cache value, and generates a mod from it
|
||||
modgen = list(self.cache.get("modgen"))
|
||||
if modgen:
|
||||
self.mods = [Mod(*modgen)]
|
||||
```
|
||||
|
||||
### Triggers
|
||||
|
||||
Buffs which have one or more strings in the `triggers` attribute can be triggered by events.
|
||||
|
||||
When the handler `trigger` method is called, it searches all buffs on the handler for any with a matching
|
||||
trigger, then calls their `at_trigger` methods. You can tell which trigger is the one it fired with by the `trigger`
|
||||
trigger, then calls their `at_trigger` methods. Buffs can have multiple triggers, and you can tell which trigger was fired by the `trigger`
|
||||
argument in the method.
|
||||
|
||||
```python
|
||||
|
|
@ -192,6 +303,7 @@ def AmplifyBuff(BaseBuff):
|
|||
|
||||
A buff with ticking isn't much different than one which triggers. You're still executing arbitrary code off
|
||||
the buff class. The main thing is you need to have a `tickrate` higher than 1.
|
||||
|
||||
```python
|
||||
def Poison(BaseBuff):
|
||||
...
|
||||
|
|
@ -201,39 +313,46 @@ def Poison(BaseBuff):
|
|||
def at_tick(self, initial, **kwargs):
|
||||
self.owner.take_damage(10)
|
||||
```
|
||||
It's important to note the buff always ticks once when applied. For this first tick only, `initial` will be True
|
||||
in the `at_tick` hook method.
|
||||
> **Note**: The buff always ticks once when applied. For this **first tick only**, `initial` will be True in the `at_tick` hook method.
|
||||
|
||||
Ticks utilize a persistent delay, so they should be pickleable. As long as you are not adding new properties to your buff class, this shouldn't be a concern.
|
||||
If you **are** adding new properties, try to ensure they do not end up with a circular code path to their object or handler, as this will cause pickling errors.
|
||||
|
||||
### Extras
|
||||
|
||||
Buffs have a grab-bag of extra functionality to make your life easier!
|
||||
Buffs have a grab-bag of extra functionality to let you add complexity to your designs.
|
||||
|
||||
You can restrict whether or not the buff will check or trigger through defining the `conditional` hook. As long
|
||||
#### Conditionals
|
||||
|
||||
You can restrict whether or not the buff will `check`, `trigger`, or `tick` through defining the `conditional` hook. As long
|
||||
as it returns a "truthy" value, the buff will apply itself. This is useful for making buffs dependent on game state - for
|
||||
example, if you want a buff that makes the player take more damage when they are on fire.
|
||||
example, if you want a buff that makes the player take more damage when they are on fire:
|
||||
|
||||
```python
|
||||
def conditional(self, *args, **kwargs):
|
||||
if self.owner.buffs.get_by_type(FireBuff): return True
|
||||
return False
|
||||
def FireSick(BaseBuff):
|
||||
...
|
||||
def conditional(self, *args, **kwargs):
|
||||
if self.owner.buffs.get_by_type(FireBuff): return True
|
||||
return False
|
||||
```
|
||||
|
||||
There are a number of helper methods. If you have a buff instance - for example, because you got the buff with
|
||||
`handler.get(key)` - you can `pause`, `unpause`, `remove`, `dispel`, etc.
|
||||
Conditionals for `check`/`trigger` are checked when the buffs are gathered by the handler methods for the respective operations. `Tick`
|
||||
conditionals are checked each tick.
|
||||
|
||||
Finally, if your handler has `autopause` enabled, any buffs with truthy `playtime` value will automatically pause
|
||||
#### Helper Methods
|
||||
|
||||
Buff instances have a number of helper methods you can access either on the buff itself or wherever the buff is instances (typically through
|
||||
handler getters)
|
||||
|
||||
- `remove`/`dispel`: Allows you to remove or dispel the buff. Calls `at_remove`/`at_dispel`, depending on optional arguments.
|
||||
- `pause`/`unpause`: Pauses and unpauses the buff. Calls `at_pause`/`at_unpause`.
|
||||
- `reset`: Resets the buff's start to the current time; same as "refreshing" it.
|
||||
|
||||
#### Playtime Duration
|
||||
|
||||
If your handler has `autopause` enabled, any buffs with truthy `playtime` value will automatically pause
|
||||
and unpause when the object the handler is attached to is puppetted or unpuppetted. This even works with ticking buffs,
|
||||
although if you have less than 1 second of tick duration remaining, it will round up to 1s.
|
||||
|
||||
If you want more control over this process, you can comment out the signal subscriptions on the handler and move the autopause logic
|
||||
to your object's at_pre/post_puppet/unpuppet hooks.
|
||||
|
||||
### How Does It Work?
|
||||
|
||||
Buffs are stored in two parts alongside each other in the cache: a reference to the buff class, and as mutable data.
|
||||
You can technically store any information you like in the cache; by default, it's all the basic timing and event
|
||||
information necessary for the system to run. When the buff is instanced, this cache is fed to the constructor
|
||||
|
||||
When you use the handler to get a buff, you get an instanced version of that buff created from these two parts, or
|
||||
a dictionary of these buffs in the format of {buffkey: instance}. Buffs are only instanced as long as is necessary to
|
||||
run methods on them.
|
||||
> **Note**: If you want more control over this process, you can comment out the signal subscriptions on the handler and move the autopause logic
|
||||
> to your object's at_pre/post_puppet/unpuppet hooks.
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@ A buff is a timed object, attached to a game entity, that modifies values, trigg
|
|||
code, or both. It is a common design pattern in RPGs, particularly action games.
|
||||
|
||||
This contrib gives you a buff handler to apply to your objects, a buff class to extend them,
|
||||
and a sample property class to show how to automatically check modifiers.
|
||||
a sample property class to show how to automatically check modifiers, some sample buffs to learn from,
|
||||
and a command which applies buffs.
|
||||
|
||||
## Quick Start
|
||||
## Installation
|
||||
Assign the handler to a property on the object, like so.
|
||||
|
||||
```python
|
||||
|
|
@ -15,39 +16,17 @@ Assign the handler to a property on the object, like so.
|
|||
def buffs(self) -> BuffHandler:
|
||||
return BuffHandler(self)```
|
||||
|
||||
You may then call the handler to add or manipulate buffs.
|
||||
## Using the Handler
|
||||
|
||||
### Customization
|
||||
To make use of the handler, you will need:
|
||||
|
||||
If you want to customize the handler, you can feed the constructor two arguments:
|
||||
- `dbkey`: The string you wish to use as a key for the buff database. Defaults to "buffs". This allows you to keep separate buff pools - for example, "buffs" and "perks".
|
||||
- `autopause`: If you want this handler to automatically pause certain buffs when its owning object is unpuppeted.
|
||||
- Some buffs to add. You can create these by extending the `BaseBuff` class from this module. You can see some examples in `samplebuffs.py`.
|
||||
- A way to add buffs to the handler. You can see a basic example of this in the `CmdBuff` command in this module.
|
||||
|
||||
> IMPORTANT: If you enable autopausing, you MUST initialize the property in your object's
|
||||
> `at_init` hook to cache. Otherwise, a hot reload can cause playtime buffs to not update properly
|
||||
> on puppet/unpuppet. You have been warned!
|
||||
|
||||
Let's say you want another handler for an object, `perks`, which has a separate database and
|
||||
respects playtime buffs. You'd assign this new property as so:
|
||||
|
||||
```python
|
||||
@lazy_property
|
||||
def perks(self) -> BuffHandler:
|
||||
return BuffHandler(self, dbkey='perks', autopause=True)
|
||||
```
|
||||
|
||||
And add `self.perks` to the object's `at_init`.
|
||||
|
||||
### Using the Handler
|
||||
|
||||
To actually make use of the handler, you still have to do some leg work.
|
||||
|
||||
#### Apply a Buff
|
||||
### Applying a Buff
|
||||
|
||||
Call the handler `add(BuffClass)` method. This requires a class reference, and also contains a number of
|
||||
optional arguments to customize the buff's duration, stacks, and so on. You can also store any arbitrary value
|
||||
in the buff's cache by passing a dictionary through the `to_cache` argument. This will not overwrite the normal
|
||||
values on the cache.
|
||||
optional arguments to customize the buff's duration, stacks, and so on.
|
||||
|
||||
```python
|
||||
self.buffs.add(StrengthBuff) # A single stack of StrengthBuff with normal duration
|
||||
|
|
@ -55,17 +34,11 @@ self.buffs.add(DexBuff, stacks=3, duration=60) # Three stacks of DexBuff, with
|
|||
self.buffs.add(ReflectBuff, to_cache={'reflect': 0.5}) # A single stack of ReflectBuff, with an extra cache value
|
||||
```
|
||||
|
||||
Two important attributes on the buff are checked when the buff is applied: `refresh` and `unique`.
|
||||
- `refresh` (default: True) determines if a buff's timer is refreshed when it is reapplied.
|
||||
- `unique` (default: True) determines if the buff uses the buff's normal key (True) or one created with the key and the applier's dbref (False)
|
||||
|
||||
#### Modify
|
||||
### Modify
|
||||
|
||||
Call the handler `check(value, stat)` method wherever you want to see the modified value.
|
||||
This will return the value, modified by and relevant buffs on the handler's owner (identified by
|
||||
the `stat` string).
|
||||
|
||||
For example, let's say you want to modify how much damage you take. That might look something like this
|
||||
the `stat` string). For example:
|
||||
|
||||
```python
|
||||
# The method we call to damage ourselves
|
||||
|
|
@ -74,167 +47,61 @@ def take_damage(self, source, damage):
|
|||
self.db.health -= _damage
|
||||
```
|
||||
|
||||
#### Trigger
|
||||
### Trigger
|
||||
|
||||
Call the handler `trigger(triggerstring)` method wherever you want an event call. This
|
||||
will call the `at_trigger` hook method on all buffs with the relevant trigger.
|
||||
|
||||
For example, let's say you want to trigger a buff to "detonate" when you hit your target with an attack.
|
||||
You'd write a buff with at least the following stats:
|
||||
|
||||
```python
|
||||
triggers = ['take_damage']
|
||||
def at_trigger(self, trigger, *args, **kwargs)
|
||||
self.owner.take_damage(100)
|
||||
def Detonate(BaseBuff):
|
||||
...
|
||||
triggers = ['take_damage']
|
||||
def at_trigger(self, trigger, *args, **kwargs)
|
||||
self.owner.take_damage(100)
|
||||
self.remove()
|
||||
|
||||
def Character(Character):
|
||||
...
|
||||
def take_damage(self, source, damage):
|
||||
self.buffs.trigger('take_damage')
|
||||
self.db.health -= _damage
|
||||
```
|
||||
|
||||
And then call `handler.trigger('take_damage')` in the method you use to take damage.
|
||||
### Tick
|
||||
|
||||
#### Tick
|
||||
Ticking a buff happens automatically once applied, as long as the buff's `tickrate` is more than 0.
|
||||
|
||||
Ticking buffs are slightly special. They are similar to trigger buffs in that they run code, but instead of
|
||||
doing so on an event trigger, they do so are a periodic tick. A common use case for a buff like this is a poison,
|
||||
or a heal over time.
|
||||
|
||||
All you need to do to make a buff tick is ensure the `tickrate` is 1 or higher, and it has code in its `at_tick`
|
||||
method. Once you add it to the handler, it starts ticking!
|
||||
```python
|
||||
def Poison(BaseBuff):
|
||||
...
|
||||
tickrate = 5
|
||||
def at_tick(self, initial=True, *args, **kwargs):
|
||||
_dmg = self.dmg * self.stacks
|
||||
if not initial:
|
||||
self.owner.location.msg_contents(
|
||||
"Poison courses through {actor}'s body, dealing {damage} damage.".format(
|
||||
actor=self.owner.named, damage=_dmg
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
## Buffs
|
||||
|
||||
But wait! You still have to actually create the buffs you're going to be applying.
|
||||
A buff is a class which contains a bunch of immutable data about itself - such as tickrate, triggers, refresh rules, and
|
||||
so on - and which merges mutable data in from the cache when called.
|
||||
|
||||
Creating a buff is very easy: extend BaseBuff into a new class, and fill in all the relevant buff details.
|
||||
However, there are a lot of individual moving parts to a buff! Here's a step-through of the important stuff.
|
||||
Buffs are always instanced when they are called for a method. To access a buff's properties and methods, you should do so through
|
||||
this instance, rather than directly manipulating the buff cache on the object. You can modify a buff's cache through various handler
|
||||
methods instead.
|
||||
|
||||
### Basics
|
||||
You can see all the features of the `BaseBuff` class below, or browse `samplebuffs.py` to see how to create some common buffs. Buffs have
|
||||
many attributes and hook methods you can overload to create complex, interrelated buffs.
|
||||
|
||||
Regardless of any mods or hook methods, all buffs have the following qualities:
|
||||
|
||||
- They have customizable `key`, `name`, and `flavor` strings.
|
||||
- They can stack, if `maxstacks` is not equal to 1. If it's 0, the buff stacks forever.
|
||||
- They have a `duration`, and automatically clean up at the end of it (-1 for infinite duration, 0 to cleanup immediately).
|
||||
|
||||
### Modifiers
|
||||
|
||||
Buffs which have one or more Mod objects in them can modify stats. You can use the handler method to check all
|
||||
mods of a specific stat string and apply their modifications to the value; however, you are encouraged to use
|
||||
`check` in a getter/setter, for easy access.
|
||||
|
||||
Mod objects consist of only four values, assigned by the constructor in this order:
|
||||
|
||||
- `stat`: The stat you want to modify. When `check` is called, this string is used to find all the mods that are to be collected.
|
||||
- `mod`: The modifier. Defaults are 'add' and 'mult'. Modifiers are calculated additively, and in standard arithmetic order (see `_calculate_mods` for more)
|
||||
- `value`: How much value the modifier gives regardless of stacks
|
||||
- `perstack`: How much value the modifier grants per stack, INCLUDING the first. (default: 0)
|
||||
|
||||
To add a mod to a buff, you do so in the buff definition, like this:
|
||||
```python
|
||||
def DamageBuff(BaseBuff):
|
||||
mods = [Mod('damage', 'add', 10)]
|
||||
```
|
||||
|
||||
No mods applied to the value are permanent in any way. All calculations are done at
|
||||
runtime, and the mod values are never stored anywhere except on the buff in question. In
|
||||
other words: you don't need to track the origin of particular stat mods, and you
|
||||
will never permanently change a stat modified by a trait buff. To remove the modification, simply
|
||||
remove the buff off the object.
|
||||
|
||||
### Triggers
|
||||
|
||||
Buffs which have one or more strings in the `triggers` attribute can be triggered by events.
|
||||
|
||||
When the handler `trigger` method is called, it searches all buffs on the handler for any with a matching
|
||||
trigger, then calls their `at_trigger` methods. You can tell which trigger is the one it fired with by the `trigger`
|
||||
argument in the method.
|
||||
|
||||
```python
|
||||
def AmplifyBuff(BaseBuff):
|
||||
triggers = ['damage', 'heal']
|
||||
|
||||
def at_trigger(self, trigger, **kwargs):
|
||||
if trigger == 'damage': print('Damage trigger called!')
|
||||
if trigger == 'heal': print('Heal trigger called!')
|
||||
```
|
||||
|
||||
### Ticking
|
||||
|
||||
A buff with ticking isn't much different than one which triggers. You're still executing arbitrary code off
|
||||
the buff class. The main thing is you need to have a `tickrate` higher than 1.
|
||||
```python
|
||||
# this buff will tick 6 times between application and cleanup.
|
||||
duration = 30
|
||||
tickrate = 5
|
||||
def at_tick(self, initial, **kwargs):
|
||||
self.owner.take_damage(10)
|
||||
```
|
||||
It's important to note the buff always ticks once when applied. For this first tick only, `initial` will be True
|
||||
in the `at_tick` hook method.
|
||||
|
||||
### Extras
|
||||
|
||||
Buffs have a grab-bag of extra functionality to make your life easier!
|
||||
|
||||
You can restrict whether or not the buff will check or trigger through defining the `conditional` hook. As long
|
||||
as it returns a "truthy" value, the buff will apply itself. This is useful for making buffs dependent on game state - for
|
||||
example, if you want a buff that makes the player take more damage when they are on fire.
|
||||
|
||||
```python
|
||||
def conditional(self, *args, **kwargs):
|
||||
if self.owner.buffs.get_by_type(FireBuff): return True
|
||||
return False
|
||||
```
|
||||
|
||||
There are a number of helper methods. If you have a buff instance - for example, because you got the buff with
|
||||
`handler.get(key)` - you can `pause`, `unpause`, `remove`, `dispel`, etc.
|
||||
|
||||
Finally, if your handler has `autopause` enabled, any buffs with truthy `playtime` value will automatically pause
|
||||
and unpause when the object the handler is attached to is puppetted or unpuppetted. This even works with ticking buffs,
|
||||
although if you have less than 1 second of tick duration remaining, it will round up to 1s.
|
||||
|
||||
If you want more control over this process, you can comment out the signal subscriptions on the handler and move the autopause logic
|
||||
to your object's at_pre/post_puppet/unpuppet hooks.
|
||||
|
||||
### How Does It Work?
|
||||
|
||||
Buffs are stored in two parts alongside each other in the cache: a reference to the buff class, and as mutable data.
|
||||
You can technically store any information you like in the cache; by default, it's all the basic timing and event
|
||||
information necessary for the system to run. When the buff is instanced, this cache is fed to the constructor
|
||||
|
||||
When you use the handler to get a buff, you get an instanced version of that buff created from these two parts, or
|
||||
a dictionary of these buffs in the format of {buffkey: instance}. Buffs are only instanced as long as is necessary to
|
||||
run methods on them.
|
||||
|
||||
## Context
|
||||
|
||||
You may have noticed that almost every important handler method optionally accepts a `context` dictionary.
|
||||
|
||||
Context is an important concept for this handler. Every method which modifies, triggers, or checks a buff passes this
|
||||
dictionary (default: empty) to the buff hook methods as keyword arguments (**kwargs). It is used for nothing else. This allows you to make those
|
||||
methods "event-aware" by storing relevant data in the dictionary you feed to the method.
|
||||
|
||||
For example, let's say you want a "thorns" buff which damages enemies that attack you. Let's take our `take_damage` method
|
||||
and add a context to the mix.
|
||||
|
||||
```
|
||||
def take_damage(attacker, damage):
|
||||
context = {'attacker': attacker, 'damage': damage}
|
||||
_damage = self.buffs.check(damage, 'taken_damage', context=context)
|
||||
self.buffs.trigger('taken_damage', context=context)
|
||||
self.db.health -= _damage
|
||||
```
|
||||
Now we use the values that context passes to the buff kwargs to customize our logic.
|
||||
```
|
||||
triggers = ['taken_damage']
|
||||
# This is the hook method on our thorns buff
|
||||
def at_trigger(self, trigger, attacker=None, damage=0, **kwargs):
|
||||
if not attacker: return
|
||||
attacker.db.health -= damage * 0.2
|
||||
```
|
||||
Apply the buff, take damage, and watch the thorns buff do its work!
|
||||
"""
|
||||
|
||||
from random import random
|
||||
import time
|
||||
from evennia import Command
|
||||
from evennia.server import signals
|
||||
from evennia.utils import utils, search
|
||||
from evennia.typeclasses.attributes import AttributeProperty
|
||||
|
|
@ -553,8 +420,10 @@ class BuffHandler(object):
|
|||
context = {}
|
||||
b = {}
|
||||
_context = dict(context)
|
||||
if buff.cache:
|
||||
b = dict(buff.cache)
|
||||
if to_cache:
|
||||
b = dict(to_cache)
|
||||
b.update(dict(to_cache))
|
||||
if stacks < 1:
|
||||
stacks = min(1, buff.stacks)
|
||||
|
||||
|
|
@ -609,6 +478,7 @@ class BuffHandler(object):
|
|||
if b["duration"] > -1:
|
||||
utils.delay(b["duration"], self.cleanup, persistent=True)
|
||||
|
||||
# region removers
|
||||
def remove(self, key, stacks=0, loud=True, dispel=False, expire=False, context=None):
|
||||
"""Remove a buff or effect with matching key from this object. Normally calls at_remove,
|
||||
calls at_expire if the buff expired naturally, and optionally calls at_dispel. Can also
|
||||
|
|
@ -642,6 +512,8 @@ class BuffHandler(object):
|
|||
del self.buffcache[key]
|
||||
elif stacks:
|
||||
self.buffcache[key]["stacks"] -= stacks
|
||||
if self.buffcache[key]["stacks"] <= 0:
|
||||
del self.buffcache[key]
|
||||
|
||||
def remove_by_type(
|
||||
self,
|
||||
|
|
@ -665,6 +537,50 @@ class BuffHandler(object):
|
|||
return None
|
||||
self._remove_via_dict(_remove, loud, dispel, expire, context)
|
||||
|
||||
def remove_by_stat(
|
||||
self,
|
||||
stat,
|
||||
loud=True,
|
||||
dispel=False,
|
||||
expire=False,
|
||||
context=None,
|
||||
):
|
||||
"""Removes all buffs modifying the specified stat from this object.
|
||||
|
||||
Args:
|
||||
stat: The stat string to search for
|
||||
loud: (optional) Calls at_remove when True. (default: True)
|
||||
dispel: (optional) Calls at_dispel when True. (default: False)
|
||||
expire: (optional) Calls at_expire when True. (default: False)
|
||||
context: (optional) A dictionary you wish to pass to the at_remove/at_dispel/at_expire method as kwargs
|
||||
"""
|
||||
_remove = self.get_by_stat(stat)
|
||||
if not _remove:
|
||||
return None
|
||||
self._remove_via_dict(_remove, loud, dispel, expire, context)
|
||||
|
||||
def remove_by_trigger(
|
||||
self,
|
||||
trigger,
|
||||
loud=True,
|
||||
dispel=False,
|
||||
expire=False,
|
||||
context=None,
|
||||
):
|
||||
"""Removes all buffs with the specified trigger from this object.
|
||||
|
||||
Args:
|
||||
trigger: The stat string to search for
|
||||
loud: (optional) Calls at_remove when True. (default: True)
|
||||
dispel: (optional) Calls at_dispel when True. (default: False)
|
||||
expire: (optional) Calls at_expire when True. (default: False)
|
||||
context: (optional) A dictionary you wish to pass to the at_remove/at_dispel/at_expire method as kwargs
|
||||
"""
|
||||
_remove = self.get_by_trigger(trigger)
|
||||
if not _remove:
|
||||
return None
|
||||
self._remove_via_dict(_remove, loud, dispel, expire, context)
|
||||
|
||||
def remove_by_source(
|
||||
self,
|
||||
source,
|
||||
|
|
@ -673,7 +589,7 @@ class BuffHandler(object):
|
|||
expire=False,
|
||||
context=None,
|
||||
):
|
||||
"""Removes all buffs from the specified source from this object. Functionally similar to remove, but takes a source instead.
|
||||
"""Removes all buffs from the specified source from this object.
|
||||
|
||||
Args:
|
||||
source: The source to search for
|
||||
|
|
@ -716,6 +632,8 @@ class BuffHandler(object):
|
|||
cache = self.all
|
||||
self._remove_via_dict(cache, loud, dispel, expire, context)
|
||||
|
||||
# endregion
|
||||
# region getters
|
||||
def get(self, key: str):
|
||||
"""If the specified key is on this handler, return the instanced buff. Otherwise return None.
|
||||
You should delete this when you're done with it, so that garbage collection doesn't have to.
|
||||
|
|
@ -731,6 +649,8 @@ class BuffHandler(object):
|
|||
def get_all(self):
|
||||
"""Returns a dictionary of instanced buffs (all of them) on this handler in the format {buffkey: instance}"""
|
||||
_cache = dict(self.buffcache)
|
||||
if not _cache:
|
||||
return None
|
||||
return {k: buff["ref"](self, k, buff) for k, buff in _cache.items()}
|
||||
|
||||
def get_by_type(self, buff: BaseBuff, to_filter=None):
|
||||
|
|
@ -742,52 +662,36 @@ class BuffHandler(object):
|
|||
|
||||
Returns a dictionary of instanced buffs of the specified type in the format {buffkey: instance}."""
|
||||
_cache = self.get_all() if not to_filter else to_filter
|
||||
if not _cache:
|
||||
return None
|
||||
return {k: _buff for k, _buff in _cache.items() if isinstance(_buff, buff)}
|
||||
|
||||
def get_by_stat(self, stat: str, context=None, to_filter=None):
|
||||
def get_by_stat(self, stat: str, to_filter=None):
|
||||
"""Finds all buffs which contain a Mod object that modifies the specified stat.
|
||||
|
||||
Args:
|
||||
stat: The string identifier to find relevant mods
|
||||
context: (optional) A dictionary you wish to pass to the conditional method as kwargs
|
||||
to_filter: (optional) A dictionary you wish to slice. If not provided, uses the whole buffcache.
|
||||
|
||||
Returns a dictionary of instanced buffs which modify the specified stat in the format {buffkey: instance}."""
|
||||
_cache = self.traits if not to_filter else to_filter
|
||||
if not _cache:
|
||||
return None
|
||||
if not context:
|
||||
context = {}
|
||||
|
||||
buffs = {
|
||||
k: buff
|
||||
for k, buff in _cache.items()
|
||||
for m in buff.mods
|
||||
if m.stat == stat
|
||||
if not buff.paused
|
||||
if buff.conditional(**context)
|
||||
}
|
||||
buffs = {k: buff for k, buff in _cache.items() for m in buff.mods if m.stat == stat}
|
||||
return buffs
|
||||
|
||||
def get_by_trigger(self, trigger: str, context=None, to_filter=None):
|
||||
def get_by_trigger(self, trigger: str, to_filter=None):
|
||||
"""Finds all buffs with the matching string in their triggers.
|
||||
|
||||
Args:
|
||||
trigger: The string identifier to find relevant buffs
|
||||
context: (optional) A dictionary you wish to pass to the conditional method as kwargs
|
||||
to_filter: (optional) A dictionary you wish to slice. If not provided, uses the whole buffcache.
|
||||
|
||||
Returns a dictionary of instanced buffs which fire off the designated trigger, in the format {buffkey: instance}."""
|
||||
_cache = self.effects if not to_filter else to_filter
|
||||
if not context:
|
||||
context = {}
|
||||
buffs = {
|
||||
k: buff
|
||||
for k, buff in _cache.items()
|
||||
if trigger in buff.triggers
|
||||
if not buff.paused
|
||||
if buff.conditional(**context)
|
||||
}
|
||||
if not _cache:
|
||||
return None
|
||||
buffs = {k: buff for k, buff in _cache.items() if trigger in buff.triggers}
|
||||
return buffs
|
||||
|
||||
def get_by_source(self, source, to_filter=None):
|
||||
|
|
@ -799,6 +703,8 @@ class BuffHandler(object):
|
|||
|
||||
Returns a dictionary of instanced buffs which came from the provided source, in the format {buffkey: instance}."""
|
||||
_cache = self.all if not to_filter else to_filter
|
||||
if not _cache:
|
||||
return None
|
||||
buffs = {k: buff for k, buff in _cache.items() if buff.source == source}
|
||||
return buffs
|
||||
|
||||
|
|
@ -812,12 +718,16 @@ class BuffHandler(object):
|
|||
|
||||
Returns a dictionary of instanced buffs with cache values matching the specified value, in the format {buffkey: instance}."""
|
||||
_cache = self.all if not to_filter else to_filter
|
||||
if not _cache:
|
||||
return None
|
||||
if not value:
|
||||
buffs = {k: buff for k, buff in _cache.items() if buff.cache.get(key)}
|
||||
elif value:
|
||||
buffs = {k: buff for k, buff in _cache.items() if buff.cache.get(key) == value}
|
||||
return buffs
|
||||
|
||||
# endregion
|
||||
|
||||
def check(self, value: float, stat: str, loud=True, context=None, trigger=False):
|
||||
"""Finds all buffs and perks related to a stat and applies their effects.
|
||||
|
||||
|
|
@ -825,7 +735,7 @@ class BuffHandler(object):
|
|||
value: The value you intend to modify
|
||||
stat: The string that designates which stat buffs you want
|
||||
loud: (optional) Call the buff's at_post_check method after checking (default: True)
|
||||
context: (optional) A dictionary you wish to pass to the at_pre_check/at_post_check methods as kwargs
|
||||
context: (optional) A dictionary you wish to pass to the at_pre_check/at_post_check and conditional methods as kwargs
|
||||
trigger: (optional) Trigger buffs with the `stat` string as well. (default: False)
|
||||
|
||||
Returns the value modified by relevant buffs."""
|
||||
|
|
@ -835,12 +745,16 @@ class BuffHandler(object):
|
|||
# Find all buffs and traits related to the specified stat.
|
||||
if not context:
|
||||
context = {}
|
||||
applied = self.get_by_stat(stat, context)
|
||||
applied = self.get_by_stat(stat)
|
||||
if not applied:
|
||||
return value
|
||||
for buff in applied.values():
|
||||
buff.at_pre_check(**context)
|
||||
|
||||
applied = {
|
||||
k: buff for k, buff in applied.items() if buff.conditional(**context) if not buff.paused
|
||||
}
|
||||
|
||||
# The final result
|
||||
final = self._calculate_mods(value, stat, applied)
|
||||
|
||||
|
|
@ -865,17 +779,24 @@ class BuffHandler(object):
|
|||
context: (optional) A dictionary you wish to pass to the at_trigger method as kwargs
|
||||
"""
|
||||
self.cleanup()
|
||||
_effects = self.get_by_trigger(trigger, context)
|
||||
if _effects is None:
|
||||
_effects = self.get_by_trigger(trigger)
|
||||
if not _effects:
|
||||
return None
|
||||
if not context:
|
||||
context = {}
|
||||
|
||||
_to_trigger = {
|
||||
k: buff
|
||||
for k, buff in _effects.items()
|
||||
if buff.conditional(**context)
|
||||
if not buff.paused
|
||||
if trigger in buff.triggers
|
||||
}
|
||||
|
||||
# Trigger all buffs whose trigger matches the trigger string
|
||||
for buff in _effects.values():
|
||||
for buff in _to_trigger.values():
|
||||
buff: BaseBuff
|
||||
if trigger in buff.triggers and not buff.paused:
|
||||
buff.at_trigger(trigger, **context)
|
||||
buff.at_trigger(trigger, **context)
|
||||
|
||||
def pause(self, key: str, context=None):
|
||||
"""Pauses the buff. This excludes it from being checked for mods, triggered, or cleaned up. Used to make buffs 'playtime' instead of 'realtime'.
|
||||
|
|
@ -945,7 +866,7 @@ class BuffHandler(object):
|
|||
instance: BaseBuff = buff["ref"](self, key, buff)
|
||||
instance.at_unpause(**context)
|
||||
utils.delay(buff["duration"], cleanup_buffs, self, persistent=True)
|
||||
if buff["ref"].ticking:
|
||||
if instance.ticking:
|
||||
utils.delay(
|
||||
tickrate, tick_buff, handler=self, buffkey=key, initial=False, persistent=True
|
||||
)
|
||||
|
|
@ -1028,6 +949,8 @@ class BuffHandler(object):
|
|||
"""Removes buffs within the provided dictionary from this handler. Used for remove methods besides the basic remove."""
|
||||
if not context:
|
||||
context = {}
|
||||
if not buffs:
|
||||
return
|
||||
for k, instance in buffs.items():
|
||||
instance: BaseBuff
|
||||
if loud:
|
||||
|
|
@ -1051,6 +974,48 @@ class BuffableProperty(AttributeProperty):
|
|||
return _value
|
||||
|
||||
|
||||
class CmdBuff(Command):
|
||||
"""
|
||||
Buff a target.
|
||||
|
||||
Usage:
|
||||
buff <target> <buff>
|
||||
|
||||
Applies the specified buff to the target. All buffs are defined in the bufflist dictionary on this command.
|
||||
"""
|
||||
|
||||
key = "buff"
|
||||
aliases = ["buff"]
|
||||
help_category = "builder"
|
||||
|
||||
bufflist = {"foo": BaseBuff}
|
||||
|
||||
def parse(self):
|
||||
self.args = self.args.split()
|
||||
|
||||
def func(self):
|
||||
caller = self.caller
|
||||
target = None
|
||||
now = time.time()
|
||||
|
||||
if self.args:
|
||||
target = caller.search(self.args[0])
|
||||
caller.ndb.target = target
|
||||
elif caller.ndb.target:
|
||||
target = caller.ndb.target
|
||||
else:
|
||||
caller.msg("You need to pick a target to buff.")
|
||||
return
|
||||
|
||||
if self.args[1] not in self.bufflist.keys():
|
||||
caller.msg("You must pick a valid buff.")
|
||||
return
|
||||
|
||||
if target:
|
||||
target.buffs.add(self.bufflist[self.args[1]], source=caller)
|
||||
pass
|
||||
|
||||
|
||||
def cleanup_buffs(handler: BuffHandler):
|
||||
"""Cleans up all expired buffs from a handler."""
|
||||
_remove = handler.expired
|
||||
|
|
@ -1069,6 +1034,8 @@ def tick_buff(handler: BuffHandler, buffkey: str, context=None, initial=True):
|
|||
# Cache a reference and find the buff on the object
|
||||
if buffkey not in handler.buffcache.keys():
|
||||
return
|
||||
if not context:
|
||||
context = {}
|
||||
|
||||
# Instantiate the buff and tickrate
|
||||
buff: BaseBuff = handler.get(buffkey)
|
||||
|
|
|
|||
|
|
@ -131,7 +131,7 @@ class StatBuff(BaseBuff):
|
|||
def __init__(self, handler, buffkey, cache={}) -> None:
|
||||
super().__init__(handler, buffkey, cache)
|
||||
# Finds our "modgen" cache value, which we pass on application
|
||||
modgen = list(self.cache["modgen"])
|
||||
modgen = list(self.cache.get("modgen"))
|
||||
if modgen:
|
||||
self.mods = [Mod(*modgen)]
|
||||
msg = ""
|
||||
|
|
|
|||
|
|
@ -130,6 +130,10 @@ class TestBuffsAndHandler(EvenniaTest):
|
|||
# remove
|
||||
handler.remove("tmb")
|
||||
self.assertFalse(self.testobj.db.buffs.get("tmb"))
|
||||
# remove stacks
|
||||
handler.add(_TestModBuff, stacks=3)
|
||||
handler.remove("tmb", stacks=3)
|
||||
self.assertFalse(self.testobj.db.buffs.get("tmb"))
|
||||
# remove by type
|
||||
handler.add(_TestModBuff)
|
||||
handler.remove_by_type(_TestModBuff)
|
||||
|
|
@ -149,7 +153,7 @@ class TestBuffsAndHandler(EvenniaTest):
|
|||
# remove all
|
||||
handler.add(_TestModBuff)
|
||||
handler.clear()
|
||||
self.assertFalse(self.testobj.db.buffs.get("tmb"))
|
||||
self.assertFalse(self.testobj.buffs.all)
|
||||
|
||||
def test_getters(self):
|
||||
"""tests all built-in getters"""
|
||||
|
|
@ -172,8 +176,6 @@ class TestBuffsAndHandler(EvenniaTest):
|
|||
self.assertTrue("tmb" in handler.get_by_source(self.obj2))
|
||||
self.assertFalse("ttb" in handler.get_by_source(self.obj2))
|
||||
# cachevalue getter
|
||||
self.assertFalse(handler.get("tmb").cache.get("ttbcache"))
|
||||
self.assertFalse(handler.get("ttb").cache.get("testfalse"))
|
||||
self.assertFalse("tmb" in handler.get_by_cachevalue("ttbcache"))
|
||||
self.assertTrue("ttb" in handler.get_by_cachevalue("ttbcache"))
|
||||
self.assertTrue("ttb" in handler.get_by_cachevalue("ttbcache", True))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue