OrigenNetwork
Docs

Custom Labs · origen_ilegalv2

origen_ilegalv2 ships a generic Lab Process Runner that handles all the boilerplate for a production lab: interaction points, markers, progress bars, animations, server-side validation, item consumption, and upgrade modifiers. To add a new drug lab you only need to write a config table and call one function.


How it works

The runner has two sides that mirror each other:

SideFileResponsibility
Clientclient/lab_process_runner.lualib.points, markers, HelpText, progress bars, animations, lifecycle events
Serverserver/lab_process_runner.luaCallback validation, item checks, item consumption/delivery, upgrade modifiers, cooldowns

Both sides register under the same lab type key and communicate via auto-named ox_lib callbacks:

text
origen_gang:labs:{labType}:stage_{stageId}

Minimum viable lab

1. Create the config file

luaconfig/lab_heroin.lua
Config.LabHeroin = {
    Enabled = true,
 
    -- Ordered production stages
    Stages = {
        { id = 'mix',      label_key = 'mixing',   help_key = 'mix',  done_key = 'mix_done',    timer = 10000 },
        { id = 'filter',   label_key = 'filtering', help_key = 'filter', done_key = 'filter_done', timer = 8000  },
        { id = 'bag',      label_key = 'bagging',  help_key = 'bag',  done_key = 'bag_done',    timer = 6000  },
    },
 
    ProductionCooldown = 600000,  -- 10 minutes between full cycles (ms)
 
    Interactions = {
        PointDistance    = 40.0,   -- lib.points render radius
        DrawDistance     = 8.0,    -- marker draw distance
        InteractDistance = 1.8,    -- HelpText / [E] distance
    },
 
    -- World coords for each stage (interior coords inside the lab bucket)
    Coords = {
        mix    = vector3(1006.09, -3200.59, -38.52),
        filter = vector3(1007.89, -3201.17, -38.99),
        bag    = vector3(1014.25, -3194.93, -38.99),
    },
 
    -- Progress bar durations per stage (ms)
    Timers = {
        mix    = 10000,
        filter = 8000,
        bag    = 6000,
    },
 
    -- Items consumed/delivered per stage
    -- inputs:   consumed when the stage starts
    -- outputs:  delivered when the stage completes
    -- requires: validated but NOT consumed (tools)
    Recipe = {
        mix = {
            inputs = {
                { item = 'opium_raw',  amount = 2 },
                { item = 'acetic_acid', amount = 1 },
            },
        },
        bag = {
            inputs  = { { item = 'empty_bag', amount = 3 } },
            outputs = { { item = 'heroin_bag', amount = 3 } },
        },
    },
}

Stages order is the mandatory execution order. The runner enforces it — a player cannot jump to stage N+1 without completing stage N first.

2. Register on the server

luaserver/lab_heroin.lua
if not Config.LabHeroin or not Config.LabHeroin.Enabled then return end
 
LabProcessRunner.Register('heroin', Config.LabHeroin)
 
-- Optional: expose a reset export for external resources
exports('HeroinLabResetPlayerState', function(source)
    LabProcessRunner.ResetPlayerState('heroin', source)
end)

3. Register on the client

luaclient/lab_heroin.lua
if not Config.LabHeroin or not Config.LabHeroin.Enabled then return end
 
LabProcessRunner.Register('heroin', Config.LabHeroin)

4. Add the lab type to Config.LabTypes

The runner does not create the lab in the DB — you need to register a lab type so the admin panel can create instances of it:

luaconfig/labs.lua
Config.LabTypes = {
    -- ...existing types...
    heroin = {
        label = 'Heroin Lab',
        interior = {
            spawn  = vector4(997.17, -3200.64, -37.39, 270.0),
            laptop = vector4(1002.01, -3194.89, -38.99, 356.98),
        }
    },
}

5. Add to fxmanifest.lua

luafxmanifest.lua
shared_scripts {
    -- ...
    'config/lab_heroin.lua',
}
 
server_scripts {
    -- ...
    'server/lab_heroin.lua',
}
 
client_scripts {
    -- ...
    'client/lab_heroin.lua',
}

6. Add locale keys

For each stage you need three locale keys (in locales/translations/en.lua):

lua
-- Progress bar label
['notify.heroin_mixing']      = 'Mixing...',
['notify.heroin_filtering']   = 'Filtering...',
['notify.heroin_bagging']     = 'Packaging...',
 
-- HelpText (shown when in range)
['notify.heroin_help_mix']    = 'Press [E] to mix chemicals',
['notify.heroin_help_filter'] = 'Press [E] to filter the mixture',
['notify.heroin_help_bag']    = 'Press [E] to bag the product',
 
-- Stage-done notifications
['notify.heroin_mix_done']    = 'Mixture ready',
['notify.heroin_filter_done'] = 'Filtration complete',
['notify.heroin_bag_done']    = 'Product packaged',  -- last stage receives output amount as arg
 
