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.
| Need | Means |
|---|---|
standing = true | Not while prone, paralysed or entangled. Attacks and GET need it; eating and sipping do not. |
bal = true | Balance is up, and nothing of Emunah's is about to spend it. |
eq = true | Equilibrium is up, and nothing of Emunah's is about to spend it. |
whileAsleep = true | Allowed while asleep. Only WAKE should say this. |
Stun, unconsciousness, death and a pause hold every command whatever it declares.
| Call | Does |
|---|---|
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
})
| Call | Does |
|---|---|
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.
| Call | Answers |
|---|---|
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 call | Means |
|---|---|
vitals.hp, .mp, .ep, .wp, vitals.percent.hp | Current values and percentages, as numbers. |
vitals.bal, vitals.eq | Real 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.count | By 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
| Event | Arguments | Raised when |
|---|---|---|
emunah.tick | count | Every prompt. |
emunah.vitals | vitals | Char.Vitals has been processed. |
emunah.balance.gained, .lost | Balance came back or went. | |
emunah.equilibrium.gained, .lost | The same, for equilibrium. | |
emunah.recovered | A stun lifted, or you stood up. | |
emunah.affliction.added, .removed | name | An affliction appeared or went. |
emunah.defence.added, .lost | name | A defence went up or came down. |
emunah.items.added, .removed | location, item | Something appeared or left. A kill is a removal. |
emunah.items.list | location | A full list replaced a location. |
emunah.room | num, name | You moved. |
emunah.bashing.started, .stopped | reason on stop | Hunting started or stopped. |
emunah.bashing.killed | id, name | The target died, fled, or was taken. |
emunah.walker.arrived, .finished | room; visited, reason | The walker reached a room, or finished. |
emunah.pvp.started, .stopped, .target | PvP state and target changes. | |
emunah.loot.taken | id, name | Gold was picked up. |
emunah.character.died, .revived | Death and revival. | |
emunah.map.walking, .arrived, .stopped, .failed | target, and steps or reason | A walk started, arrived, stopped or found no route. |
emunah.foe | name, class | Someone attacked you with a line only one class sends, or changed class. |
emunah.ally.arrived, .left, .call | name; caller, target | A partner came or went, or called a target. |
emunah.opponent.afflicted, .defence | name, affliction or defence | An affliction or defence was seen on someone else. |
emunah.prompt | The 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
| Call | Does |
|---|---|
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
| Call | Does |
|---|---|
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
| Call | Does |
|---|---|
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
| Call | Does |
|---|---|
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
| Call | Answers |
|---|---|
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.