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));