Things that happen on their own: tickers and effects.
Everything so far happens because a player typed something. A world where nothing moves unless someone pokes it feels like a museum. Two systems let things happen on their own.
A ticker calls a verb on an object over and over, every so many seconds, until you stop it. Start one from any verb:
ticker_add(30, 'at_regen', pobj, 'regen')
^ ^ ^ ^
seconds verb object a name for this ticker
Every thirty seconds, that calls the at_regen verb on the player.
The last argument is a label so one object can run several tickers at once and
you can stop them individually:
ticker_remove(pobj, 'regen') # stop that one
ticker_remove_all(pobj) # stop all of this object's tickers
First, a rule that catches everyone out: a verb can change a property
but cannot invent one. Anything your verb writes to has to exist
first, and running a ticker on an object needs one more —
tickers, where the subscription is kept. Declare all three before
writing any code:
> @adprop torch.fuel = 0
> @adprop torch.lit = 0
> @adprop torch.tickers = {}
Skip this and the verb fails at the first assignment with
Permission denied: cannot create 'fuel' — a confusing message for
what is really a missing declaration.
Now light the torch. It starts a ticker on itself; each tick reduces its fuel, and when the fuel runs out it goes dark and stops its own ticker:
# the `light_` exception verb on the torch
this.fuel = 20
ticker_add(60, 'burn', this, 'burn')
pobj.msg("The torch catches with a soft rush.")
return True# the `burn` verb on the torch — called once a minute
this.fuel = (getattr(this, 'fuel', 0) or 0) - 1
if this.fuel <= 0:
ticker_remove(this, 'burn')
this.location.msg_room("The torch gutters and dies.")
this.lit = False
elif this.fuel == 5:
this.location.msg_room("The torch is burning low.")Watched with a two-second tick instead of a minute, that runs:
fuel 4 → 3 → 2 "The torch is burning low."
fuel 1 → 0 "The torch gutters and dies." (ticker removed)A ticker runs forever until something removes it, so always give a verb a way to stop its own. Prefer one ticker doing a small amount of work to many tickers doing almost nothing — a room can tick for everything in it.
An effect is a temporary state on a character that fires a set number of times and then ends by itself — drunk, poisoned, blessed, regenerating. You do not manage the countdown; the effects system does.
$eu.trigger(pobj, 'intoxicate', 3, 60)
^ ^ ^ ^
who effect name times seconds apart
That applies the intoxicate effect three times, a minute apart, and then it is
over. Each effect is a verb named do_<name> on
$eu, the effects object — the shipped world includes
do_intoxicate, and you add your own the same way.
| Call | Does |
|---|---|
$eu.trigger(who, name, times, interval) | Starts one effect. |
$eu.trigger_all(who, effects) | Starts a whole list at once. |
$eu.cancel(who, name) | Ends an effect early. |
$eu.list_active(who) | What is currently running on them. |
Lists are written as one tuple per effect, which is what you store on any object that should apply several of them at once:
effects = [('intoxicate', 3, 60)] # name, times, seconds apartThe character sees the effect arrive, repeat, and wear off on its own:
A warm, dizzy feeling washes over you.
The world spins around you...
The world spins around you...
Your head begins to clear.tickers property
Effects are timed, so applying one starts a ticker on the character. A
character without a tickers property fails with
Permission denied: cannot create 'tickers' the moment an
effect lands. @adprop <character>.tickers = {} fixes
it.
| Use a ticker when | Use an effect when |
|---|---|
| An object should keep doing something — a torch burning, a fountain splashing, a beacon sweeping. | A character is temporarily in some state that should wear off on its own. |
| You decide when it stops. | It stops itself after the count you gave. |
| You write the verb it calls, on the object. | You write the effect once on $eu, and anything can apply it. |
A ticker has to be registered before it can fire, and registration does not
survive being set up by hand once. Anything a world needs done on every
start — register a ticker, prime a cache, open a gate — goes in
$startup_evals, a list of expressions on #0:
#0.startup_evals = ["ticker_add(60, 'sweep', $bin, 'sweep')"]
Each entry is evaluated the way eval evaluates one, so what goes
in the list is written in the same language as everything else in the world.
They run after the database, the listeners, the tickers and the background
tasks are all up, so an entry can register a ticker or reach the network
without needing ordering rules of its own.
Keep each subsystem's line with that subsystem rather than as a
literal in the list — a property on the object it belongs to, gathered into
#0:
$bin.sweep_eval = "ticker_add(60, 'sweep', $bin, 'sweep')"
#0.startup_evals = [$bin.sweep_eval]
That stores the value of $bin.sweep_eval, not a live
reference to it. Editing the property later does not change the list; the
property is where the line lives, and the list is a snapshot of what runs.
Each entry is isolated, and so is the machinery around them: a failure is logged and the next one runs. These are edited from inside a running game, where a typo is an ordinary afternoon — and a server that will not boot because of a line you can only fix from inside it is a trap. Check the startup log to see what ran:
Running 1 startup eval(s) from $startup_evals
startup_evals[0] ok: ticker_add(60, 'sweep', ...)