Skip to content

Authoring Lua Init

EmoTracker Community edited this page Apr 8, 2026 · 1 revision

Authoring Lua — init.lua

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.

What init.lua is responsible for

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:

  1. Determine the active variant (if your pack has variants).
  2. Load any utility scripts the rest of init.lua depends on.
  3. Register items so codes resolve when locations and layouts are loaded.
  4. Register maps so layouts can reference them.
  5. Register locations, which look up items by code as they parse.
  6. Register layouts, which reference items, maps, and other layouts by name.
  7. Register custom logic functions (define them as globals so the $ prefix can find them).
  8. Optionally start autotracking by calling into the autotracker API and defining the standard autotracker_started callback.

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.

A minimal init.lua

-- 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.

Loading other Lua files

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* calls

The standard Lua require(...) is not what you want here — EmoTracker doesn't set up Lua's package path to your pack root. Use ScriptHost:LoadScript instead.

Detecting the active variant

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")
end

The 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.

Defining custom logic functions

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
end

See Authoring Lua — Custom Access Rules for the full custom-logic contract.

Setting up callbacks

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
end

See Authoring Lua — Standard Callbacks for the full list.

A more complete init.lua

-- 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")

Tips and pitfalls

  • 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.lua from 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.json and load those before the main tracker.json.
  • Don't try to use require. EmoTracker doesn't configure Lua's package path. Use ScriptHost: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 local for repeated property reads. Tracker.ActiveVariantUID is a property accessor, so caching it in a local is both faster and more readable.

See also

Clone this wiki locally