Daemons and APIs
The calls you actually reach for when building an area.
A daemon is a single loaded object that owns one job for the whole game:
cooldowns, timed effects, quests. You never clone a daemon. You call
it by the name its header gives it, like COOLDOWN_D->add(...), and the
driver loads it on first use.
This tome covers only the handful that area code uses day to day. Every name here was checked against the live mudlib. When in doubt, read the daemon’s own source: most of them open with a usage comment.
The three headers
| Header | Gives you |
|---|---|
<std.h> |
Inherit paths (ROOM, MONSTER, WEAPON, and the rest), plus EFFECT_D. Nearly every file includes it. |
<daemons.h> |
Daemon paths: COOLDOWN_D, QUEST_D, and the rest. |
<dirs.h> |
Directory roots: DIR_DAEMONS, DIR_LOGS, REALMS_DIRS. Rarely needed in area code. |
The top of a typical area file: the two global headers, then your area’s own header by relative path.
#include <std.h>
#include <daemons.h>
#include "../../cabeiri.h"What to inherit
Each define in <std.h> is the path of a base object. Inherit the one that
matches what you are building, then call ::create() first in your own
create().
| Define | Path | Use it for |
|---|---|---|
ROOM |
/std/room |
Every ordinary room. |
VAULT |
/std/vault |
A room with doors. |
PIER |
/std/pier |
A room players can fish in (with a fishing pole). |
MONSTER |
/std/monster |
Every NPC. |
VENDOR |
/std/npc/vendor |
A shopkeeper. |
BARKEEP |
/std/npc/barkeep |
A bartender who sells food and drink. |
OBJECT |
/std/Object |
A plain item: keys, quest tokens, junk. |
WEAPON |
/std/weapon |
Anything wielded. |
ARMOUR |
/std/armour |
Anything worn. |
STORAGE |
/std/storage |
A container (BAG is the same path). |
BACKPACK |
/std/obj/backpack |
A wearable container. |
FOOD / DRINK |
/std/obj/food, /std/obj/drink |
Consumables. |
LIGHT |
/std/light |
Torches and lanterns. |
SALVE |
/std/obj/salve |
Healing salves. |
The Area Design tome has a complete, annotated file for the most common of these. Every one follows the same opening.
inherit ROOM;
void create() {
::create();
}COOLDOWN_D: once every so often
Use a cooldown whenever a player can trigger something good: praying at an altar, pulling a lever that heals, a weapon’s special. Without one, a script can repeat it every second.
COOLDOWN_D->add(who, key, rounds) starts a cooldown and returns a mapping,
or returns 0 if one with that key is still running. A round is 2.5
seconds, so 24 rounds is one minute. Cooldowns are saved to disk and keep
running through a reboot.
public int cmd_pray(string str) {
object tp = this_player();
if(str != "altar") {
write("Pray at what?");
return 1;
}progress() tells the player how far along they are (“You are on the way
to recovery.”) and returns 1, so it doubles as the command’s return value.
if(!COOLDOWN_D->add(tp, "Cabeiri altar", 24 + random(24))) {
return COOLDOWN_D->progress(tp, "Cabeiri altar");
}The key is shared by everything that uses it. Name it for your area (“Cabeiri altar”), not something generic (“altar”), or two areas will share one cooldown.
write("You kneel and pray. A tranquil feeling fills you.");
tp->add_hp(50 + random(100));
return 1;
}EFFECT_D: something that happens over time
Poison that ticks, a chant that plays out line by line, a buff that wears
off. EFFECT_D calls your functions on a timer so you never write a
call_out chain by hand.
EFFECT_D->add(id, target, owner, duration, options)
| Argument | Meaning |
|---|---|
id |
A name for the effect. |
target |
Who it happens to. |
owner |
Who started it. |
duration |
How many times the action function is called before the effect ends. |
options |
Optional mapping: source, args, tick_delay, and more. |
It returns 1 if added, or 0 if the same effect (id, target, owner) is
already running.
public int cmd_drink(string str) {
object tp = this_player();
if(str != "water") {
write("Drink what?");
return 1;
}
write("You drink from the stagnant pool.");The effect’s functions live in source, which defaults to owner. Here
the room is both, so the daemon looks for them in this file.
When owner and target are both living (an NPC poisoning a player), the
effect also ends when their fight ends. Pass "enforce_combat": 0 to let
it outlast the fight.
EFFECT_D->add("pool sickness", tp, this_object(), 5, ([
"tick_delay": 2,
]));
return 1;
}The daemon finds your functions by name: the id with spaces turned into
underscores, then _begin_effect, _action_effect, or _end_effect. Each
is optional. Every one receives the effect’s mapping first, then anything
you passed in args.
public void pool_sickness_begin_effect(mapping effect) {
tell_object(effect["target"], "Your stomach turns over.");
}tick_delay above makes the action fire every other tick instead of every
tick.
public void pool_sickness_action_effect(mapping effect) {
tell_object(effect["target"], "Nausea rolls through you.");
effect["target"]->add_hp(-10);
}
public void pool_sickness_end_effect(mapping effect) {
tell_object(effect["target"], "The sickness finally passes.");
}Check for or cancel an effect with the same three keys. delete skips the
end function unless you pass "call_end": 1.
if(EFFECT_D->exists("pool sickness", tp, this_object())) {
EFFECT_D->delete("pool sickness", tp, this_object(), ([
"call_end": 1,
]));
}Instead of add |
When the effect is already running |
|---|---|
add_or_refresh |
Restart its countdown. |
add_or_duplicate |
Stack a second copy. max_stacks caps it. |
add_or_upgrade |
Raise its tier, up to max_tier. |
The full option list is in the comment at the top of /daemon/effect.c.
Quests and deeds
Merentha has two kinds of achievement. A quest is a major, registered story with fixed rewards. A deed (called a mini quest in the code) is a smaller accomplishment any area can award on its own.
Quests
A quest must be registered in the quests mapping in /daemon/quest.c
before it can reward anything. The entry names it, sets its level, and lists
any extra rewards: stat, class_stat, skill, money, or alignment.
"goblin king": ([
"short": "The Goblin King",
"source": "/domains/Cabeiri/goblin/mon/king",
"level": 5,
"long": "The goblins of the Cabeiri caves answer to a king.",
"rewards": ({ ({ "alignment", 250 }) }),
]),Award it by calling set_quest() on the player. It returns 0 if they
already have it. Otherwise it records the quest, logs it, and has QUEST_D
award experience (level squared times 1000) plus the listed rewards.
An id missing from /daemon/quest.c is still recorded on the player, but
they get no rewards. Register first.
if(tp->set_quest("goblin king")) {
write("The goblins will remember your name.");
}Deeds
set_mini_quest(id, exp, description) needs no registration. The player
gets exp experience, and the description shows in their deeds list. It
returns 0 if they already have this deed, so the reward only happens once.
if(tp->set_mini_quest("cabeiri shovel", 500,
"You returned the gravedigger's shovel.")) {
write("The gravedigger nods his thanks.");
}query_quests() and query_mini_quests() return the ids a player has.
They are also what set_auto_load‘s quest and deed keys check (see the
Area Design appendix).
if(member_array("goblin king", tp->query_quests()) == -1) {
write("The guard blocks your way.");
return 1;
}Money
Money is a mapping of currency to amount on every living thing. The
currencies are platinum, gold, electrum, silver, and copper.
add_money() takes a negative amount to charge, and drops the currency
entirely when it falls below 1. It does not check the player can afford it,
so always check first.
if(tp->query_money("gold") < 50) {
write("You cannot afford the toll.");
return 1;
}
tp->add_money("gold", -50);For an NPC’s purse, use set_money() in create(). It refuses to work on
players.
set_money("copper", 20 + random(20));