Documentation menu

Documentation / Under the hood

Lua API

Everything under emunah. that is safe to build on from your own aliases, triggers and scripts, and the few places to call Emunah instead of the obvious Mudlet function.

Send through the gate

Automated commands go through emunah.act.send(), not Mudlet's send(). send() knows nothing about stun, prone or balance, and will fire into a refusal once per prompt for as long as the condition lasts.

-- Fires into "You must regain equilibrium first."
send("perform penitence " .. target)
-- Held until the character can do it
emunah.act.send("perform penitence " .. target, { standing = true, eq = true })

A command you typed is different: it should go straight out, and Emunah's own keys do exactly that.

emunah.act

State what a command needs; the gate decides when that can be met.

NeedMeans
standing = trueNot while prone, paralysed or entangled. Attacks and GET need it; eating and sipping do not.
bal = trueBalance is up, and nothing of Emunah's is about to spend it.
eq = trueEquilibrium is up, and nothing of Emunah's is about to spend it.
whileAsleep = trueAllowed while asleep. Only WAKE should say this.

Stun, unconsciousness, death and a pause hold every command whatever it declares.

CallDoes
act.send(command, needs)Sends if it can, and returns whether it did. Under emset debug a held command is logged with its reason.
act.can(needs)Whether it could go now, without sending.
act.batched(fn)Run fn with its sends collected, and send them as one line joined by the game's separator when it returns. Every prompt's tick, every timer lapsing and every confirmation timing out already runs inside one.
act.separator()What to join two commands with when composing one that carries both (act.JOIN, ;;); empty when queue.chain is off. act.send() splits it again before sending.
act.gameSeparator()The game's CONFIG SEPARATOR, once the game has named it this session; nil before then, or with none set, and every command goes on its own line.
act.blocked(needs)Why not, as a short reason, or nil. For logging; do not branch on the wording.

emunah.queue

One slot per balance. A more urgent push replaces what is waiting; a less urgent one is refused.

emunah.queue.push("elixir", "drink health", {
  priority = 20,           -- lower wins; default 100
  tag      = "emergency",  -- what it is for
  confirm  = 2.0,          -- seconds to wait for the game before freeing the slot
  needs    = {},           -- passed to act
  valid    = function() return emunah.gmcp.vitals.below("hp", 50) end,  -- asked again at send time
})
CallDoes
queue.flush()Send what is pending on every free balance. Runs every prompt; you rarely call it.
queue.pending(vector)What is waiting on a balance.
queue.awaiting(vector)What has been sent on it and not yet answered.
queue.confirm(vector)Mark the sent action answered, freeing the balance early.
queue.heldBy(vector)The affliction or paralysis holding a balance shut, if any.
queue.clear(vector)Drop what is waiting on one balance, or on all of them.
queue.snapshot()Everything pending and in flight.

Balances: free, balance, equilibrium, herb, salve, elixir, purgative, smoke, focus, tree, moss, writhe, special and rift.

Your own command or function

queue.user(kind, what, label) puts a command, or a Lua function, on the balance it needs, ahead of everything Emunah queued. kind is bal, eq, eqbal or free. While it waits, Emunah's own sends that want the same balance hold for it, the attacks included. A function runs as Emunah's own, so what it sends does not count as typed. emset queue <kind> <command> does the same from the command line, and queue.clearUser() or emset queue clear drops what has not gone yet.

emunah.queue.user("eq", function()
  send("angel strip " .. emunah.bashing.target)
end, "strip")

After you type a command, Emunah's balance and equilibrium sends wait queue.typedHold seconds (0.5 by default) plus your latency, so yours goes first.

emunah.have

"Can I actually do this?", answered from tracked state. It never sends anything, and when data has not arrived yet the answer is the cautious one.

CallAnswers
have.skill(name), have.skillset(name)Do I know this ability, or this skillset?
have.item(name), have.inRift(name), have.supply(name)How many in inventory, in the rift, or both.
have.pipe(herb)Is there a lit pipe with this herb in it?
have.def(name), have.affliction(name)Is this defence up, or this affliction on me?
have.balance(vector)Is this balance free right now?
have.vectorBlocked(vector)Why a balance is shut, or nil.
have.cure(option)Can I perform this cure? false, reason when not.
have.spend(vector), have.recover(vector)Mark a balance spent or recovered ahead of its timer.

emunah.gmcp

Field or callMeans
vitals.hp, .mp, .ep, .wp, vitals.percent.hpCurrent values and percentages, as numbers.
vitals.bal, vitals.eqReal booleans. The raw GMCP fields are the strings "1" and "0", and "0" is true in Lua.
vitals.below(res, pct), vitals.damageTaken()Under a percentage; health lost since the last prompt.
vitals.trusted(res)False while recklessness is falsifying the figure.
vitals.stat(name, fallback)A class figure, such as vitals.stat("Devotion").
items.at("inv"), items.at("room")Everything in a place.
items.find, items.first, items.countBy a substring of the name.
items.attrib(item)The attribute letters as named booleans: container, monster, dead and so on.
items.roomFresh()Has the room list arrived since the last move? Check before deciding a room is empty.
skills.has(name), skills.rank(group)Do I have an ability; my rank in a skillset.
room.playerCount(), room.isSelf(name)Other players here, excluding you; is that name you.

emunah.event

