OrigenNetwork
Docs

Hooks · origen_ilegalv2

The runner exposes a set of hooks that let you inject custom logic into the lab lifecycle without modifying core files. All hooks are optional — pass them as the third argument to LabProcessRunner.Register() on the client side, or use the standalone server-side functions described below.


Client hooks

Hooks are passed as a table when registering the lab:

luaclient/lab_heroin.lua
LabProcessRunner.Register('heroin', Config.LabHeroin, {
    onEnter         = function(labType) ... end,
    onExit          = function(labType) ... end,
    onReset         = function() ... end,
    onStageComplete = function(stageId, result) ... end,
})

onEnter(labType)

Called immediately after the runner creates all interaction points, every time a player enters a lab of this type.

Use cases:

  • Load custom props / IPLs specific to the lab
  • Query the server for pre-existing state (e.g. which plants were already harvested)
  • Start a custom thread that runs while inside
lua
onEnter = function(labType)
    -- Ask the server which plants are already taken
    local takenPlants = lib.callback.await('origen_gang:labs:weed:getTakenPlants', false)
    for _, idx in ipairs(takenPlants or {}) do
        RemovePlantProp(idx)  -- your custom function
    end
end,

onExit(labType)

Called after the runner removes all interaction points and resets the production state, every time a player exits a lab of this type.

Use cases:

  • Delete custom props spawned on onEnter
  • Cancel any custom threads
  • Clean up client-side state
lua
onExit = function(labType)
    DeleteCustomProps()
    StopMyThread()
end,

onReset()

Called by the runner whenever the production cycle is forcibly reset — on exit, on player unload (origen:Client:OnPlayerUnload), and on resource stop.

Use cases:

  • Detach props attached to the ped during a stage (e.g. the meth lab tray prop attached to the hand during grab_tray)
  • Clear any HUD overlays tied to a production cycle
lua
onReset = function()
    if trayPropEntity and DoesEntityExist(trayPropEntity) then
        DetachEntity(trayPropEntity, true, true)
        DeleteEntity(trayPropEntity)
        trayPropEntity = nil
    end
end,

onReset is always called before onExit. The production state (stage, runTimers, etc.) is already cleared when your hook runs.


onStageComplete(stageId, result)

Called after the runner finishes processing a stage (progress bar complete, server callback returned success, items delivered). Runs for every stage including the last.

ArgumentTypeDescription
stageIdstringThe ID of the stage that just completed
resulttableThe raw server callback response for this stage

result fields (what the server returned):

FieldPresent onDescription
successalwaystrue
timersfirst stage onlyEffective timers after upgrade modifiers
modifiersfirst stage only{ process_time_mult, output_bonus_pct }
outputslast stageFinal items delivered (with bonus applied)
bonus_triggeredlast stagetrue if the output bonus activated

Use cases:

  • Attach / detach props in sync with stage transitions
  • Trigger a custom sound or particle effect on stage complete
  • Show a custom HUD element based on remaining stages
lua
onStageComplete = function(stageId, result)
    if stageId == 'grab_tray' then
        -- Attach the tray prop to the player's hand
        AttachTrayToHand()
    elseif stageId == 'smash' then
        -- Detach and delete the tray
        DetachTrayFromHand()
    end
end,

Lifecycle events (client)

The runner listens to these resource-level events automatically for every registered lab. You can also listen to them from your own lab files for custom logic:

EventWhen it fires
origen:Lab:OnEnterPlayer is teleported inside the lab (arg: labType)
origen:Lab:OnExitPlayer exits the lab (arg: labType)
origen:Client:OnPlayerUnloadPlayer disconnects / character unloads
lua
-- Example: custom logic on any weed lab entry
AddEventHandler('origen:Lab:OnEnter', function(labType)
    if labType ~= 'weed' then return end
    print('Player entered a weed lab!')
end)

Server-side lifecycle

The server has no hook table parameter — instead it exposes functions and exports you can call from other scripts:

LabProcessRunner.ResetPlayerState(labType, source)

Force-resets the production state of a single player for a specific lab type. Use this when a script interrupts production externally (e.g. a raid eject).

lua
-- From another resource via export
exports['origen_ilegalv2']:MethLabResetPlayerState(source)
 
-- From inside origen_ilegalv2
LabProcessRunner.ResetPlayerState('meth', source)

LabProcessRunner.ResetAllForSource(source)

Clears production state for a player across all registered lab types. Called automatically on exit and playerDropped — use it if you eject a player programmatically.

lua
LabProcessRunner.ResetAllForSource(source)

LabProcessRunner.GetPlayerState(labType, source)table | nil

Returns the current in-memory production state for a player in a specific lab type:

lua
local state = LabProcessRunner.GetPlayerState('meth', source)
-- state = { stage, lab_id, timers, modifiers, last_done }
-- nil if not in production
FieldDescription
stageCurrent stage ID, or nil if between cycles
lab_idDB ID of the lab instance the player is in
timersEffective timers (post-modifier) computed at cycle start
modifiers{ process_time_mult, output_bonus_pct }
last_doneos.time() of when the last cycle completed (used for cooldown)

exports('GetPlayerLabState', ...)table | nil

Returns which lab a player is currently inside (set by the enter/exit callbacks in server/labs.lua):

lua
local labState = exports['origen_ilegalv2']:GetPlayerLabState(source)
-- { lab_id, lab_type, bucket, world_exit }
-- nil if the player is not inside any lab

This is what all lab server files use to validate "is this player actually in the right lab?".


Per-lab reset exports

Each built-in lab registers its own named reset export for external compatibility:

ExportLab
MethLabResetPlayerState(source)Meth
WeedLabResetPlayerState(source)Weed
CokeLabResetPlayerState(source)Coke

Custom labs you add should follow the same pattern:

luaserver/lab_heroin.lua
exports('HeroinLabResetPlayerState', function(source)
    LabProcessRunner.ResetPlayerState('heroin', source)
end)

Full hook example — meth tray prop

The meth lab uses onStageComplete and onReset to attach a physical tray prop to the player's hand between stages grab_tray and smash:

luaclient/lab_meth.lua (simplified)
local trayProp = nil
 
local function attachTray(ped)
    local hash = GetHashKey('bkr_prop_meth_tray_02a')
    RequestModel(hash)
    while not HasModelLoaded(hash) do Wait(50) end
    trayProp = CreateObject(hash, 0, 0, 0, true, true, true)
    AttachEntityToEntity(trayProp, ped, GetPedBoneIndex(ped, 28422),
        0.01, -0.2, -0.2, 20.0, 0.0, 0.0, true, true, false, true, 1, true)
    SetModelAsNoLongerNeeded(hash)
end
 
local function detachTray()
    if trayProp and DoesEntityExist(trayProp) then
        DetachEntity(trayProp, true, true)
        DeleteEntity(trayProp)
        trayProp = nil
    end
end
 
LabProcessRunner.Register('meth', Config.LabMeth, {
    onStageComplete = function(stageId, result)
        if stageId == 'grab_tray' then
            attachTray(PlayerPedId())
        elseif stageId == 'smash' then
            detachTray()
        end
    end,
    onReset = function()
        detachTray()
    end,
})