-
Notifications
You must be signed in to change notification settings - Fork 8
Authoring Lua Init
Every pack that ships any Lua at all has a single entry-point script at scripts/init.lua (relative to the pack root). When EmoTracker loads your pack, it constructs the Lua sandbox, injects the global objects, and then runs your init.lua exactly once. From there it's your script's job to register everything the pack provides.
See Authoring Lua Scripts for the broader scripting overview, sandbox rules, and global object table.
The pure-data parts of a pack — items, locations, layouts, maps — don't load themselves. The runtime exposes Tracker:AddItems(...), Tracker:AddLocations(...), Tracker:AddLayouts(...), and Tracker:AddMaps(...) functions, and your script calls them in the order you choose.
A typical init.lua does roughly this:
- Determine the active variant (if your pack has variants).
- Load any utility scripts the rest of init.lua depends on.
- Register items so codes resolve when locations and layouts are loaded.
- Register maps so layouts can reference them.
- Register locations, which look up items by code as they parse.
- Register layouts, which reference items, maps, and other layouts by name.
-
Register custom logic functions (define them as globals so the
$prefix can find them). -
Optionally start autotracking by calling into the autotracker API and defining the standard
autotracker_startedcallback.
The order matters: many fields in items, locations, and layouts are resolved at parse time, so anything they reference must already exist by the time the parser sees them.
-- scripts/init.lua
-- 1. Items first, so locations and layouts can resolve their codes
Tracker:AddItems("items/common.json")
Tracker:AddItems("items/keys.json")
-- 2. Maps before any layout that references them
Tracker:AddMaps("maps/maps.json")
-- 3. Locations after items and maps
Tracker:AddLocations("locations/dungeons.json")
Tracker:AddLocations("locations/overworld.json")
-- 4. Layouts last
Tracker:AddLayouts("layouts/popups.json")
Tracker:AddLayouts("layouts/tracker.json")That's a complete init.lua for a pack that has no scripted logic and no autotracking — just JSON loading.
To split your script across multiple files, use ScriptHost:LoadScript("path/to/file.lua"). The path is pack-relative and the script is executed in the same Lua state, so anything it defines as a global is visible to the rest of your code.
-- scripts/init.lua
ScriptHost:LoadScript("scripts/logic.lua") -- defines logic functions used by access rules
ScriptHost:LoadScript("scripts/autotracker.lua") -- sets up the autotracker_started callback
-- ...then the usual Tracker:Add* callsThe standard Lua
require(...)is not what you want here — EmoTracker doesn't set up Lua's package path to your pack root. UseScriptHost:LoadScriptinstead.
If your pack ships multiple variants (defined in manifest.json), init.lua runs the same way for every variant. To branch on which one is currently loaded, check Tracker.ActiveVariantUID:
local variant = Tracker.ActiveVariantUID
if variant == "item_tracker" then
Tracker:AddLayouts("layouts/items_only.json")
elseif variant == "map_tracker" then
Tracker:AddLayouts("layouts/items_and_map.json")
elseif variant == "keysanity_map_tracker" then
Tracker:AddLayouts("layouts/items_and_keysanity_map.json")
endThe variant UIDs are the same strings you declared in your pack's manifest. Use a local for Tracker.ActiveVariantUID rather than calling it repeatedly — it's a property accessor each time.
If your access rules use the $ prefix to call Lua functions, those functions need to be defined as Lua globals before the locations file that references them is loaded. The cleanest pattern is to define them before calling Tracker:AddLocations(...):
-- scripts/init.lua
ScriptHost:LoadScript("scripts/logic.lua") -- defines `can_reach_dam`, `has_n_keys`, etc.
Tracker:AddItems("items/common.json")
Tracker:AddLocations("locations/overworld.json") -- access_rules can now use $can_reach_dam-- scripts/logic.lua
function can_reach_dam()
if has("flippers") then
return 1, AccessibilityLevel.Normal
end
if has("moonpearl") then
return 1, AccessibilityLevel.SequenceBreak
end
return 0
endSee Authoring Lua — Custom Access Rules for the full custom-logic contract.
EmoTracker calls a fixed set of well-known function names when major events happen (pack ready, accessibility refreshed, save loaded, autotracker connected). You implement the ones you need by defining a global function with the matching name anywhere in your script — typically in init.lua itself or in a file it loads.
function tracker_on_pack_ready()
print("Pack is ready")
end
function autotracker_started()
-- Set up memory watches now that the autotracker is live
endSee Authoring Lua — Standard Callbacks for the full list.
-- scripts/init.lua
-- Choose layouts based on the active variant
local variant = Tracker.ActiveVariantUID
-- Load shared logic before any locations that reference it
ScriptHost:LoadScript("scripts/logic.lua")
-- Items first, then maps, then locations, then layouts
Tracker:AddItems("items/common.json")
Tracker:AddItems("items/keys.json")
Tracker:AddItems("items/dungeon_prizes.json")
Tracker:AddMaps("maps/maps.json")
Tracker:AddLocations("locations/dungeons.json")
Tracker:AddLocations("locations/overworld.json")
if variant == "item_tracker" then
Tracker:AddLayouts("layouts/items_only.json")
else
Tracker:AddLayouts("layouts/popups.json")
Tracker:AddLayouts("layouts/tracker.json")
end
-- Set up autotracking last (it doesn't have to be in init.lua, but a single
-- file is easier to read for a first-time pack)
ScriptHost:LoadScript("scripts/autotracker.lua")
print("Pack init complete")-
There's no automatic re-run on pack reload. When the user reloads the pack from the gear menu, the runtime tears the entire Lua state down and runs
init.luafrom scratch. Don't rely on state surviving across reloads. -
Items must be loaded before locations and layouts. Locations look up items by code at parse time (
gate_item,hosted_item); layouts look up items by code at parse time (item,itemgrid). If items aren't loaded yet, those references silently become null. -
Maps must be loaded before any
map-type layout element. Same reason. -
Layouts referenced by other layouts (or by button popups) must be loaded first. Split into a
popups.json/panels.jsonand load those before the maintracker.json. -
Don't try to use
require. EmoTracker doesn't configure Lua's package path. UseScriptHost:LoadScript("scripts/foo.lua"). - Don't print huge amounts at startup. The script output buffer is capped at 500 lines; spamming it pushes useful messages out.
-
Use
localfor repeated property reads.Tracker.ActiveVariantUIDis a property accessor, so caching it in a local is both faster and more readable.
- Authoring Lua Scripts — top-level overview of the Lua sandbox and globals
- Authoring Lua — Standard Callbacks — the well-known function names the runtime calls
-
Authoring Lua — Custom Access Rules —
$-prefixed Lua functions used inaccess_rules - Authoring Lua — Autotracking — adding memory-watch-driven updates
-
Authoring Lua — API Reference — full reference for
Tracker,ScriptHost, etc.
- Installation
- Installing and Loading Packages
- Item Types and Mouse Controls
- Map Locations
- Map Location Colors
- Saving and Loading
- Multi-Tab and Window
- Autotracking
- NDI Broadcasting
- Twitch Chat HUD
- Note Taking
- Voice Control
- Keyboard Shortcuts