-- Error messages used by the runner
['notify.heroin_not_in_lab']             = 'You are not inside the lab',
['notify.heroin_wrong_lab_type']         = 'Wrong lab type',
['notify.heroin_on_cooldown']            = 'Wait before starting another cycle',
['notify.heroin_already_in_progress']    = 'Production already in progress',
['notify.heroin_wrong_stage']            = 'Complete the previous step first',
['notify.heroin_requires_basic_upgrade'] = 'Upgrade the lab before processing',

Full config reference

Stages array

Each entry defines one production step:

FieldTypeRequiredDescription
idstringUnique stage identifier. Used in Coords, Timers, Markers, Animations, and Recipe keys
label_keystringLocale suffix for the progress bar: notify.{labType}_{label_key}
help_keystringLocale suffix for HelpText: notify.{labType}_help_{help_key}. Defaults to label_key
done_keystringLocale suffix for stage-complete notification. Defaults to {label_key}_done
timernumberStage timer in ms (informational — the runner uses Timers[id] at runtime)

Recipe table

Indexed by stage_id. Each entry supports:

FieldTypeDescription
inputs{ item, amount }[]Consumed when the stage starts
outputs{ item, amount }[]Delivered when the stage completes
requires{ item, amount }[]Validated but NOT consumed (tools, licenses)

Only the stages that consume or produce items need an entry. Stages without a Recipe entry are "free" transitions.

Markers table

Indexed by stage_id. All fields are optional — the runner falls back to a default marker:

lua
Markers = {
    mix = {
        type          = 22,
        offset        = { x = 0.0, y = 0.0, z = 0.0 },
        scale         = { x = 0.25, y = 0.25, z = 0.25 },
        color         = { r = 0, g = 0, b = 0, a = 180 },
        bobUpAndDown  = true,
        faceCamera    = true,
        rotationOrder = 2,
        rotate        = false,
    },
},

Animations table

Indexed by stage_id. Two modes:

Simple (TaskPlayAnim):

lua
mix = {
    networked = false,
    dict      = 'anim@amb@business@meth@meth_monitoring_cooking@',
    clip      = 'idle_a',
    flag      = 49,
    blendIn   = 4.0,
    blendOut  = -8.0,
    freeze    = false,
},

Networked (NetworkCreateSynchronisedScene) — with props:

lua
cook = {
    networked = true,
    dict      = 'anim@amb@business@meth@meth_monitoring_cooking@cooking@',
    clip      = 'chemical_pour_short_cooker',
    flag      = 49,
    blendIn   = 1.5,
    blendOut  = -4.0,
    freeze    = true,
    offset    = vector3(4.79, 2.13, -0.41),
    rotation  = vector3(0.0, 0.0, 0.0),
    objects   = {
        { hash = 'bkr_prop_meth_sacid',   clip = 'chemical_pour_short_sacid'   },
        { hash = 'bkr_prop_meth_ammonia', clip = 'chemical_pour_short_ammonia' },
    },
},

RequiresUpgrade flag

By default, the first stage of any runner-based lab requires a style module upgrade to be unlocked in that lab instance. To disable this gate (useful while testing or for free-access labs):

lua
Config.LabHeroin = {
    RequiresUpgrade = false,  -- skip the upgrade gate
    -- ...
}

MaxDistPerStage

Global or per-stage maximum distance (meters) for server-side interaction validation:

lua
-- Global: same distance for every stage
MaxDistPerStage = 4.0,
 
-- Per-stage override
MaxDistPerStage = {
    mix    = 3.0,
    filter = 5.0,
    bag    = 3.0,
},

Upgrade modifiers

When a lab has style module upgrades unlocked (from origen_gang_lab_upgrade_unlocks), the runner automatically applies:

ModifierEffectStacking
process_time_multMultiplies all stage timers (min 0.70 — max 30% reduction)Multiplicative
output_bonus_pct% chance to give +1 unit on the last stage output (max 25%)Max of all variants

These are computed server-side at the start of each cycle and returned to the client as timers and modifiers in the first-stage callback response.


Weed lab: special flow

The weed lab (Config.LabWeed) does not use the generic runner. It has a custom flow:

StepActionItems
0 — PlantsPick 14 plants (respawn after PlantRespawnTime s)wetcannabis ×1 each
1 — DryingPlace wetcannabis in one of 4 independent dry spotswetcannabisdrycannabis after DryWait ms
2 — GrindUse the grinder (requires weedgrinder, not consumed)drycannabisgrindedweed
3 — BagUse the baggergrindedweed + bluntwrapblunt

Steps 1–3 are independent — there is no enforced sequence between them. You only need the required input item.

Dry slot timing is enforced server-side (ready_at timestamp) — the client timer is visual only.