Skip to main content
For server developers writing the integration resource: after this page an NPC on your server can perform a deed only your script knows how to do, and only once your script has reported the facts that must come first. Beside the observations it reports, the actions provider declares actions: the model chooses them like any catalogue action, HumaLike lets it perform one only once the required facts have been reported, and your RunAction does the deed.
A shopkeeper works the same way with an amount:
A refund that can’t be exploited:
Then enable srp:give_map on the NPCs that should have it, in the dashboard, like any other action.

Stocking an NPC

What an NPC has to give is your script’s to set, never the dashboard’s:
The call replaces the NPC’s whole shelf, so call it again to restock; an item not listed is one the NPC has none of. The NPC reads its exact counts, a deed that uses_stock locks at zero, and every delivered deed (a shop deliver included) takes its share. Up to 32 items per NPC, each named as your inventory spells it (a-z, 0-9, _, ., -, at most 48 characters), with a whole count up to 1,000,000 or 'unlimited'. An NPC that is not this server’s, or a shelf outside those bounds, is refused and nothing changes. The dashboard shows the current shelf on the NPC’s page, read-only. What happens: a player who asks for the map before handing over the amulet is refused in the NPC’s own words (“Not until that amulet is in my hand.”) and nothing reaches your script. After your hook reports item_given { item = 'amulet' } the NPC may answer with the tag, [srp:give_map copies=2] Here, take it., and RunAction receives ('give_map', source, npcCoords, { player_id = 12, copies = 2, item = 'treasure_map' }). What fails: a declaration with a mistake is refused at registration, in your console, with the field at fault — [humalike] actions provider from my-adapter rejected: unknown observation in requirement 1 of action give_map — and nothing is reported. A tag whose values do not fit the declaration (copies=3 here) is dropped before it is spoken or remembered, so your script never sees a value it did not declare.

A shop counter

For NPCs that sell, declare a Catalog instead of one action per product and HumaLike runs the whole sale:
An NPC sells whatever of the catalogue your script stocked it with, at these prices. The model takes the order as one tag with every line ([srp:order water=10 pistol=1]); HumaLike checks the lines against the shelf and the per-item limits, prices them and tells the NPC the total. Payment is the reported item_given of the currency, in whole units: the payment observation declares quantity = 'integer' (a number is refused at registration), and a reported quantity of zero or less buys nothing. Once the money on the counter covers the total, HumaLike calls deliver itself on the NPC’s next reply to that player, with the basket and the change (164 paid on 163 → 1 back), records the sale in plain words (“you handed over 10 x water for 50 cash, 1 back as change”) and takes every line off the shelf. The NPC is told, in that reply’s prompt, that the goods go out with this reply unless it cancels. Hand over everything or nothing: return false when the customer cannot take it, and nothing is recorded — order and money stay on the counter, the NPC is told the hand-over failed and HumaLike tries again on its next reply to that player. The retry carries the same invocation id for as long as the same order stands: the id is derived from the order (its record and its basket), not from the money, so a top-up or a tip in between does not change it and a RunAction that did deliver but lost the answer can recognise the repeat instead of handing over twice. A new [srp:order ...] is a new deed with a new id. A refund is identified the same way by the order it cancels (by the payments themselves when money sits on the counter with no order). If the shelf ran out in the meantime (another customer took the last unit), the hand-over is not attempted; the NPC is told and can cancel for a refund. Underpaid (160) → nothing moves, the NPC is told what is owed. One counter deed at a time per customer: while a deliver or a refund for a player has not answered yet, HumaLike neither retries it nor accepts a [srp:cancel_order] for that player, and the NPC is told the hand-over is pending — a slow callback can never pay out twice. Answer RunAction promptly; HumaLike waits up to 5 seconds. An answer that never arrives (a timeout, a dropped connection, an HTTP 5xx) is sent again at once, up to three attempts in all, under the same invocation id — for every action, not only the counter’s. Keep the ids you accepted and answer a repeat from that record, and a lost reply never runs the deed twice. A false is an answer: a rejected deed is not retried automatically. If the customer backs out, the NPC’s [srp:cancel_order] calls refund with exactly what is on the counter — and wins over a pending delivery in the same reply, wherever in the line it was written, so what the NPC says and what happens never diverge. A refund your script refuses (false) is remembered too: the NPC is told the money did not go back, HumaLike retries the refund on its next reply to that player, and the goods do not go out meanwhile — the sale was called off out loud. A new [srp:order ...] while one is open replaces it; the money on the counter stays and counts towards the new total. An order the counter refuses (a line not sold, not enough on the shelf, over a per-player limit) is recorded with its reason, so the NPC reads why on its next turn instead of quoting a price for a sale it cannot make. The model never authors a price, a quantity delivered or an amount returned. A server with its own shop menu skips the conversation: exports.humalike:PlaceOrder(npcId, playerId, { water = 2 }) sends the picked lines, which HumaLike prices the same way against the live shelf; the NPC then announces the total. A menu order does not replace one the player has already paid for: while their paid order awaits hand-over it is refused (recorded as order_refused with reason order_open), and the NPC says so. A spoken re-take waits the same way while a hand-over is pending. Money on the counter never expires: an unspent payment is the player’s until a deliver or a refund spends it, so a customer who could not take the goods and comes back hours later still has their money on the counter and [srp:cancel_order] stays open to give it back. Only the order ages out after an hour (yesterday’s basket is not what today’s money is for); the money then sits on the counter with no order, and the NPC is told so. An observation’s spent field is HumaLike’s own (it names the facts a deed spent) and cannot be declared or reported. Declare deliver and refund bare — no params, requires, limit, uses_stock, auto or params_from — the counter fills them. The counter’s keys are reserved in your namespace: you cannot declare order, cancel_order, order_placed or order_refused as an action or an observation, nor deliver/refund as observations, and no key may be both an action and an observation.

Declaration

Keys are namespaced on the wire (srp:give_map) and local in RunAction (give_map). Everything is re-declared on every capability report, so a changed or removed action takes effect on the next resource start; an NPC that had a removed action enabled simply stops being offered it.

How the gate works

  • A player saying the amulet was handed over is talk. Only the observation your script reports counts, and only for the player the NPC is answering.
  • While a condition is unmet the action stays listed as something the NPC can do, but the tag is not accepted this turn and the NPC is told to say what has to happen first. Nothing reaches RunAction.
  • The check runs again when the model writes the tag, so a fact that expired while it was answering does not slip through.
  • A delivered deed is written into the NPC’s transcript as a world event naming the facts it spent, so the NPC remembers doing it and every consume condition — and the shop counter — knows those facts are gone.
  • RunAction returning false marks the invocation rejected; the model may write the tag again on its next reply (the per-action cooldown is released), and that is a new deed with a new invocation id. Only a lost answer is retried under the same id, by HumaLike, at once. Prefer expressing state as observations over refusing at run time: by then the NPC has spoken.
  • The NPC’s gate reads its recent conversation plus, beside it, the newest of its server-namespaced world events (your reported facts, its performed deeds, the counter’s records) however old they are, so a busy NPC forgets neither an unpaid order nor a spent fact because a conversation scrolled past it. Money on the counter does not expire: an unspent payment is refundable until a settlement spends it, and [srp:cancel_order] stays open while any unspent payment exists; only an order ages out (after an hour).

Debugging

  • A refused declaration is printed by RegisterProvider in your console with the field at fault.
  • The dashboard’s NPC page lists the server’s declared actions with their conditions; the transcript view shows the observations and the deeds.
  • With humalike_debug 1, the resource logs every inbound push (inbound push: npc=… action=srp:give_map) and its result.

Next