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:
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
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
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
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.
| Argument | Type | Description |
|---|---|---|
stageId | string | The ID of the stage that just completed |
result | table | The raw server callback response for this stage |
result fields (what the server returned):
| Field | Present on | Description |
|---|---|---|
success | always | true |
timers | first stage only | Effective timers after upgrade modifiers |
modifiers | first stage only | { process_time_mult, output_bonus_pct } |
outputs | last stage | Final items delivered (with bonus applied) |
bonus_triggered | last stage | true 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
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:
| Event | When it fires |
|---|---|
origen:Lab:OnEnter | Player is teleported inside the lab (arg: labType) |
origen:Lab:OnExit | Player exits the lab (arg: labType) |
origen:Client:OnPlayerUnload | Player disconnects / character unloads |
-- 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).
-- 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.
LabProcessRunner.ResetAllForSource(source)LabProcessRunner.GetPlayerState(labType, source) → table | nil
Returns the current in-memory production state for a player in a specific lab type:
local state = LabProcessRunner.GetPlayerState('meth', source)
-- state = { stage, lab_id, timers, modifiers, last_done }
-- nil if not in production| Field | Description |
|---|---|
stage | Current stage ID, or nil if between cycles |
lab_id | DB ID of the lab instance the player is in |
timers | Effective timers (post-modifier) computed at cycle start |
modifiers | { process_time_mult, output_bonus_pct } |
last_done | os.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):
local labState = exports['origen_ilegalv2']:GetPlayerLabState(source)
-- { lab_id, lab_type, bucket, world_exit }
-- nil if the player is not inside any labThis 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:
| Export | Lab |
|---|---|
MethLabResetPlayerState(source) | Meth |
WeedLabResetPlayerState(source) | Weed |
CokeLabResetPlayerState(source) | Coke |
Custom labs you add should follow the same pattern:
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:
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,
})