Skip to content

Fenia Behaviors

Kit Oliynyk edited this page Nov 3, 2026 · 2 revisions

TL;DR: A behavior is a named bundle of Fenia triggers, default properties and a help article. You create it once with bedit, write its trigger files, and attach it to any number of mobs, items or rooms. Only some triggers reach behaviors. The table below is built from the C++ dispatch sites. Use when: you share one scenario between many prototypes, attach or detach a behavior, or need to know whether a given trigger works on a behavior. Source of truth: dreamland_code/plug-ins/loadsave/behavior_utils.cpp (behavior_trigger), plug-ins/feniaroot/behaviorwrapper.cpp, plug-ins/olc/bedit.cpp, plug-ins/olc/olcstate.cpp (editBehaviors), dreamland_world/behaviors/*.xml, dreamland_world/fenia.examples/behavior/ Verified: 2026-11-02 against dreamland_code a7c4fd10, dreamland_world a35c62ce. Private script repositories were read for behaviour only.

Triggers and their arguments are on Fenia-Triggers. This page covers what is specific to behaviors.

What a behavior is

A behavior has two halves that must agree:

  1. A definition in the world data: dreamland_world/behaviors/<name>.xml.
  2. Trigger functions in the Fenia database, one script per trigger, each assigning to .Behavior("<name>").on<Event>.

The behavior does nothing until it is attached to a prototype and both halves are loaded.

<?xml version="1.0" encoding="UTF-8"?>
<behavior type="DefaultBehavior">
  <cmd></cmd>
  <description></description>
  <help id="257" level="-1" type="BehaviorHelp">
    <extra l="en">adept acolyte healer buffs newbie</extra>
    <text l="en">An adept looks after the young and after its own ...</text>
  </help>
  <id>30</id>
  <nameRus>адепт</nameRus>
  <nameUa>адепт</nameUa>
  <props>null</props>
  <target>mob</target>
</behavior>
Field Meaning
id Numeric id, allocated by bedit create.
target mob, obj or room. The kind of prototype the behavior can attach to.
nameRus, nameUa, description Names and text shown in editors and help. English uses the file name.
cmd Alternative sub-command names for use <item> <subcommand>. Usually empty.
props Default properties as JSON, or null. Copied to a prototype when the behavior is attached.
help The help article for the behavior. bedit create allocates its id.

A behavior name is lower-case and may contain spaces, for example master samurai. Two-word names are quoted in a script: .Behavior("master samurai").

Create and edit

bedit create <new name>
bedit list
bedit <name>
  • bedit create refuses a name that collides with an existing behavior keyword. It registers the behavior in the Fenia database, allocates a help id and opens the editor.
  • .Behavior("name") throws name: behavior not found until the behavior has been created. The registry persists in the database across reboots.
  • bedit list shows every mob, item and room that uses the behavior.
  • Editor sub-commands: nameRus, nameUa, description, target, props, subcommands, help, affect, fenia <trigger>, list, show, commands, done. fenia <trigger> opens the web editor with a template taken from fenia.examples/behavior. The templates exist for these triggers:
Target Templated triggers
mob onArea, onBribe, onCantTeach, onCantTrain, onFight, onGive, onGreet, onSpec, onSpeech, onTeach, onTrain, postSpeech, postTeach, postTrain
item onArea, onCantFetch, onCantPut, onDrop, onEquip, onFetch, onFight, onLook, onPut, onRemove, onSpec, onUse, postUse
room onDive, onDiveUpdate, onReset, onShowEnd, onSpec

A template exists to help you start. A trigger without a template can still work if the engine dispatches it (see below).

  • affect creates or edits an affect handler tied to the behavior, so one behavior can also act as a persistent affect.
  • The props editor sets the default properties. A script can read them with .Behavior("name").props.
  • The definition file lives in dreamland_world/behaviors. Commit it with the rest of the world data. Do not hand-write a second definition with a different id for a name bedit already created.
  • A help id collision crashes the next plugin reload. Pick help ids from the live maximum, see Help-Articles.

Trigger files

One file per trigger, named behaviors/<name>/<trigger>. The body is a single assignment. The first argument of every behavior trigger is the entity the behavior sits on:

.Behavior("fido").onGreet = function(mob, ch) {
    if (ch.is_npc())
        return false;
    mob.interpret("emote wags its tail.");
    return false;
}

A file with more than one top-level assignment must be wrapped in .apply(function() { ... }), with a ; after each assignment inside. A shared engine file of helper methods is written that way: .Behavior("name").helper = function(...) { ... };.

Attaching

Area files list behaviors on each prototype as a space-separated registry list:

<behaviors registry="behavior">fido thief</behaviors>

A prototype can carry several Fenia behaviors and also a C++ behavior class at the same time. The C++ class is written as <behavior type="ShopTrader">...</behavior> and is a different mechanism.

In OLC, behaviors inside medit, oedit and redit edits the list. Adding a behavior copies its default props into the prototype's own props[<behavior name>]. Removing it deletes that key.

From a script you can set the list on a prototype. A string of names or a list replaces the whole set, null clears it, and an unknown name throws:

.get_mob_index(3090).behaviors = "fido";

To check from a script: mob.hasBehavior("fido"), obj.hasBehavior("name"), room.hasBehavior("name"). The check reads the prototype's list and throws if the behavior name does not exist. mob.clearBehavior() resets the mob's C++ behavior class (it does not touch Fenia behaviors).

Which triggers reach behaviors

Dispatch is per event, not universal. A behavior method fires only where the C++ code for that command calls behavior_trigger. This table is built from the call sites.

Entity Triggers a behavior can define Arguments (entity first)
mob onSpec mob. Skipped in an area marked empty.
mob onArea mob. Once per tick.
mob onGreet mob, walker
mob onSpeech mob, talker, msg. Fires for say in the room and for a tell to the mob.
mob onBribe mob, giver, gold, silver
mob onGive mob, ch, obj
mob onFight mob, victim
mob onShow mob, ch. Returns a string tag added to the mob line in room lists and matched by where.
mob onDescr mob, ch. Returns a hint line shown under the mob description on look.
mob any name fired by a Fenia command script with mob.trigger("Name", ...) arguments as that script passes them
item onSpec, onArea obj
item onGet obj, ch. The item entered ch's inventory: get from the floor or a corpse, give and request with ch the receiver, obj.trigger("Get"). Return true to stop the rest of the get chain.
item onDrop obj, ch
item onLook obj, ch, keyword. After the text was shown, append only.
item onUse obj, ch, argument
item onPut, onCantPut container, ch, obj and container, ch, obj, pocket, verbose
item onFetch, onCantFetch container, ch, obj and container, ch, obj, pocket
item onEquip, onRemove obj, ch
item onWear obj, ch. ch put the item on through the wear path: the wear command, a mob arming itself, a reset re-equipping a mob. Login and obj.equip fire only onEquip. Hang affects in onEquip.
item onPourOn item, ch, out, liqname, amount
item onPourGround out, ch, liqname, amount
item onFight obj, ch
room onReset, onSpec, onDiveUpdate room
room onDive room, walker, danger
room onEntryNear room, walker, movetype
room onShowEnd room, ch, looker. Returns a string.

Things that do not reach a behavior:

  • onDeath, onKill, onCantLeave, onCommand, onTell, onSafe, onSocial, onEmote, onExamine.
  • onTell as a separate trigger: a behavior onTell loads and never fires. Use onSpeech on the behavior, or keep onTell as a prototype trigger.
  • onExamine. examine calls only the C++ behavior class, so a Fenia onExamine on a behavior never runs. onLook does fire, but only after the main text.
  • Anything not in the table. Before you rely on a trigger, search the C++ for behavior_trigger( with its name.

Two practical rules follow:

  1. A migration from prototype triggers to a behavior can end up hybrid: onGreet and onSpec on the behavior, onTell and onSafe still on the prototype. Check each trigger with a real action in the game. hasBehavior returning true proves nothing about dispatch.
  2. For generated text that must answer look or read (a book that lists its pages, a board), use a prototype onExtraDescr or onExamine, not a behavior. Answer only your own keywords.

Behavior triggers also come in post form. A post trigger runs after the command in its own thread. A behavior with only a post<Event> and no on<Event> cannot suppress the underlying C++ command, whatever it returns.

Properties

Properties are a JSON object per behavior. The behavior definition holds the defaults. Each prototype holds its own copy, under the behavior's name:

var proto; proto = .get_obj_index(obj.vnum);
if (proto != null && proto.props != null && proto.props["random weapon"] != null)
    ...
  • Read per-prototype settings through the prototype (obj_index.props, mob_index.props, room.props). The key is the behavior name, quoted with ["..."] when it has a space. A missing key gives NONE.
  • obj.props on an instance is that instance's own properties. It is null unless a script gave the instance some. Do not expect the prototype's properties there.
  • obj_index.property(name, default) and setProperty(key, subkey, value) read and write a prototype property. obj.property(name, default) and obj.setProperty(name, value) do the same on an instance.

Retiring an old engine

When you replace a prototype-level scenario with a behavior, annul the old bindings so nothing fires twice:

  1. Stand up and load the new behavior. Check that every trigger fires once.
  2. Set every trigger the old script attached to the prototypes to null, including helper functions it hung on them: .get_obj_index(v).onUse = null;. Trigger names are mandatory here. Otherwise both the prototype and the behavior fire.
  3. Set the old shared namespace to null (.tmp.OldEngine = null;).
  4. Delete the old files, and for a C++ migration remove the <spec> tag from the area nodes.
  5. Look for leftovers with cs search <old name>.

See also

Clone this wiki locally