Custom Labs

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 consumpt

How it works

The runner has two sides that mirror each other:

Side
File
Responsibility

Client

client/lab_process_runner.lua

lib.points, markers, HelpText, progress bars, animations, lifecycle events

Server

server/lab_process_runner.lua

Callback 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:

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

Minimum viable lab

1. Create the config file

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

3. Register on the client

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:

5. Add to fxmanifest.lua

6. Add locale keys

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


Full config reference

Stages array

Each entry defines one production step:

Field
Type
Required
Description

id

string

โœ…

Unique stage identifier. Used in Coords, Timers, Markers, Animations, and Recipe keys

label_key

string

โœ…

Locale suffix for the progress bar: notify.{labType}_{label_key}

help_key

string

โ€”

Locale suffix for HelpText: notify.{labType}_help_{help_key}. Defaults to label_key

done_key

string

โ€”

Locale suffix for stage-complete notification. Defaults to {label_key}_done

timer

number

โ€”

Stage timer in ms (informational โ€” the runner uses Timers[id] at runtime)

Recipe table

Indexed by stage_id. Each entry supports:

Field
Type
Description

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:

Animations table

Indexed by stage_id. Two modes:

Simple (TaskPlayAnim):

Networked (NetworkCreateSynchronisedScene) โ€” with props:

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

MaxDistPerStage

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


Upgrade modifiers

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

Modifier
Effect
Stacking

process_time_mult

Multiplies 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:

Step
Action
Items

0 โ€” Plants

Pick 14 plants (respawn after PlantRespawnTime s)

โ†’ wetcannabis ร—1 each

1 โ€” Drying

Place wetcannabis in one of 4 independent dry spots

wetcannabis โ†’ drycannabis after DryWait ms

2 โ€” Grind

Use the grinder (requires weedgrinder, not consumed)

drycannabis โ†’ grindedweed

3 โ€” Bag

Use the bagger

grindedweed + bluntwrap โ†’ blunt

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.

Last updated