Configuration
Every oxide-pills setting — how database-backed settings and the /pills settings menu work, which values live only in the config files, and each setting's menu location and default.
oxide-pills is configured in game, from a settings menu. You don't edit script files on a running server. This page explains how that works, then lists every setting so you know what each one does.
How Configuration Is Stored
oxide-pills keeps its live configuration in your database, in a shared table called oxide_settings. The shared/config.lua file and the files in shared/config/ are the factory defaults: the starting values the resource ships with. The table is created automatically on first start (you don't run any SQL by hand).
How it works:
- The first time the resource starts, the values from the config files are copied into
oxide_settings. If you edited those files before that first start, your edits are what gets copied. - From then on, the database is what the resource reads. Settings you save in the menu are stored there and win over the files. Editing a config file on a server that has already started once will not change a setting that's stored in the database.
What this means for you:
- Setting up for the very first time? You can edit the config files before the first start, and your values will be imported. The easier path is to start with the defaults and adjust everything in game.
- Changing a setting on a server that has already run the resource? Use the in-game settings menu (below).
- A resource update adds new settings? Those aren't in your database yet, so they come from the updated config file automatically on the next start. No action needed.
The in-game settings menu
As an admin, run /pills settings in game. It opens an editor for the settings. Changes apply live and are saved to the database right away, with no restart.
- Settings are grouped into a sidebar of four categories: Core, Lab, Players, and Advanced, holding 12 sections between them.
- A Basic / Advanced switch at the top of the menu picks which settings you see, and the search box searches the settings on that side. Three sections (Usage Whitelist, Quality, Dispatch) only have Advanced settings, so they only appear in the sidebar once you switch to Advanced.
- A setting you've edited but not saved yet is marked changed. Save Changes saves everything you've edited.
- Reset to default puts a setting back to its factory value from the config files and saves it straight away. It resets the whole setting: resetting Pharma delivery run, for example, also puts back the contact and the drop points.
- The in-world placement zones are drawn directly in the world: the menu hides, you place the corners of the area, then the menu comes back. Each zone also has a Teleport button.
- Item fields have a picker that lists the items your inventory knows about.
Almost every change takes effect the moment you save. The exceptions:
- Changing the workbench's model or slot count only affects workbenches placed afterwards.
- Changing a model replacement rebuilds the placed stations for everyone.
Developers can also read and write settings from another resource. See API Reference.
The setting names below (like Config.RequireOwnership) are the names in the config files. In the menu each one has a plain label instead (like "Require ownership"). The tables list both where they differ.
Settings That Aren't In The Menu
The menu covers everything most servers want to change. A few settings, mostly lists and layouts that a form can't show well, live only in the config files. They come in two kinds, and the difference matters:
1. Config file only: always read from the file. These are never stored in the database, so the config file always wins:
Config.Skill(how much each level reveals) inshared/config/profile.luaConfig.Buyers(the street buyer types) inshared/config/world.luaConfig.SettingsCommand(the command named in missing-model warnings,'/pills settings') inshared/config/models.lua
To change one: edit the file, save it, and restart the server.
2. Not in the menu, but part of a section that is. Some settings sit inside a section the menu does show, but aren't shown themselves. For example, the product line recipes are part of "Product line unlocks", and the bottle label templates are part of "Packaging". They're marked (not in menu) in the tables below. Only the values the menu shows are taken from the database; these are read from the config file on every start.
To change one: edit the file, save it, and restart the server.
One exception: when the server starts, each saved setting is checked the same way the menu checks it. If a saved setting fails that check (for example the delivery run is switched on with no drop points), it is loaded exactly as it was saved, and file edits to its (not in menu) values are ignored. Fix that setting in the menu and save it, then restart.
The Config Files
| File | What's in it | Menu location |
|---|---|---|
shared/config.lua | General settings, placement zones, usage whitelist, police alerts, batch retention | Core, Advanced → Dispatch, Lab → Production |
shared/config/equipment.lua | The lab workbench and the placeable machines | Lab → Workbench, Lab → Machines |
shared/config/production.lua | Every production stage, packaging, and the product line formulas | Lab → Production (the level for each line is under Players → Progression) |
shared/config/profile.lua | Tablet Profile tuning, and what each level reveals | Lab → Quality (the reveal table is file only) |
shared/config/scenes.lua | Stage animations and cameras | Lab → Production (Advanced tab) |
shared/config/progression.lua | XP rewards, levels and level-10 perks | Players → Progression |
shared/config/world.lua | Pills breaking, reagent tests, dust, taking pills, evidence, buyers, reputation, the delivery run | Players → World, Players → Selling (buyers are file only) |
shared/config/models.lua | Model replacements, and the command missing-model warnings point to | Advanced → Compatibility (the command is file only) |
Core
General
| Setting | Menu label | Type | Default | What it does |
|---|---|---|---|---|
Config.RequireOwnership | Require ownership | boolean | true | Only the player who placed a station can pack it up, and only the player who placed a workbench can mount machines on it. Set false to let anyone do both. |
Config.SpawnDistance | Prop spawn distance | number | 100.0 | How close (in metres) a player must be before placed stations appear for them. |
Config.AnimateDistance | Animation distance | number | 30.0 | Machine parts only move for players within this range (metres). |
Config.PlaceDistance | Placement reach | number | 4.0 | How far from the player a station can be placed (metres). |
Config.Debug | Debug mode | boolean | false | (Advanced) Prints extra [oxide-pills] lines to the console, and adds a Machine Test option on every machine (every player sees it, but only admins can use it; see Admin). Leave it off on a live server. |
Config.Anims | Shared animations | table | base-game clips | (Advanced) The animations played when placing (place) or packing up (remove) a station. Each has a dict, name and duration (ms, default 1000). Both default to weapons@first_person@aim_rng@generic@projectile@sticky_bomb@ / plant_floor. |
Placement Zones
| Setting | Menu label | Type | Default | What it does |
|---|---|---|---|---|
Config.ZoneMode | Zone mode | string | 'blacklist' | How placement zones work: 'off' (place anywhere), 'blacklist' ("Blocked areas": blocked inside SafeZones), or 'whitelist' ("Allowed areas only": blocked outside AllowedZones). |
Config.SafeZones | Blocked areas | table | {} (empty) | Areas where stations can't be placed, used in Blocked areas mode. Drawn in the world. |
Config.AllowedZones | Allowed areas | table | {} (empty) | The only areas where stations may be placed, used in Allowed areas only mode. Both lists are kept whichever mode is active. Leaving this empty in Allowed areas only mode blocks placement everywhere. |
Zones apply to workbenches and floor machines. Benchtop machines sit on a workbench, so they go wherever the workbench is.
Usage Whitelist
(Advanced tab.)
| Setting | Menu label | Type | Default | What it does |
|---|---|---|---|---|
Config.UsageWhitelist | Item usage rules | table | {} (empty) | Per-item rules: { item, jobs, gangs, minRank, dutyOnly }. Items with no rule stay usable by everyone. |
Each rule:
| Field | What it does |
|---|---|
item | The item name the rule applies to. One rule per item. |
jobs | Job names allowed to use it. With jobs and gangs both empty, any job passes (rank and duty still apply). Capital letters don't matter. |
gangs | Gang names allowed to use it. ESX has no gang system, so gang names never match on ESX. |
minRank | The minimum job or gang grade number. 0 means any rank. |
dutyOnly | Job holders must be on duty. Ignored for gangs. |
A player passes if they match on the job side or the gang side. The rules cover items used from the inventory: station items, the mortar, the sieve, the dust mask, the reagent kit, the Pressing Manual, and every pill, loose or packed.
To limit an item to gangs, fill in only gangs. With jobs empty and at least one gang listed, only members of those gangs can use the item.
Lab
Workbench
Config.Workbench (menu: Lab workbench).
| Setting | Type | Default | What it does |
|---|---|---|---|
item | string | 'pill_workbench' | The inventory item that places the workbench |
model | string | 'bkr_prop_weed_table_01b' | The table model. Any table works: slots are measured from its top surface. |
slots | number | 2 | How many benchtop machines fit along one workbench (1–4) |
facingOffset | number | 0 | Turns the workbench by this many degrees when placed |
Changing the model or slot count only affects workbenches placed afterwards. Workbenches already standing keep the model and slots they were placed with.
Machines
Config.Equipment (menu: Placeable machines). One entry per machine:
| Machine key | item | requiredLevel | Mounts on |
|---|---|---|---|
hand_press | pill_crank_press | 1 | Workbench |
mixer | pill_mixer | 1 | Workbench |
grinder | pill_grinder | 2 | Workbench |
lightbox | pill_lightbox | 3 | Workbench |
engraver | pill_engraver | 3 | Workbench |
electric_press | pill_electric_press | 5 | Workbench |
label_printer | pill_label_printer | 5 | Workbench |
hardness_tester | pill_hardness_tester | 6 | Workbench |
capsule_board | pill_capsule_board | 7 | Workbench |
ball_mill | pill_ball_mill | 7 | Workbench |
blister_sealer | pill_blister_sealer | 8 | Workbench |
reject_bin | pill_reject_bin | none | Floor |
dust_collector | pill_dust_collector | none | Floor |
drum_tumbler | pill_drum_tumbler | 6 | Floor |
pan_coater | pill_pan_coater | 7 | Floor |
rotary_press | pill_rotary_press | 9 | Floor |
item: the inventory item that places the machine. If you rename it, register the new name in your inventory too.requiredLevel: the pill level needed to place the machine and to work it, including a machine someone else placed. It is the only level a machine has. Mixing a product line also needs that line's own level (Formulas).facingOffset(not set by default): turns a floor machine by this many degrees when placed. Benchtop machines always face the way their workbench does.machineandmount(not in menu) tie the entry to its model and decide bench or floor. Don't change them.
Production
All of these are under Lab → Production in the menu. A stage worked on a machine needs that machine's level, set under Machines. Only the stages with no machine of their own have a level here: bagging at the workbench and the sieve in the menu, and the mortar in the config file only.
Milling
Config.Mill (menu: Milling).
| Setting | Type | Default | What it does |
|---|---|---|---|
minRevMs | number | 210 | Mortar turns arriving faster than this (ms) are ignored. A real hand can't beat about 200 ms, so lowering it only helps cheaters. |
maxRevs | number | 80 | The most turns one mortar session can count |
minTime | number | 3 | A grind finished sooner than this (seconds) produces nothing |
inputs | table | see below | (not in menu) The raw actives that can be milled |
output | table | { item = 'pill_milled', count = 1 } | (not in menu) What milling produces |
regrindFines | number | 25 | (not in menu) Extra fines reground Reject Tablets come back with |
regrindShare | number | 1.0 | (not in menu) How many Reject Tablets one regrind takes, as a share of the line's tabletsPerBlend (so 48 by default, 30 for capsules). Below 1.0, regrinding makes more pills than were broken. |
tools.mortar | table | see below | (not in menu) Mortar tuning |
tools.grinder | table | see below | (not in menu) Blade grinder tuning |
inputs lists party_powder, pharma_bottle, mystery_powder and pill_rejects (marked regrind = true), each with the container model the grinder shows (the stock ones use oxide_pill_powder_pouch). You can add another raw active here (for example an oxy item from another script), but it's only useful if a formula in Config.Formulas accepts it.
Mortar (tools.mortar):
| Setting | Default | What it does |
|---|---|---|
item | 'pill_mortar' | The mortar item |
requiredLevel | 1 | Level to use it |
grindPerRev | 3.4 | Grind added per turn |
finesAfterRev | 24 | Turns before fines start |
finesPerRev | 3.2 | Fines added per turn after that |
Blade grinder (tools.grinder):
| Setting | Default | What it does |
|---|---|---|
station | 'grinder' | The machine it runs on. Its level is the Blade Grinder's requiredLevel under Machines. |
grindPerSecond | 9.0 | Grind per second of running, with a clean chamber |
cakePerSecond | 14.0 | How fast powder cakes on the chamber wall while the blade runs |
cakeChoke | 0.85 | How much of the grind rate a fully caked chamber loses |
finesPerSecond | 7.0 | Fines per second with a fully caked chamber (none when clean) |
knockClears | 55.0 | Cake cleared by one knock |
knockCooldownMs | 400 | How often you can knock (ms) |
maxPulseMs | 8000 | The longest one pulse can count for (ms) |
cakeLightAt / cakeHeavyAt | 33.0 / 66.0 | When the chamber shows as caking and choked |
pouch | table | Where the powder container stands and the chamber mouth, relative to the grinder |
Blending
Config.Blend (menu: Blending).
| Setting | Type | Default | What it does |
|---|---|---|---|
runSeconds | number | 8.0 | How long the mixer runs once started |
minTime | number | 5 | A blend finished sooner than this (seconds) produces nothing |
fillBand | table | { low = 0.55, high = 0.85, falloff = 0.35 } | How full the bowl should be (as a fraction of a full bowl) to mix evenly. Outside low–high, uniformity drops to nothing over falloff. |
pourRate | number | 26.0 | Units per second for anything without its own rate (filler, dye) |
pourRates | table | { active = 14.0, binder = 5.0, lubricant = 1.0 } | Slower rates for the small ingredients, so their targets can be hit by holding rather than tapping |
dyes | table | 6 colours | The dye colours players can pick at the bowl, in order: rose, violet, cyan, lime, gold, white. Each has a key (its name comes from the locale entry dye_<key>) and a tint (the tablet colour: 0 pink, 1 blue, 2 yellow, 3 purple, 4 green, 5 ivory, 6 orange, 7 teal). |
containerUnits | number | 70.0 | How much one ingredient item can pour before it's empty |
batchUnits | number | 100.0 | A full bowl, which the gauges and the fill band are measured against |
station, output, overlay, containers, dragHeight, dropRadius, pickRadius | (not in menu) The machine, the output item, how the bowl looks while mixing, and where the ingredients stand on the bench |
The locale file already has names for dye_orange and dye_teal if you want to add those colours.
Pressing
Config.Press (menu: Pressing).
| Setting | Type | Default | What it does |
|---|---|---|---|
minTime | number | 3 | A run can't be stopped by hand sooner than this (seconds) |
fill.fullAt | number | 0.9 | Seconds per crank turn that fills the die completely. Faster than this gives light tablets. |
fill.minCharge | number | 0.35 | How full the die gets on the fastest possible turn |
jitterSpan | number | 0.6 | How much one turn's pace may differ from the last before the tablet counts as unevenly pressed (0.6 = 60%) |
minStrokeMs | number | 260 | Crank turns faster than this (ms) are ignored. Lowering it only helps cheaters. |
residuePerStroke | number | 2.2 | Residue each tablet leaves on the die (out of 100) |
residueRushed | number | 4.0 | Extra residue from cranking at full speed |
residueLightAt / residueHeavyAt | number | 28.0 / 62.0 | When the die looks dirty and filthy |
wearPerStroke | number | 0.35 | Die wear per tablet (out of 100). At the default a die lasts about six blends. |
maxWear | number | 100 | Wear at which a die set is finished |
rampMs | number | 950 | How long a tablet takes to travel from the die to the tray (ms) |
pendingDays | number | 7 | Days a tray left on a press waits for its owner before it's cleared (checked when the server starts). 0 keeps trays forever. |
clean.perScrub | number | 7.0 | Residue removed per brush stroke |
clean.minScrubMs | number | 120 | Brush strokes faster than this (ms) are ignored |
clean.strokeLength | number | 0.02 | Brush travel that counts as one stroke (metres) |
clean.reach | number | 0.035 | How far from the die centre the brush reaches (metres) |
stations | table | see below | (not in menu) Which machines are presses, and how each is driven |
fillDepths | table | { 30, 40, 50, 60, 75 } | (not in menu) The fill depth dial's positions |
forces | table | { 35, 48, 58, 68, 82 } | (not in menu) The force dial's positions |
input, rejects, tooling | table | pill_blend, pill_rejects, punch_die_set | (not in menu) The blend, reject and die set items |
clean.inputs | table | 1 cleaning_brush + 1 cleaning_solvent | (not in menu) What a clean costs (also a rotary service) |
maxSeconds | number | 900 | (not in menu) A press session still open after this long (seconds) is closed. Any tablets stay on the press for their owner. |
persistEvery | number | 8 | (not in menu) Saves the tray every this many tablets, so it survives a restart |
In the menu, pendingDays is labelled Keep an abandoned tray for.
stations (not in menu):
| Station | Setting | Default |
|---|---|---|
hand_press | drive | 'crank' |
electric_press | drive | 'motor' |
electric_press | speeds | { 1.2, 0.9, 0.6, 0.45 } seconds per tablet |
electric_press | fill | { fullAt = 0.6, minCharge = 0.35 } |
capsule_board | drive, lines | 'board', { 'capsule' } |
Sorting
Config.Sort (menu: Sorting).
| Setting | Type | Default | What it does |
|---|---|---|---|
pickRadius | number | 0.045 | How close the cursor must be to a tablet (fraction of screen height). A large value picks neighbours. |
flickMs | number | 480 | How long a rejected tablet takes to fly to the bin (ms) |
flickArc | number | 0.30 | How high a rejected tablet is tossed (metres) |
binDistance | number | 3.5 | How close a Reject Bin must be for rejects to go into it. Without one they're swept off the tray; a bin is never required. |
loupe | boolean | false | Shows a magnifier on the cursor while sorting. Purely cosmetic: the HUD names the tablet either way. |
loupeHeight | number | 0.09 | Height of that magnifier (metres) |
Inspecting
Config.Inspect (menu: Inspecting).
| Setting | Type | Default | What it does |
|---|---|---|---|
cost | number | 1 | Tablets of the batch the first reading destroys |
station | string | 'lightbox' | (not in menu) The machine inspections happen at |
Engraving
Config.Engrave (menu: Engraving).
| Setting | Type | Default | What it does |
|---|---|---|---|
seconds | number | 3.2 | How long a cut takes (matches the machine's animation) |
brandLevel | number | 4 | Level needed to register a brand and engrave it |
maxBrands | number | 2 | Brands one character can hold |
blocklist | table | {} (empty) | Brand names nobody may register (whole name, capital letters ignored) |
name | table | { min = 3, max = 20 } | (not in menu) Brand name length limits |
station | string | 'engraver' | (not in menu) The machine it runs on |
Packaging
Config.Packaging (menu: Packaging).
| Setting | Type | Default | What it does |
|---|---|---|---|
requiredLevel | number | 1 | Level to bag pills. Bottles and blister cards need the level of their machine (Label Printer, Blister Sealer) instead. |
bagItem | string | 'pill_baggie' | The empty baggie item used per bag |
perBag | number | 12 | Pills per baggie |
maxBagsPerSession | number | 10 | The most bags in one go |
minBagMs | number | 700 | (not in menu) The fastest a bag can be filled by hand, in milliseconds |
bottle.item | string | 'pill_bottle' | The empty bottle item used per bottle |
bottle.perBottle | number | 30 | Pills per bottle |
bottle.maxPerSession | number | 4 | The most bottles in one go |
bottle.lines | table | { 'bar', 'blues', 'perc' } | Product lines that can be bottled |
blister.item | string | 'blister_sheet' | The blister sheet item used per card |
blister.perBlister | number | 10 | Pills per card |
blister.maxPerSession | number | 5 | The most cards in one go |
protection | table | loose 0.0, baggie 0.25, bottle 0.6, blister 0.95, coated 0.3 | How much each packing shields pills from breaking (0–1). coated is added on top for coated batches. |
station, bottle.station, blister.station | string | 'workbench', 'label_printer', 'blister_sealer' | (not in menu) Where each kind of packing happens |
bottle.labels | table | see below | (not in menu) The label templates for each line |
bottle.labels (not in menu): players pick from these when filling bottles, so a bottle can only claim what you wrote here. Each has a key, a name and directions.
| Line | key | name | directions |
|---|---|---|---|
perc | axia_10 | AXIA 10/325 | Take 1 tablet by mouth every 6 hours as needed for pain. |
perc | axia_5 | AXIA 5/325 | Take 1 to 2 tablets by mouth every 6 hours as needed. |
blues | vela_30 | VELA 30 mg | Take 1 tablet by mouth every 4 to 6 hours as needed. |
bar | nova_2 | NOVA 2 mg | Take one half tablet at bedtime. May cause drowsiness. |
bar | nova_1 | NOVA 1 mg | Take 1 tablet by mouth up to three times daily. |
A line in bottle.lines with no templates can't be bottled.
Granulating (sieve)
Config.Granulate (menu: Granulating (sieve)).
| Setting | Type | Default | What it does |
|---|---|---|---|
requiredLevel | number | 6 | Level to use the sieve |
pourRate | number | 22.0 | Wetness added per second of pouring |
wetBand | table | { low = 40, high = 60, falloff = 30 } | The wetness that granulates well |
passPerStroke | number | 0.07 | Share of the mass one stroke pushes through at a good wetness |
clog | number | 0.75 | How much less a soaked mass passes per stroke |
minStrokeMs | number | 110 | Strokes faster than this (ms) are ignored |
minTime | number | 3 | A granulation finished sooner than this (seconds) produces nothing |
tool, fluid | string | 'pill_sieve', 'granulating_fluid' | (not in menu) The sieve and fluid items |
strokeLength, heavyUntil, layerUntil, screen, reach, bench, jerrycan, dropRadius, dragHeight | (not in menu) Scene layout and how the screen looks as it clears |
What granulating is worth is set under Quality (Granulation).
Drum tumbler
Config.Tumble (menu: Drum tumbler).
| Setting | Type | Default | What it does |
|---|---|---|---|
capacity | number | 4 | Blends it holds |
runSeconds | number | 12 | How long it runs |
band | table | { low = 0.5, high = 0.75, falloff = 0.5 } | The fill (share of capacity) that tumbles best |
boost | number | 30 | Uniformity added by a good fill |
maxUniformity | number | 97 | The highest uniformity tumbling can reach |
station | string | 'drum_tumbler' | (not in menu) The machine it runs on |
Ball mill
Config.BallMill (menu: Ball mill).
| Setting | Type | Default | What it does |
|---|---|---|---|
capacity | number | 6 | Raw actives it holds |
runSeconds | number | 15 | How long it runs |
band | table | { low = 0.34, high = 0.67, falloff = 0.5 } | The fill that grinds best |
grind | table | { ideal = 72, fine = 96, coarse = 34 } | (not in menu) Grind at a good fill, at the emptiest and at the fullest |
fines | table | { ideal = 4, max = 80 } | (not in menu) Fines at a good fill, and at the emptiest |
station | string | 'ball_mill' | (not in menu) The machine it runs on |
Hardness tester
Config.Hardness (menu: Hardness tester).
| Setting | Type | Default | What it does |
|---|---|---|---|
cost | number | 3 | Tablets broken per test |
kpPerPoint | number | 0.12 | Break force (kp) shown per point of integrity |
station | string | 'hardness_tester' | (not in menu) The machine it runs on |
Pan coater
Config.Coat (menu: Pan coater).
| Setting | Type | Default | What it does |
|---|---|---|---|
item | string | 'coating_solution' | The coating solution item used per run |
capacity | number | 48 | Tablets it holds |
runSeconds | number | 14 | How long it runs |
band | table | { low = 0.5, high = 1.0, falloff = 0.5 } | The fill that coats evenly |
finishGain | number | 0.45 | Share of the gap to a perfect finish an even coat closes |
integrityGain | number | 8 | Integrity an even coat adds |
station | string | 'pan_coater' | (not in menu) The machine it runs on |
Capsule board
Config.Capsule (menu: Capsule board). The level to use the board is the Capsule Filling Board's under Machines, and capsules also need the capsule line's level (Formulas).
| Setting | Type | Default | What it does |
|---|---|---|---|
shellItem | string | 'empty_capsule' | The empty capsule item (30 per board) |
perPass | number | 0.34 | Dose added to a column each time the spreader crosses it (1.0 is a full dose) |
heapAt | number | 1.2 | A column filled past this is heaped and spills |
idealTamps | number | 2 | The number of tamps the board's gauge marks as right |
minPassMs | number | 90 | Crossings of the same column faster than this (ms) are ignored |
tampForce | table | { 30, 42, 56, 68, 82 } | (not in menu) The compaction from 0, 1, 2, 3 and 4 tamps |
spillResidue | number | 90 | (not in menu) How much a heaped column dirties its capsules |
Rotary press (crew)
Config.Rotary (menu: Rotary press (crew)). Starting a run as the feeder needs the Rotary Press's level under Machines. Crew who join a run don't need it.
| Setting | Type | Default | What it does |
|---|---|---|---|
hopperBlends | number | 3 | Blends the hopper holds |
trayCapacity | number | 24 | Tablets the output tray holds before the sorter has to clear it (max 24) |
residuePerStroke | number | 0.25 | Residue per tablet |
wearPerStroke | number | 0.05 | Die wear per tablet |
serviceSeconds | number | 4 | How long a service takes |
perBag | number | 12 | Pills per bag the packer makes |
crewDistance | number | 6.0 | How close a crew member must stay to keep their role (metres) |
speeds | table | { 2.0, 3.0, 4.0, 5.0 } | (not in menu) Turret speeds, in tablets per second |
fill | table | { fullAt = 0.3, minCharge = 0.4 } | (not in menu) How fast the turret's stations fill |
xp | table | { blend = 6, tray = 4, bag = 1 } | XP to the feeder per blend the turret uses up, to the sorter per full tray's worth sorted, and to the packer per bag |
station, tickMs | 'rotary_press', 250 | (not in menu) The machine, and how often the turret updates |
Stage animations and cameras
(Advanced tab.)
Config.SceneAnims(menu: Stage animations): what other players see your character doing while you're inside a stage. The stage camera is only on your screen, so without these your character would stand frozen at the bench. Each stage (mill,blend,press,engrave,printer,sealer,bagging,granulate,hardness,ballmill,tumble,coat,rotary,reagent) has adict,clipandflag.Config.SceneCameras(menu: Stage cameras): where the camera sits for each stage and what it frames. Each has anoffset(from the station: -Y is in front, +Z is up), alookAtpoint and afov. The press, electric press, capsule board and mixer also have extra shots (over the tray, on the die, while mixing) and an ease time. Change these live, walk into the stage, and adjust until the whole working area is in shot.
Keep unchecked batches for
(Advanced tab.)
| Setting | Type | Default | What it does |
|---|---|---|---|
Config.BatchRetentionDays | number | 0 | Deletes the stats of batches nobody ever inspected once they're older than this many days (checked when the server starts). 0 keeps them forever. Inspected batches are always kept. |
Pills whose batch has been deleted become "unknown": they can't be inspected, packed or tested, and taking one uses it up with no effect. Only raise this if old pills no longer matter on your server.
Formulas
Config.Formulas in shared/config/production.lua defines the five product lines. Only each line's requiredLevel is in the menu, under Players → Progression → Product line unlocks. Everything else here is (not in menu) and read from the file on every start.
| Line key | requiredLevel | accepts (raw actives) | product | stamps | tabletsPerBlend | Other |
|---|---|---|---|---|---|---|
party | 1 | party_powder | party_pill | plain, brand | 48 | Takes dye |
bar | 3 | pharma_bottle | bar_pill | plain, ref | 48 | |
blues | 5 | pharma_bottle | blue_pill | plain, ref | 48 | counterfeit = true |
perc | 5 | pharma_bottle | perc_pill | plain, ref | 48 | counterfeit = true |
capsule | 7 | party_powder, mystery_powder | molly_capsule | plain | 30 | Filled on the capsule board |
requiredLevelis the level to make that line (mix it, and cut a die for it).acceptsis which raw actives' milled powder the line takes.stampsis what the engraver may put on the line's dies:plain(a blank face),ref(the line's reference imprint) orbrand(a player brand).pourablelists what can go in the bowl (milled powder, filler, binder, lubricant, and dye for party pills).packednames the item each packing turns the line's pills into, for NOVA Barspacked = { baggie = 'bar_pill_bag', bottle = 'bar_pill_bottle', blister = 'bar_pill_blister' }. Party pills and capsules have no bottle, since they can't be bottled. If you let a line into a packing it has no item for, its packed pills stay the loose item.
Changing the line keys, items or the capsule setup can break other parts of the resource. Adjust levels and accepts, and leave the rest as shipped unless you're sure.
Quality
Config.Profile (menu: Lab → Quality → Tablet Profile, Advanced tab). This is what makes a good tablet. Every "window" is a target range, not a maximum: going over costs as much as falling short.
| Setting | Type | Default | What it does |
|---|---|---|---|
PotencyWindow | table | { low = 45, high = 68 } | The potency target for Party Pills and AURA Capsules. The other lines have their own (see Lines). |
PotencyRef | number | 0.30 | The dose that reads as a potency of 50. Raise it to make the same formula read weaker. |
ForceWindow | table | { low = 48, high = 66, falloff = 20 } | The compression that gives solid tablets |
DepthForceRef | number | 50 | The fill depth at which a force setting delivers exactly itself. This ties the two press dials together. |
TimingFloor | number | 0.45 | What a completely uneven stroke still scores, so bad timing never zeroes a stat |
WearSpan | number | 130 | Die wear at which wear's effect on imprint reaches zero |
ResidueSpan | number | 95 | Residue at which residue's effect on imprint reaches zero |
FillerFloor | number | 0.35 | Below this share of filler, tablets can't form a clean face |
GrindWindow | table | { low = 60, high = 80, falloff = 25 } | The grind that compacts well |
Granulation | table | { spreadRelief = 0.45, finesRelief = 0.65, flowBonus = 0.45 } | What a fully granulated blend is worth: less spread, fines that stop counting, faster die filling |
BinderWindow | table | { low = 0.07, high = 0.12, falloff = 0.05 } | Binder as a share of the formula |
LubeWindow | table | { low = 0.008, high = 0.018, falloff = 0.010 } | Lubricant as a share of the formula |
Variance | table | { base = 30.0, floor = 1.5, levelRelief = 0.8 } | Tablet-to-tablet spread at a poor blend and a perfect one, and how much each level above 1 narrows it |
ConsistencyScale | number | 12.0 | The spread (in stat points) that scores zero consistency |
BaseDefectChance | number | 0.04 | The chance of a cosmetic dud even on a flawless run |
Weights | table | (not in menu) How much each input counts toward integrity, imprint and finish | |
Defects | table | (not in menu) The stat levels at which each defect starts to appear | |
Grades | table | Excellent 85, Good 70, Fair 52, Poor 34, Bad 0 | (not in menu) The grade bands |
ImpressionCrumbly, ImpressionChipped, ImpressionRough, ImpressionUneven, ImpressionClean | number | 35, 45, 45, 50, 75 | (not in menu) When each impression shows on an uninspected batch |
Lines | table | see below | (not in menu) Per-line overrides |
Lines (not in menu) overrides the base values for one product line:
| Line | Overrides |
|---|---|
bar | PotencyWindow = { low = 40, high = 60 }, and adds the snapped defect |
blues | PotencyWindow = { low = 52, high = 72 }, and a Match (counterfeit) score: stamp weights ref = 1.0, plain = 0.25; term weights stamp 30, imprint 30, finish 25, consistency 15 |
perc | PotencyWindow = { low = 38, high = 58 }, and the same Match as blues |
capsule | No imprint; the tablet defects are switched off and replaced with dented and leaking |
Skill (file only)
Config.Skill.Reveal in shared/config/profile.lua maps each level to how much the gauges and readings show: 'zones' (words, no numbers), 'values' (exact numbers) or 'preview' (numbers, plus each tablet's defect called out and the fuller light-box reading). Defaults: levels 1–2 zones, 3–5 values, 6–10 preview. It's always read from the file: edit it and restart.
Players
World
All under Players → World.
Pills breaking
Config.Decay (menu: Pills breaking).
| Setting | Type | Default | What it does |
|---|---|---|---|
enabled | boolean | true | Turns pills breaking on or off |
maxLoss | number | 0.6 | Share of a stack a full-strength knock breaks, for a batch with no integrity and no packing |
exponent | number | 1.5 | How much integrity protects. Higher means sound batches barely notice knocks. |
time.everyMinutes | number | 20 | How often weak batches crumble over time |
time.integrityFloor | number | 55 | Only batches below this integrity crumble over time |
time.severity | number | 0.25 | How big a knock time counts as |
crash.minDrop | number | 9.0 | The smallest sudden speed drop that counts as a crash (m/s) |
crash.fullDrop | number | 30.0 | The speed drop that counts as a full-strength knock |
fall.minHeight | number | 3.0 | The shortest fall that counts (metres) |
fall.fullHeight | number | 12.0 | The fall that counts as a full-strength knock |
cooldownSeconds | number | 4 | Knocks closer together than this count as one |
How much packing protects is set under Packaging (protection).
Dust
Config.Dust (menu: Dust).
| Setting | Type | Default | What it does |
|---|---|---|---|
enabled | boolean | true | Turns dust on or off |
perSecond | number | 1.2 | Exposure gained per second in a dusty stage |
decayPerSecond | number | 2.5 | Exposure lost per second otherwise |
hazeAt | number | 20 | Exposure at which the screen starts to haze |
coughAt | number | 55 | Exposure at which the character starts coughing |
damage | number | 0 | Health lost every few seconds while coughing. 0 does no harm. Never takes a player below 101 health. |
stages | table | { mill = true, press = true, rotary = true } | (not in menu) Which stages throw dust. A press only counts while it's striking tablets or the capsule board is being filled, not while sorting or scrubbing. |
collector.range | number | 4.0 | A running dust collector this close to the player clears the air entirely (metres) |
mask.shield | number | 0.85 | Share of dust exposure the mask stops |
mask.anim | table | mp_masks@standard_car@ds@ / put_on_mask, 1200 ms | Played putting the mask on and taking it off. The mask changes halfway through. |
collector.station | string | 'dust_collector' | (not in menu) The machine that counts as a dust collector |
mask.item | string | 'dust_mask' | (not in menu) The item that puts the mask on and takes it off |
mask.model | string | 'oxide_pill_dust_mask_worn' | (not in menu) The model shown on the wearer's face. It ships with the resource |
mask.bone | number | 31086 | (not in menu) The head bone the model is attached to |
mask.offset | vector3 | vec3(0.0, 0.0, 0.0) | (not in menu) Nudges the model on the face, in metres |
mask.rotation | vector3 | vec3(0.0, 0.0, 0.0) | (not in menu) Turns the model on the face, in degrees |
Taking pills
Config.Consume (menu: Taking pills).
| Setting | Type | Default | What it does |
|---|---|---|---|
enabled | boolean | true | Whether pills can be taken at all. When off, using a pill does nothing. |
overdose.enabled | boolean | true | Turns overdoses on or off |
overdose.perPointOver | number | 0.035 | Overdose chance per point of potency over the window |
overdose.severeShare | number | 0.35 | Share of overdoses that are severe |
overdose.mildDamage | number | 15 | Health lost in a mild overdose |
overdose.severeDamage | number | 150 | Health lost in a severe overdose |
overdose.brandPenalty | number | 25 | Reputation a brand loses when one of its pills overdoses someone because the batch was too strong (not when the player simply took too many) |
overdose.stack.limit | number | 3 | Pills a player can take inside the window before each extra one adds risk, however good the batch |
overdose.stack.windowSeconds | number | 600 | The window, in seconds |
overdose.stack.perExtra | number | 0.12 | Overdose chance added per pill past the limit |
lines | table | see below | (not in menu) Each line's effect and length |
effects | table | see below | (not in menu) What each effect does |
anim | table | mp_suicide / pill, 2200 ms | (not in menu) The pill-taking animation |
overdose.lines | table | party 1.0, capsule 1.0, bar 0.8, blues 2.0, perc 1.2 | (not in menu) Overdose risk per line |
overdose.medical | table | mild: sickness / overdose, severity 2, expires after 600 s; severe: severity 4 | (not in menu) The condition recorded by a medical resource such as oxide-medical, if you run one |
lines (not in menu): party and capsule are euphoria for 240 s; bar is sedated for 300 s; blues is opioid for 300 s; perc is opioid for 240 s. Lengths are for a batch in the middle of its potency window.
effects (not in menu):
| Effect | Screen effect | Strength | Other | Stress |
|---|---|---|---|---|
euphoria | drug_flying_base | 0.35 | Sprint ×1.12 (run) | -15 |
sedated | drug_drive_blend01 | 0.45 | Drunk walk (walk, move_m@drunk@slightlydrunk) | -30 |
opioid | drug_wobbly | 0.3 | +3 health every 5 seconds (health, tick) | -10 |
Stress changes need a framework with a stress system (QBCore and QBX; ESX has none).
Reagent testing
Config.Reagent (menu: Reagent testing).
| Setting | Type | Default | What it does |
|---|---|---|---|
item | string | 'reagent_kit' | The kit item |
uses | number | 5 | Tests in a new kit |
seconds | number | 3.0 | How long a test takes |
classes | table | (not in menu) The vial colour for each class: mdma black, meth orange, opioid purple, fentanyl blue, benzo and cut clear | |
actives | table | party_powder = 'mdma', pharma_bottle = 'opioid' | (not in menu) What each known raw active really is, for powder not yet made into a line |
lines | table | party/capsule 'mdma', bar 'benzo', blues/perc 'opioid' | (not in menu) What each product line tests as. A line's class wins over its active's |
mystery | table | see below | (not in menu) What mystery powder can turn out to be |
mystery (not in menu): each lot is rolled once from these, by weight.
| Class | Weight | Strength (scale) | Overdose risk (overdose) |
|---|---|---|---|
mdma | 45 | 1.0 | 1.0 |
cut | 25 | 0.5 | 1.0 |
meth | 15 | 1.25 | 1.5 |
fentanyl | 15 | 1.8 | 3.0 |
Police evidence
Config.Evidence (menu: Police evidence).
| Setting | Type | Default | What it does |
|---|---|---|---|
jobs | table | { 'police', 'sheriff' } | Jobs that can log evidence in Pill ID. Capital letters don't matter. |
dutyOnly | boolean | true | Only while on duty |
destroy | boolean | false | Remove the pills once logged. Off keeps them in the officer's inventory, marked as evidence. |
serialTrail | boolean | false | Gives every press placed from now on a serial number, and shows it on seized batches |
showOwner | boolean | false | With the serial trail on, evidence also names who placed the press |
Selling
All under Players → Selling.
Brand reputation
Config.Reputation (menu: Brand reputation).
| Setting | Type | Default | What it does |
|---|---|---|---|
perUnit | number | 0.5 | The most reputation one pill sold can add (a better batch adds more) |
maxBonus | number | 0.25 | Extra price a brand gets at the reputation cap (0.25 = 25%). A brand below zero sells for up to this much less. |
seizurePenalty | number | 10 | Reputation lost per police seizure |
max | number | 1000 | The reputation cap |
min | number | -100 | The reputation floor |
tiers | table | bad -100, shaky -25, known 25, respected 150, notorious 400 | (not in menu) What Pill ID calls each reputation level |
Pharma delivery run
Config.Delivery (menu: Pharma delivery run).
| Setting | Type | Default | What it does |
|---|---|---|---|
enabled | boolean | true | Turns the delivery run and its contact on or off |
requiredLevel | number | 5 | Level to take work |
contact.coords | vector4 | vec4(283.81, 30.70, 88.47, 160.0) | Where the contact stands and which way they face. Place previews the ped in the world: scroll to turn, click to confirm. |
contact.model | string | 'a_m_m_business_01' | The contact's ped model |
points | list | 7 doorsteps | Where buyers come out. A run picks dropsPerRun of them at random. Add and each point's Place button open the in-world picker: aim at the doorstep and click. The menu won't save the run turned on with no points. |
perDrop | number | 10 | Tablets handed over per drop |
dropsPerRun | number | 4 | Drops in a run |
perTablet | table | { blues = 60, perc = 45, bar = 35 } | Cash per tablet at full price, before the buyer judges the batch |
launder.enabled | boolean | true | Wash dirty money at each drop |
launder.type | string | 'auto' | Where dirty money comes from: 'auto', 'account', 'item' or 'bills' (see Laundering) |
launder.name | string | 'black_money' | The account or item name (not used by 'auto') |
launder.itemValue | number | 1 | What one dirty-money item is worth ('item' only) |
launder.metadataKey | string | 'worth' | The field on each bill that holds its worth ('bills' only) |
launder.perDrop | number | 2000 | The most dirty money washed per drop |
launder.rate.min / launder.rate.max | number | 0.55 / 0.9 | Clean money back per dirty dollar, for a poor batch and for a perfect one |
cooldownMinutes | number | 15 | Wait after a run ends before the next (also applied if the player logs out mid-run) |
timeLimitMinutes | number | 25 | Time limit for a run |
xpPerDrop | number | 12 | XP per drop |
contact.scenario | string | 'WORLD_HUMAN_STAND_IMPATIENT' | (not in menu) The contact's idle animation |
buyerModels | table | 4 models | (not in menu) The buyer character models: a_m_y_business_02, a_f_y_business_02, a_m_m_prolhost_01, a_f_m_bevhills_01 |
lines | table | { 'blues', 'perc', 'bar' } | (not in menu) Product lines the buyers take |
packing | string | 'blister' | (not in menu) How the pills must be packed |
handoffDistance | number | 6.0 | (not in menu) How close to the doorstep the handoff must happen (metres) |
The 7 default doorsteps are vec3(772.54, -152.31, 74.42), vec3(839.75, -181.45, 74.18), vec3(920.87, -238.29, 70.17), vec3(1060.83, -378.49, 68.22), vec3(1099.41, -438.71, 67.78), vec3(1207.42, -620.35, 66.43) and vec3(525.93, 207.16, 104.73).
The contact has no map blip, and is stood on the ground wherever you put them, so the height only needs to be close. A moved contact appears at the new spot within a couple of seconds for anyone nearby.
Police alerts for handoffs are set under Dispatch (the Delivery handoff stage).
Laundering
Players → Selling → Pharma delivery run → Laundering → Dirty money is decides where the dirty money comes from:
type | Takes dirty money from |
|---|---|
'auto' (default) | The framework's own: markedbills with their worth in the worth field on QBCore, black_money items worth $1 each on QBX, the black_money account on ESX |
'account' | The money account named in name |
'item' | The item named in name, each worth itemValue |
'bills' | The item named in name, each worth what its metadataKey field says (like QB marked bills) |
On QBX, check which one your server uses. The default matches a stock Qbox install, which keeps dirty money as black_money items. If your server uses markedbills instead, set Dirty money is to Bills with their worth in metadata, Account or item name to markedbills and Bill worth field to worth.
Each drop takes up to perDrop dollars of it and pays back clean cash. With bills, the biggest bills go first and any change comes back as one new bill. If the player has none, the laundering step does nothing and the drop still pays its normal cash. To switch laundering off entirely, turn off Laundering → Enabled.
Buyers (file only)
Config.Buyers in shared/config/world.lua defines the four street buyer types used for oxide-drugselling sales. It's always read from the file: edit it and restart. Each archetype has:
| Field | What it does |
|---|---|
share | How many buyers are this type, relative to the others (club 35, street 35, pharma 15, bulk 15) |
weights | How much each stat counts to them (potency, integrity, imprint, finish, consistency, match) |
hot | Price multiplier for a batch over its potency window |
lines | Price multiplier per product line |
packing | Price multiplier per packing (loose, baggie, bottle, blister) |
range | The multiplier a worthless batch and a perfect one fetch |
The default values for each type are in Features. The delivery run pays by the pharma type.
Progression
Config.Progression (menu: Players → Progression).
Levels (in the menu)
| Level | XP required |
|---|---|
| 1 | 0 |
| 2 | 100 |
| 3 | 300 |
| 4 | 650 |
| 5 | 1200 |
| 6 | 2000 |
| 7 | 3200 |
| 8 | 5000 |
| 9 | 7500 |
| 10 | 11000 |
Levels must go up in order. If you add levels beyond 10, also look at Config.Skill (above) so the new levels reveal what you want.
XP rewards (in the menu)
Config.Progression.xpRewards (menu: XP per stage):
| Key | Default | Earned for |
|---|---|---|
mill | 6 | Milling a powder |
blend | 10 | Mixing a blend |
press | 14 | Finishing a press run |
clean | 3 | Cleaning a die |
sort | 8 | Sorting a tray |
package | 6 | Packing pills: once per bagging visit (if at least one bag was filled), and once per confirm at the label printer or blister sealer |
engrave | 4 | Cutting a die: its first shape, and the stamp that finishes it. Re-cutting a blank die to another shape pays nothing |
granulate | 6 | Granulating a blend |
tumble | 6 | A drum tumbler run |
ballmill | 6 | A ball mill run |
coat | 8 | A pan coater run |
Inspecting, hardness tests and reagent tests pay nothing on purpose: they already cost tablets. Rotary press and delivery XP are set in their own sections (Config.Rotary.xp, Config.Delivery.xpPerDrop). XP for street sales is set in oxide-drugselling.
Product line unlocks (in the menu)
The level needed to mix each product line (Config.Formulas.<line>.requiredLevel, see Formulas):
| Line | Level |
|---|---|
| Party Pills | 1 |
| NOVA Bars | 3 |
| VELA Blues | 5 |
| AXIA Percs | 5 |
| AURA Capsules | 7 |
A line also needs its machines: capsules need the Capsule Filling Board, for example, whose level is set under Machines.
Perks (not in menu)
Config.Progression.perks:
| Key | Level | Effect |
|---|---|---|
tooling | 10 | wearMultiplier = 0.6: die sets wear 40% slower |
reputation | 10 | reputationMultiplier = 1.25: brand reputation from sales builds 25% faster |
Advanced
Dispatch
Config.Dispatch (menu: Advanced → Dispatch → Police alerts, Advanced tab). Production is risk-free out of the box.
When a player starts a stage, the server rolls once. On a hit, a dispatch call goes out at the station's saved location, not the player's, so a suspect can't fake or move it.
| Setting | Menu label | Default | What it does |
|---|---|---|---|
chance | Alert chance | 0.0 | Chance of an alert each time a stage is started. 0 turns production alerts off. 0.15 is roughly one in seven. |
stageChance | Per-stage chance | all -1 | A different chance for one stage. -1 means "use the alert chance above". |
cooldown | Site cooldown | 300 | Seconds after a station alerts before it can alert again. A roll that doesn't alert doesn't start it. Other stations are unaffected. |
jobs | Alert jobs | { 'police' } | Jobs that receive the alert. Type each name exactly as your framework spells it (usually lowercase, like police). |
code, title, message | Call code, Title, Message | '10-15', 'Suspected Narcotics Lab', 'Reports of chemical odour and unusual activity at this location.' | The text responders see. The default message doesn't say what's being made. |
stageAlerts | (not in menu) | delivery: title 'Suspicious Handoff', message 'A caller reports what looks like a drug handoff at their door.' | Different wording for a stage that isn't a lab. Anything not set here uses the lines above. |
priority | Priority | 2 | 1 is highest |
icon | Icon | 'fas fa-pills' | Font Awesome icon |
blipData | Map blip | { sprite = 51, color = 5, scale = 1.0, radius = 100 } | The map blip, and the size of the search circle around the site (metres), for dispatch resources that draw one |
The stages you can tune separately are mill, blend, press, package (baggies), bottle, blister, granulate, tumble, ballmill, coat, rotary and delivery (a delivery run handoff, labelled Delivery handoff in the menu). A common setup is to leave the master chance low and raise just the press.
What "chance" means in practice: it's rolled when a stage is started, not per batch finished. After a station alerts, it doesn't alert again until the Site cooldown has passed. So with the defaults, each station sends at most one alert every 5 minutes, however many times players start stages on it. A roll that doesn't alert leaves the station free to roll again on the next stage.
Delivery handoffs alert where the player hands over the card (within a few metres of the doorstep), with their own wording (stageAlerts, above). Their cooldown is per player rather than per station. Like every other stage they follow the master Alert chance unless you set Delivery handoff yourself, so with the defaults they're off.
What you need installed
Alerts go through whatever dispatch resource you run, as long as o-link supports it: ps-dispatch, cd_dispatch, qs_dispatch, lb-tablet, oxide-dispatch, tk_dispatch, fd_dispatch, emergencydispatch, linden_outlawalert, origen_police, bub-mdt, kartik-mdt, redutzu-mdt, wasabi_mdt and piotreq_gpt.
Without one, the fallback is basic. Players with the alert job get a short notification ([10-15] plus the message; the title isn't shown) and a map blip on the station that disappears after 30 seconds.
If nobody with the alert job is online, the alert is dropped. That's on purpose: there's no queue of old calls waiting for the first officer to log in. (oxide-dispatch is the exception: it records the call whether or not anyone is online.)
Compatibility
Config.ModelSwaps (menu: Advanced → Compatibility → Model replacements).
| Setting | Type | Default | What it does |
|---|---|---|---|
Config.ModelSwaps | table | {} (empty) | Rows of { from = 'model', to = 'model' }. If a model is missing on your game build, the resource warns admins and skips it. Add a row to use a different model instead. |
For example, to replace the workbench table with a different base-game table (add the row in the menu on a server that has already started once):
Config.ModelSwaps = {
{ from = 'bkr_prop_weed_table_01b', to = 'prop_table_03' },
}Every pill station and tablet model ships in this resource's stream/ folder, so a warning about one of those means the install is incomplete (the folder is missing or damaged), not an old game build. Re-copy the resource. The warning tells you which case it is.
Next Steps
Installation
Install oxide-pills — dependencies and minimum versions, supported inventories, all 59 items for QBCore, QBX and ESX, ingredients, and verifying the lab end to end.
Features
How oxide-pills works — the production line, every station, the hidden tablet profile, product lines, brands, packaging, the rotary press crew, dust, reagent tests, overdoses, selling, the delivery run, Pill ID and progression.