Register through Emunah rather than Mudlet, so a reload removes your handlers instead of stacking a second copy.

emunah.event.register("emunah.bashing.killed", function(_, id, name)
  cecho("\n<green>Killed " .. tostring(name))
end, "myscript")
emunah.event.kill("myscript")   -- remove all of yours
EventArgumentsRaised when
emunah.tickcountEvery prompt.
emunah.vitalsvitalsChar.Vitals has been processed.
emunah.balance.gained, .lostBalance came back or went.
emunah.equilibrium.gained, .lostThe same, for equilibrium.
emunah.recoveredA stun lifted, or you stood up.
emunah.affliction.added, .removednameAn affliction appeared or went.
emunah.defence.added, .lostnameA defence went up or came down.
emunah.items.added, .removedlocation, itemSomething appeared or left. A kill is a removal.
emunah.items.listlocationA full list replaced a location.
emunah.roomnum, nameYou moved.
emunah.bashing.started, .stoppedreason on stopHunting started or stopped.
emunah.bashing.killedid, nameThe target died, fled, or was taken.
emunah.walker.arrived, .finishedroom; visited, reasonThe walker reached a room, or finished.
emunah.pvp.started, .stopped, .targetPvP state and target changes.
emunah.loot.takenid, nameGold was picked up.
emunah.character.died, .revivedDeath and revival.
emunah.map.walking, .arrived, .stopped, .failedtarget, and steps or reasonA walk started, arrived, stopped or found no route.
emunah.foename, classSomeone attacked you with a line only one class sends, or changed class.
emunah.ally.arrived, .left, .callname; caller, targetA partner came or went, or called a target.
emunah.opponent.afflicted, .defencename, affliction or defenceAn affliction or defence was seen on someone else.
emunah.promptThe prompt line itself, after its block has been applied. Echo here to add to the end of the prompt.

Raise emunah.walker.move to tell the walker a room is finished, emunah.walker.stop to stop it, and emunah.bashing.pause to stop hunting.

emunah.timers and emunah.config

CallDoes
timers.start(name, seconds, onExpire)Start or restart a named cooldown.
timers.ready(name), timers.remaining(name)Has it lapsed; seconds left.
timers.stop(name)End it early.
config.get(key, default), config.set(key, value), config.save()Any setting, by its dotted name, saved per profile.

Hunting, walking and PvP

CallDoes
bashing.start(), .stop(reason), .report()The hunting loop. start() returns false with no class module.
walker.start(), .stop(reason), .move()Walk the area, stop, or step now.
walker.claim(owner), .release(owner)Take over pacing, so the walker waits to be told to move.
walker.avoidRoom(id), walker.retreat(n)Leave a room out of the walk; go back n rooms.
denizens.here()Living denizens in the room, by replica number, without your own angel or mount.
denizens.setWanted(name, bool)Add or remove a kind from the area's kill list.
pvp.setTarget(name), pvp.clearTarget()The only way a PvP target is ever set.
loot.sweep()Pick up the gold in the room, if it can.

Interface

CallDoes
ui.chyron.send(text, colour), ui.chyron.clear()Put a message on the ticker, or clear it.
ui.names.styleFor(name)How a name is coloured and weighted.
ui.names.ignore(name, true)Stop highlighting someone.
ui.theme.paint(console, key, body)Draw a whole console in one call, and not at all when unchanged. Use it for any panel you add.

The map

CallDoes
mapper.route.go(room, label, opts)Walk to a room number, one confirmed step at a time. Returns whether a walk started.
mapper.route.stop(reason), .pause(), .resume()End a walk, or pause it keeping its route and carry on from wherever you are.
mapper.route.path(from, to)The route between two rooms, without walking it.
mapper.route.eta()Steps left and roughly how many seconds, for the walk under way.

Opponents, allies and dangers

CallAnswers
curing.detect.opponent.list(name), .has(name, aff)The afflictions confirmed on someone else.
curing.detect.opponent.blockedVectors(name)Their cure routes shut, and by which affliction.
curing.detect.opponent.lockProgress(name), .lockNeeds(name)The locks they are in and what each other one lacks; what the hard and venom locks still need once one is building.
curing.detect.opponent.defenceList(name), .usedAgo(name, vector)Defences seen going up on them; seconds since they last cured through a balance.
curing.detect.foeClass(name), .activeFoes()The class someone attacked you as, and everyone attacking you in the last minute.
allies.partners(), .isPartner(name), .present()Your partners, and which of them are in the room.
watch.locks(), watch.dangers()The locks you are in, and the instant kills you are open to, as the prompt names them.
watch.lockProgress(has)The locks judged against any affliction check, yours or anyone's.

An example

Drink health below half, without fighting the curing engine for the sip balance or sending into a refusal:

emunah.event.register("emunah.tick", function()
  local v = emunah.gmcp.vitals
  if not v.below("hp", 50) or emunah.have.supply("health") == 0 then return end
  emunah.queue.push("elixir", "drink health", {
    priority = 20,
    tag      = "emergency",
    confirm  = 2.0,
    valid    = function() return v.below("hp", 50) end,
  })
end, "myscript")

It never calls send(), never checks for stun, and never tracks the sip balance. The queue holds it until the balance is free, the gate holds it while you cannot act, a more urgent cure can take the slot first, and valid drops it if you have healed while it waited.