Update cooldown tutorial. Resolve #2648.
This commit is contained in:
parent
6e4bb8047b
commit
e6090ef1ed
1 changed files with 114 additions and 53 deletions
|
|
@ -1,16 +1,22 @@
|
||||||
# Command Cooldown
|
# Command Cooldown
|
||||||
|
|
||||||
|
|
||||||
Some types of games want to limit how often a command can be run. If a
|
Some types of games want to limit how often a command can be run. If a
|
||||||
character casts the spell *Firestorm*, you might not want them to spam that
|
character casts the spell *Firestorm*, you might not want them to spam that
|
||||||
command over and over. Or in an advanced combat system, a massive swing may
|
command over and over. Or in an advanced combat system, a massive swing may
|
||||||
offer a chance of lots of damage at the cost of not being able to re-do it for
|
offer a chance of lots of damage at the cost of not being able to re-do it for
|
||||||
a while. Such effects are called *cooldowns*.
|
a while. Such effects are called *cooldowns*.
|
||||||
|
|
||||||
This page exemplifies a very resource-efficient way to do cooldowns. A more
|
This page exemplifies a very resource-efficient way to do cooldowns. A more
|
||||||
'active' way is to use asynchronous delays as in the [command duration
|
'active' way is to use asynchronous delays as in the [command duration
|
||||||
tutorial](./Command-Duration.md#blocking-commands), the two might be useful to
|
tutorial](./Command-Duration.md#blocking-commands), the two might be useful to
|
||||||
combine if you want to echo some message to the user after the cooldown ends.
|
combine if you want to echo some message to the user after the cooldown ends.
|
||||||
|
|
||||||
|
## The Cooldown Contrib
|
||||||
|
|
||||||
|
The [Cooldown contrib](Contribs/Contrib-Cooldowns) is a ready-made solution for
|
||||||
|
command cooldowns you can use. It implements a _handler_ on the object to
|
||||||
|
conveniently manage and store the cooldowns in a similar manner exemplified in
|
||||||
|
this tutorial.
|
||||||
|
|
||||||
## Non-persistent cooldown
|
## Non-persistent cooldown
|
||||||
|
|
||||||
|
|
@ -19,80 +25,135 @@ Commands are class instances, and those are cached in memory, a command
|
||||||
instance will remember things you store on it. So just store the current time
|
instance will remember things you store on it. So just store the current time
|
||||||
of execution! Next time the command is run, it just needs to check if it has
|
of execution! Next time the command is run, it just needs to check if it has
|
||||||
that time stored, and compare it with the current time to see if a desired
|
that time stored, and compare it with the current time to see if a desired
|
||||||
delay has passed.
|
delay has passed.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import time
|
# in, say, mygame/commands/spells.py
|
||||||
|
|
||||||
|
import time
|
||||||
from evennia import default_cmds
|
from evennia import default_cmds
|
||||||
|
|
||||||
class CmdSpellFirestorm(default_cmds.MuxCommand):
|
class CmdSpellFirestorm(default_cmds.MuxCommand):
|
||||||
"""
|
"""
|
||||||
Spell - Firestorm
|
Spell - Firestorm
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
cast firestorm <target>
|
cast firestorm <target>
|
||||||
|
|
||||||
This will unleash a storm of flame. You can only release one
|
This will unleash a storm of flame. You can only release one
|
||||||
firestorm every five minutes (assuming you have the mana).
|
firestorm every five minutes (assuming you have the mana).
|
||||||
"""
|
"""
|
||||||
key = "cast firestorm"
|
key = "cast firestorm"
|
||||||
locks = "cmd:isFireMage()"
|
rate_of_fire = 60 * 2 # 2 minutes
|
||||||
|
|
||||||
def func(self):
|
def func(self):
|
||||||
"Implement the spell"
|
"Implement the spell"
|
||||||
|
|
||||||
# check cooldown (5 minute cooldown)
|
now = time.time()
|
||||||
now = time.time()
|
last_cast = caller.ndb.firestorm_last_cast # could be None
|
||||||
if hasattr(self, "lastcast") and \
|
if last_cast and (now - last_cast < self.rate_of_fire):
|
||||||
now - self.lastcast < 5 * 60:
|
|
||||||
message = "You cannot cast this spell again yet."
|
message = "You cannot cast this spell again yet."
|
||||||
self.caller.msg(message)
|
self.caller.msg(message)
|
||||||
return
|
return
|
||||||
|
|
||||||
#[the spell effect is implemented]
|
# [the spell effect is implemented]
|
||||||
|
|
||||||
# if the spell was successfully cast, store the casting time
|
# if the spell was successfully cast, store the casting time
|
||||||
self.lastcast = now
|
self.caller.ndb.firestorm_last_cast = now
|
||||||
```
|
```
|
||||||
|
|
||||||
We just check the `lastcast` flag, and update it if everything works out.
|
We specify `rate_of_fire` and then just check for a NAtrribute
|
||||||
|
`firestorm_last_cast` and update it if everything works out.
|
||||||
|
|
||||||
Simple and very effective since everything is just stored in memory. The
|
Simple and very effective since everything is just stored in memory. The
|
||||||
drawback of this simple scheme is that it's non-persistent. If you do
|
drawback of this simple scheme is that it's non-persistent. If you do
|
||||||
`@reload`, the cache is cleaned and all such ongoing cooldowns will be
|
`reload`, the cache is cleaned and all such ongoing cooldowns will be
|
||||||
forgotten. It is also limited only to this one command, other commands cannot
|
forgotten.
|
||||||
(easily) check for this value.
|
|
||||||
|
|
||||||
## Persistent cooldown
|
## Persistent cooldown
|
||||||
|
|
||||||
This is essentially the same mechanism as the simple one above, except we use
|
To make a cooldown _persistent_ (so it survives a server reload), just
|
||||||
the database to store the information which means the cooldown will survive a
|
use the same technique, but use [Attributes](Attributes) (that is, `.db` instead
|
||||||
server reload/reboot. Since commands themselves have no representation in the
|
of `.ndb` storage to save the last-cast time.
|
||||||
database, you need to use the caster for the storage.
|
|
||||||
|
## Make a cooldown-aware command parent
|
||||||
|
|
||||||
|
If you have many different spells or other commands with cooldowns, you don't
|
||||||
|
want to have to add this code every time. Instead you can make a "cooldown
|
||||||
|
command mixin" class. A _mixin_ is a class that you can 'add' to another class
|
||||||
|
(via multiple inheritance) to give it some special ability. Here's an example
|
||||||
|
with persistent storage:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# inside the func() of CmdSpellFirestorm as above
|
# in, for example, mygame/commands/mixins.py
|
||||||
|
|
||||||
# check cooldown (5 minute cooldown)
|
import time
|
||||||
|
|
||||||
now = time.time()
|
class CooldownCommandMixin:
|
||||||
lastcast = self.caller.db.firestorm_lastcast
|
|
||||||
|
rate_of_fire = 60
|
||||||
if lastcast and now - lastcast < 5 * 60:
|
cooldown_storage_key = "last_used"
|
||||||
message = "You need to wait before casting this spell again."
|
cooldown_storage_category = "cmd_cooldowns"
|
||||||
self.caller.msg(message)
|
|
||||||
return
|
def check_cooldown(self):
|
||||||
|
last_time = self.caller.attributes.get(
|
||||||
#[the spell effect is implemented]
|
key=self.cooldown_storage_key,
|
||||||
|
category=self.cooldown_storage_category)
|
||||||
# if the spell was successfully cast, store the casting time
|
)
|
||||||
self.caller.db.firestorm_lastcast = now
|
return (time.time() - last_time) < self.rate_of_fire
|
||||||
|
|
||||||
|
def update_cooldown(self):
|
||||||
|
self.caller.attribute.add(
|
||||||
|
key=self.cooldown_storage_key,
|
||||||
|
value=time.time(),
|
||||||
|
category=self.cooldown_storage_category
|
||||||
|
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
Since we are storing as an [Attribute](../Components/Attributes.md), we need to identify the
|
This is meant to be mixed into a Command, so we assume `self.caller` exists.
|
||||||
variable as `firestorm_lastcast` so we are sure we get the right one (we'll
|
We allow for setting what Attribute key/category to use to store the cooldown.
|
||||||
likely have other skills with cooldowns after all). But this method of
|
|
||||||
using cooldowns also has the advantage of working *between* commands - you can
|
It also uses an Attribute-category to make sure what it stores is not mixed up
|
||||||
for example let all fire-related spells check the same cooldown to make sure
|
with other Attributes on the caller.
|
||||||
the casting of *Firestorm* blocks all fire-related spells for a while. Or, in
|
|
||||||
the case of taking that big swing with the sword, this could now block all
|
Here's how it's used:
|
||||||
other types of attacks for a while before the warrior can recover.
|
|
||||||
|
```python
|
||||||
|
# in, say, mygame/commands/spells.py
|
||||||
|
|
||||||
|
from evennia import default_cmds
|
||||||
|
from .mixins import CooldownCommandMixin
|
||||||
|
|
||||||
|
|
||||||
|
class CmdSpellFirestorm(
|
||||||
|
CooldownCommandMixin, default_cmds.MuxCommand):
|
||||||
|
key = "cast firestorm"
|
||||||
|
|
||||||
|
cooldown_storage_key = "firestorm_last_cast"
|
||||||
|
rate_of_fire = 60 * 2
|
||||||
|
|
||||||
|
def func(self):
|
||||||
|
|
||||||
|
if not self.check_cooldown():
|
||||||
|
self.caller.msg("You cannot cast this spell again yet.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# [the spell effect happens]
|
||||||
|
|
||||||
|
self.update_cooldown()
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
So the same as before, we have just hidden away the cooldown checks and you can
|
||||||
|
reuse this mixin for all your cooldowns.
|
||||||
|
|
||||||
|
### Command crossover
|
||||||
|
|
||||||
|
This example of cooldown-checking also works *between* commands. For example,
|
||||||
|
you can have all fire-related spells store the cooldown with the same
|
||||||
|
`cooldown_storage_key` (like `fire_spell_last_used`). That would mean casting
|
||||||
|
of *Firestorm* would block all other fire-related spells for a while.
|
||||||
|
|
||||||
|
Similarly, when you take that that big sword swing, other types of attacks could
|
||||||
|
be blocked before you can recover your balance.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue