Permissions
Reading the game needs no permission. Acting on it (walking, clicking an object, using an item, picking a menu entry) is protected. There is one key per action, declared in your manifest and approved by the user when they enable your addon. This guide is the catalogue of keys, how to ask for them, and what needs no key.
{ "permissions": ["player.move", "gob.click", "item.*"] }
The catalogue
One key per protected action, named <section>.<verb> after the section the verb lives on (pagina:use() is menugrid.use, slot:use() is actionbar.use). Where one action has two doors both are behind one key. The third column is the consent dialog’s line, word for word.
| Key |
Verb |
Lets an addon |
player.move |
session:player():move |
walk your character to a place |
player.hand.use |
session:player():hand():use |
use whatever it is holding on things |
gob.click |
session:world():click |
click objects in the world |
item.use |
item:use |
use items |
item.take |
item:take |
pick items up onto the cursor |
item.drop |
item:drop |
drop items |
item.transfer |
item:transfer |
move items between containers |
world.place |
session:world():place |
place buildings and objects |
world.select |
session:world():select |
select an area of the ground |
map.marker |
hafen.map():marker():add, :remove, marker:color, :onMap |
add and delete pins on your map, and recolour them |
menugrid.use |
pagina:use |
invoke entries of the action menu, on any of your characters |
flowermenu.select |
session:flowermenu():select |
choose from the radial menu of any of your characters |
flowermenu.cancel |
session:flowermenu():cancel |
dismiss the radial menu of any of your characters |
craft.make |
session:craft():make |
press the Craft button, on any of your characters |
actionbar.use |
slot:use |
press the action-bar buttons of any of your characters |
actionbar.res |
slot:res |
assign one of the game’s own actions to any of your characters’ action-bar buttons |
actionbar.clear |
slot:clear |
empty any of your characters’ action-bar buttons |
fight.use |
action:use |
use combat actions, in the fights of any of your characters |
fight.set |
session:fight():opponent():set |
switch the target, in the fights of any of your characters |
fight.pursue |
session:fight():pursue |
press Pursue beside an opponent’s portrait, in the fights of any of your characters |
fight.give |
session:fight():give |
click the give button beside an opponent’s portrait, in the fights of any of your characters |
fight.load |
school:load |
load a saved combat school, on any of your characters |
fight.save |
school:save |
save the combat-school layout into a slot, on any of your characters |
kin.add |
session:kin():add |
add someone to any of your characters’ kin lists |
kin.rename |
kin:rename |
rename someone on any of your characters’ kin lists |
kin.group |
kin:group |
change someone’s kin group, on any of your characters |
kin.end |
kin:endKin |
end kinship with someone, on any of your characters |
kin.forget |
kin:forget |
forget someone from any of your characters’ kin lists |
chat.send |
channel:send |
say a line in the chat, as any of your characters |
speed.set |
session:speed():set |
change the movement speed of any of your characters |
session.close |
session:close, hafen.session():remove |
log out any of your characters |
session.add |
hafen.session():add(user) |
log in any of the accounts you told the login screen to remember |
session.forget |
hafen.session():forget(user) |
forget any of the accounts you told the login screen to remember, so it asks for their password again |
widget.send |
widget:send, event:resend, event:send |
send any message the client itself could send |
widget.value |
widget:value |
flip the client’s own controls — a box it ticks, a field it types into — which the server sees |
ui.resend |
event:resend on a control |
re-run a button you pressed, so the client sends what that press sends |
ui.focus |
session:chat():selected(channel) |
move the keyboard into a chat entry line, so what you type next goes there |
virtual.click |
hafen.virtual():click |
click the controls it has standing in the world, which act as if you had clicked them |
client.settings |
every option write, binding:key(key), a key button bound to a binding not yours |
change your client settings and hotkeys |
console.run |
session:console():run |
run any of the client’s console commands, on any of your characters, including ones that run code outside the addon sandbox |
http.get |
request:send on a GET |
fetch data from the servers it lists |
http.post |
request:send on a POST |
send data to the servers it lists |
websocket.connect |
connection:connect |
keep a live connection to the servers it lists |
voice.connect |
voice:connect |
use your microphone to talk on the voice servers it lists |
| Rule |
Detail |
| The whole set |
Nothing else is protected, and no key grants the tier whole: an addon that declared gob.click can click objects and nothing else on the list. |
console.run is the widest |
It names a surface, the command line, so it covers every command the client dispatches. :lua is one, and it evaluates outside the sandbox. An addon holding this key can do what the user’s console can. |
| Keyed without reaching the game server |
map.marker deletes a pin no server restores. client.settings rewrites every hotkey and reaches the client’s own bindings (binding:key("Ctrl+I") on inv). http.*, websocket.connect and voice.connect reach outside the client, the last opening the microphone. Each is a control the player has in front of them. |
A key names the action, not the target
The client holds several logins, and a protected verb is addressed at one (session:kin():add adds a kin to the character session names, drawn or not). The key you declared covers every one of them. A verb is protected because it starts something the player could have performed, and the player could have tabbed to that character. Read every line of the table across the whole client. What the user approves is a capability. The character it is pointed at is your addon’s to choose. gob.click is the one key not named for the section its verb sits under. It names the action, clicking an object. The verb, session:world():click, sits on the world of the character clicking. A library’s exported function runs under the library’s own keys, whoever called it; a callback you hand a library runs under yours (addons).
Groups
A <prefix>.* entry stands for every key under that prefix.
| Group |
Covers |
item.* |
item.use, item.take, item.drop, item.transfer |
kin.* |
kin.add, kin.rename, kin.group, kin.end, kin.forget |
world.* |
world.place, world.select |
flowermenu.* |
flowermenu.select, flowermenu.cancel |
actionbar.* |
actionbar.use, actionbar.res, actionbar.clear |
fight.* |
fight.use, fight.set, fight.pursue, fight.give, fight.load, fight.save |
player.* |
player.move, player.hand.use |
player.hand.* |
player.hand.use |
widget.* |
widget.send, widget.value |
ui.* |
ui.resend, ui.focus |
http.* |
http.get, http.post |
session.* |
session.add, session.close, session.forget |
| Rule |
Detail |
| Any prefix is a group |
gob.*, menugrid.*, craft.*, speed.*, map.*, client.*, console.*, chat.*, virtual.*, websocket.* and voice.* parse, each a longer spelling of its single key. |
| Whole dot segments |
A group never reaches a key that merely starts with the same letters, and does reach a nested one. player.hand.use is the only nested key, covered by player.* and alone by player.hand.*. |
| The unit the user reads |
The dialog prints one line per entry as written (item.* is one line naming its verbs), and the panel row counts entries the same way. |
No "*" |
An entry that is neither a key nor a group (a typo, an invented key, a bare asterisk) is a load error. The addon does not run. The panel row names the entry and lists the vocabulary. |
| Tolerated |
"" or a string of spaces is dropped. Entries are read trimmed (" item.take " is that key). A key declared twice is one grant, printed once. |
Declaring it
| Step |
Detail |
| 1. The manifest declares a key or a group for every protected verb you call |
"permissions": ["player.move", "gob.click", "item.*"]. |
| 2. The user enables the addon |
An addon that declares anything here is disabled the first time the client sees it, unless the client installed it from its own release, which grants what that copy declares (addons the client ships). Enabling it in the AddOns manager raises a consent dialog listing exactly the entries you wrote, one line each. The tooltip of its name lists the entries, and Enable all skips it. |
| Rule |
Detail |
| Asking for more re-asks |
The approved keys and the hosts shown beside them are remembered per addon. A version adding either is disabled and prompts again with the addition marked new. A widened group counts as added, and so does a host no approved entry covers (b.example.com re-asks, narrowing *.example.com to a.example.com does not). Dropping a key or a host never re-prompts. |
| An undeclared key |
A protected verb called by an addon that did not declare its key raises naming the verb, the key and the manifest line to paste. |
| The gate asks the record, not the manifest |
What the user approved for your addon: a key added to manifest.json after consent is refused at the call until the addon is enabled again. Both halves must hold: a key no longer declared is no longer granted. |
| The gate asks who is running |
A protected verb reads the manifest of the addon whose code is on the stack. Handing another addon an event, a Widget or a Session hands it nothing. |
| The record has a fixed size |
A declaration large enough to overflow it (a network block of hundreds of hosts) cannot be recorded. Approving it is refused naming your addon and the limit, and the addon stays disabled until the declaration is smaller. |
What is not protected
Everything else writes to your own client and needs no key.
| Unprotected write |
Changes |
session:menugrid():add |
An entry of your own in a character’s action menu. |
slot:hold(pagina) |
Which of your entries the client draws over a bar slot. The server’s content stays and comes back untouched. |
category:show(flag) |
Which icons your minimap draws. |
session:chat():selected(channel) |
Which tab a character’s chat has on screen. The keyboard moves with it only for ui.focus. |
widget:position, :size, :visible, :draggable, :resizable, :remember |
Where the client’s own windows sit and how big they are, whether the user can drag or size one, whether that lasts. |
widget:replace(view) |
Which window a client toggle opens. |
hafen.ui():sheet() |
What the client looks like. |
hafen.virtual |
Props only you can see. Every verb but :click, which is virtual.click. |
hafen.sound |
What you hear. |
| Option reads |
Nothing. Every client option write is client.settings. |
option:value(value), :add(), options:panel(fn) |
Your own options and page. |
| Rule |
Detail |
| The line |
Whether the user would have to undo it by hand. Everything above is a display choice changed back in a click, or something only your addon sees. A deleted pin, a rebound hotkey, a request to another host and a connection kept to one are not. |
| A menu entry of yours |
Naming it, drawing it, putting it on the bar and running your function on a click never reach past your client. pagina:use() keeps menugrid.use whichever entry it names. |
Re-issuing an action needs widget.send |
event:resend() and event:send(table) put a message on the wire widget:send uses, with arbitrary arguments, as often as a handler loops. event:preventDefault() stays open: cancelling reaches nothing. |
Re-running a control’s action needs ui.resend |
event:resend() on a client control runs its method, its own wdgmsg. Narrower than widget.send: it re-runs the press the user made, once per event. A second event:resend() on the same event raises. |
| Two keys, one act |
kin.end and kin.forget are one message on the wire, resolved by the entry’s state. An addon granted only kin.end can endKin() an un-kinned entry and forget it. One granted only kin.forget can end a kinship. Granting either grants the pair. |
Writing an addon that acts
hafen.console():on("gotree", function()
local session = hafen.session():current() -- the character on screen
local tree = session:world():gob():nearest("terobjs/tree")
if tree then
session:player():move(tree:position()) -- player.move
session:world():click(tree, 3) -- gob.click, and open its radial menu
end
end)
| Habit |
Detail |
| Your manifest answers “may I act” |
A declared, running addon was granted the key: nothing to test at run time. A file shipping either way reads its own manifest.json rather than provoking the error. |
Act from SessionEnteredWorld onwards |
Every verb here needs a live map view or object and throws before one exists. A verb at a character not yet in the world says so and sends nothing. |
| Make the user ask |
Bind actions to a hotkey, a command or a setting, not a timer. An addon acting on its own at load is what a dialog cannot warn about. |
| Declare the narrowest set |
The dialog is the user’s whole view of what you do. A group asked for out of convenience reads as every capability in it. |
What the permission does not buy
| Rule |
Detail |
| The server is the authority |
An addon sends what a click could have sent and finds out what happened by watching the world. A refused action is refused server-side with nothing to catch. |
| Some of it outlives the addon |
slot:res(name) and slot:clear() write the character’s bar on the server, and school:save() the school saved in that slot: disabling, :reload and logout put nothing back. The user undoes it in the game. slot:hold(pagina) is unprotected beside them because the server’s content comes back untouched. |
Network: one key, and the hosts are its argument
http.get, http.post, websocket.connect and voice.connect are keys like any other. The network block is what they take. The key says whether, the hosts say where.
{ "permissions": ["http.get", "websocket.connect"],
"network": { "hosts": ["api.example.com"] } }
| Rule |
Detail |
| One line per key over the same hosts |
“fetch data from the servers it lists: api.example.com”. “keep a live connection to the servers it lists: api.example.com”. For a voice link, “use your microphone to talk on the voice servers it lists”. |
| Load error, refusal |
Hosts without a key is a load error naming the keys. A key with no hosts is refused at the call. An origin the user did not approve is refused before any request leaves, connection opens or microphone is taken. |
| An entry |
One scheme on one port. A wss:// address is the https server the block names. A wildcard covers one domain’s sub-domains, never a top-level domain (what an entry means). |
| The panel |
The exact hosts in the tooltip of the addon’s name. |
Next: theming — changing what the client looks like, which needs no permission.