Skip to content

Calling Behaviors

For: advanced authors fanning out into reusable blocks.

Big bundled mods like infection and vip-escort run a fatter pipeline than the simple one in Modifiers: event blocks guard and record shared state, then timer blocks evaluate per member. Here it is with infection:

1. Event block fires (ON_DEATH)
   A speedrunner dies what might be their final death.
        |
        v
2. Guard lines exit early (<if> plus exit)
   Former role SPEEDRUNNER? Eliminated for good?
   Anything else leaves quietly.
        |
        v
3. Shared state remembers (flags)
   <pswitch> the victim to HUNTER, append them
   to the <gflag:infected> list.
        |
        v
4. Timer block evaluates per member (INTERVAL)
   Every 12s per hunter: on the infected list?
   Root hunters exit on line one.
        |
        v
5. Game-world effects, gated by chance
   Sporadic blindness, groceries on luck
   (each candidate fails 80% of the time).

vip-escort shows the same shape with three blocks: an ON_START pick into <gflag:vip>, an ON_DEATH block that ends the game when the VIP dies, and a 1-second INTERVAL block looping effects over the VIP and clearing their armor. Bingo cards, bounties, and position swaps all follow this pattern: guard, remember, evaluate per member, apply.

<runblock:id> runs another behavior block of the same modifier inline, in the current run: same executor, same event args. The target's console list always runs; its player, hunter, and speedrunner lists run only when a live executor exists. The target keeps its execution mode (IN_ORDER or PICK_RANDOM) and its success chance; its triggers, interval, and delay are ignored, so a called-only block never fires on its own. The classic split is one interval block that refreshes state, checks completions, and calls a broadcast block only when something is due:

behavior:
  eligibility:
    runs-on:
      - INTERVAL
    options:
      interval-settings:
        interval: 8
        deviation: 2
    commands:
      console:
        - "<runblock:refresh>"
        - "<if:<gflag:dirty>==true,<runblock:broadcast>>"
  refresh:
    commands:
      console:
        - "<gflag:dirty,false>"
  broadcast:
    commands:
      console:
        - "<gmessage:standings updated>"

Chains share the calling line's step budget with loops and functions, cycles (a calls b calls a) stop with one warning per match, and unknown ids warn once per match and skip. A block that calls itself stops the same way, so prefer one direction: callers call down, called blocks never call back.

<runblock:id,arg1,arg2> passes arguments: inside the target, <args:0> is arg1, <args:1> is arg2, and so on (override-all: passed args replace the caller event args entirely, so a block that needs both must receive the event values explicitly). Args evaluate in the caller context before the hop, so <i> and <lf> names in arg position resolve per iteration; one surrounding quote layer is stripped ("a, b" arrives as a, b). The one-part form keeps inheriting the caller args, which is how an interval block hands its elapsed time to a refresh block without naming it.

# bingo: eligibility fans out per member with the name as arg
- "<for:<active-players:ALL>,<runblock:check-member,<i>>>"
# check-member completes one slot with WHO and SLOT as args
- "<runblock:announce-slot,<lf:who>,<i>>"
# announce-slot binds them to locals first
- "<lf:who,<args:0>>"
- "<lf:slot,<args:1>>"

Next Steps