Skip to content

[Memhack] Events and Hooks

DasKeifer edited this page Sep 30, 2026 · 6 revisions

Events and Hooks

memhack fires events when game state changes. These events automatically track state and only fire when values actually change. For each event, there is a corresponding hook function you can use to subscribe to the event.

Memhack Implemented Events/Hooks

Events/Hooks Implementation Details

Changes Table Format

The changes table is a common argument for these triggers and will only contain fields that actually changed. Each entry has this structure:

changes = {
    fieldName = {
        old = previousValue,
        new = currentValue
    },
    -- ... other changed fields
}

Automatic State Tracking

memhack automatically tracks struct state and fires events when setters are called

Important: Events only fire when values actually change. Setting a field to its current value will not fire an event.

Fires Via Setters

Events fire automatically when you modify struct data through setters. Each setter will fire a single event so the following code would fire the event three times

-- This will fire pilotChanged event
pilot:setLevel(2)
pilot:setXp(50)
pilot:setName("New Name")

To avoid this, assuming its unwanted behavior, you should use the set function which allows setting multiple values at a time triggering the hook only once

Fires Automatically on Change Detected

Memhack will also track and detect changes done outside of the setters. This will fire on specific other events when a change is detected. This for catching vanilla changes (like leveling up via xp gain) and should not be relied upon by mods. Mods should be using the setters that fire instantly instead of waiting for the change to be detected

Reentrancy Protection

The event system includes reentrancy protection to prevent infinite loops:

memhack:addPilotChangedHook(function(pilot, changes)
    -- This is safe - won't cause infinite loop
    if changes.xp then
        pilot:setLevel(2)  -- Can modify pilot in hook
    end
end)

Note: There is a maximum iteration limit to prevent infinite loops. If hooks modify state in a circular way, an error will be raised after too many iterations.

Priorities

Lower priority values run first.

Constant Value Role
memhack.hooks.INTERNAL_PRIORITY 0 Used by memhack for event dispatch wrappers
memhack.hooks.DEFAULT_PRIORITY 100 Default when priority is omitted

Optional second argument on both registration paths:

memhack:addPilotChangedHook(function(pilot, changes)
    -- ...
end, 50)  -- runs before DEFAULT_PRIORITY (100)

memhack.events.onPilotChanged:subscribe(function(pilot, changes)
    -- ...
end, 50)

If priority is omitted, DEFAULT_PRIORITY (100) is used. Priority must be a number.

Events vs Hooks

Each trigger can be used as a event or a hook

  1. Event Object - memhack.events.on<EventName>:subscribe(callback) - ModLoader Event subscription pattern that is never cleared
  2. Hook Function - memhack:add<EventName>Hook(callback) - Hook registration cleared each time mods are loaded

Memhack Triggers

pilotChanged

Event: memhack.events.onPilotChanged:subscribe(callback)
Hook: memhack:addPilotChangedHook(callback)

This event is fired when any tracked field of a Pilot struct changes. The event automatically tracks pilot state and only fires when values actually change.

Event Arguments

Argument name Type Description
pilot Pilot The pilot struct that changed
changes table Map of field names to change tables with old and new values

Tracked Fields

The following Pilot fields are tracked for changes:

Field Type Description
name ItBString Pilot's display name
xp int Current experience points
levelUpXp int XP required for next level
level int Current pilot level (0-2)
skill ItBString Pilot's unique skill ID
id ItBString Pilot's unique identifier
prevTimelines int Number of previous timelines

Using the Event

-- Register with hook function
memhack.events.onPilotChanged:subscribe(function(pilot, changes)
    if changes.level then
        LOG(pilot:getName() .. " changed level from " .. 
            changes.level.old .. " to " .. changes.level.new)
    end
    
    if changes.xp then
        LOG("XP changed from " .. changes.xp.old .. " to " .. changes.xp.new)
    end
    ...
end)

pilotLvlUpSkillChanged

Event: memhack.events.onPilotLvlUpSkillChanged:subscribe(callback)
Hook: memhack:addPilotLvlUpSkillChangedHook(callback)

This event is fired when any tracked field of a PilotLvlUpSkill struct changes. The event automatically tracks skill state and only fires when values actually change.

Event Arguments

Argument name Type Description
pilot Pilot The pilot struct that owns this skill (may be nil if skill accessed directly)
skill PilotLvlUpSkill The skill struct that changed
changes table Map of field names to change tables with old and new values

Note: The parent pilot is automatically prepended as the first argument for convenience. It will be nil if the skill was accessed directly via memory address without going through a pilot.

Tracked Fields

The following PilotLvlUpSkill fields are tracked for changes:

Field Type Description
id ItBString Skill ID determining effect
shortName ItBString Short name for UI display
fullName ItBString Full name shown on hover
description ItBString Description shown on hover
healthBonus int Health bonus provided
coresBonus int Reactor cores bonus
gridBonus int Grid power bonus
moveBonus int Movement bonus
saveVal int Save file value (0-13 for vanilla)

Using the Event

-- Register with hook function (cleared on mod reload)
memhack:addPilotLvlUpSkillChangedHook(function(pilot, skill, changes)
    -- pilot may be nil if skill accessed directly
    local pilotName = pilot and pilot:getName() or "<unknown>"
    
    if changes.id then
        LOG(pilotName .. "'s skill changed from " .. 
            changes.id.old .. " to " .. changes.id.new)
    end
end)

-- Or persistent event subscription:
memhack.events.onPilotLvlUpSkillChanged:subscribe(function(pilot, skill, changes)
    -- same callback shape
end)

Clone this wiki locally