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.

Step-by-step setup for oxide-pills. Follow the steps in order. Most problems come from skipping one.

Before You Start

oxide-pills works on QBCore, QBX, and ESX. It talks to your framework through o-link (the bridge every Oxide resource uses), so there is nothing framework-specific to set up in the script itself. The framework-side work is making sure you run a supported inventory (below) and adding the items to it (step 4).

You need these resources installed and working first:

ResourceMinimum VersionWhat it does
ox_libany recentProvides translations, callbacks, and UI helpers
oxmysqlany recentConnects the server to your MySQL database
o-link1.9.1Connects pills to your framework (players, inventory, notifications, targeting, and the tool used to place stations)
A targeting resourceanyThe "look at it and pick an option" menu every station uses. o-link supports ox_target, qb-target, sleepless_interact, tgiann-target and oxide-target.
A supported inventorysee belowHolds the pill items and the information stored on them

Why o-link 1.9.1? Pill stations are placed with a placement tool that o-link added in version 1.9.0, including snapping machines onto a workbench. With an older o-link, using a station item shows "Placing stations needs an updated o-link." and nothing is placed. Version 1.9.1 puts the police alert's map blip on the lab when you don't run a dispatch resource; before it, the blip landed on whichever officer received the alert.

If you already run other Oxide resources, you have ox_lib, oxmysql and o-link already. Check that o-link is 1.9.1 or newer.

Which inventory you need

Pills store information on the item itself: which batch a pill came from, how finely a powder was ground, how worn a die set is and what it's cut for, how many tests a reagent kit has left. This is called item metadata. Your inventory must be one o-link supports, it must keep that metadata, and o-link must be able to update it on a specific slot.

FrameworkInventoryWorks with pills?
QBXox_inventory (QBX's default)Yes
QBCoreqb-inventory (QBCore's default)Yes, with the unique = true setting from step 4
QBCoreox_inventoryYes
ESXox_inventoryYes
ESXESX's own built-in inventoryNo. o-link doesn't support it, so pills can't give or take any item. Install ox_inventory.
ESXhex_4_inventoryNo. It doesn't keep item metadata, so batches, die wear and reagent kits can't work.

Other inventories:

  • ps-inventory and jpr-inventory: o-link can't update metadata on a slot in these, so die wear, engraving a die, reagent kit tests, evidence marks and mystery powder lots don't save. Use one of the inventories in the table instead.
  • qs-inventory, codem-inventory, core_inventory, origen_inventory, tgiann-inventory and ak47_qb_inventory: o-link passes metadata to them and can update it on a slot. How each one stacks items that carry different information hasn't been checked. If you use one, make two batches of pills and check they stay in separate stacks.

Installation Steps

1. Place the resource

Put the oxide-pills folder inside your server's resources folder. If you run several Oxide resources, keep them together in a folder named [oxide].

What's in the folder:

PathPurpose
shared/config.luaStarting values (factory defaults): ownership, distances, placement zones, item usage whitelist, police alerts
shared/config/equipment.luaThe lab workbench and every machine: which item places it and the level it needs
shared/config/production.luaEvery production stage, and the formula for each of the five product lines
shared/config/profile.luaTablet quality tuning, and how much each level lets a player see
shared/config/progression.luaXP rewards, level thresholds and level-10 perks
shared/config/scenes.luaStage cameras and the animations other players see
shared/config/world.luaPills breaking, reagent tests, dust, taking pills, police evidence, buyers, brand reputation and the delivery run
shared/config/models.luaModel replacements for older game builds
server/, client/, other shared/ filesThe script itself
web/dist/The pre-built interface that ships with the resource
locales/en.jsonTranslation file (notifications, prompts, and menu text)
items/Inventory images for every pill item, and ox_inventory_items.lua, the item list ready to paste into ox_inventory
stream/Every station, machine part and tablet model
sql/Database schema, for reference. Every table is created automatically on first start

2. Set the startup order

Your server.cfg is the text file that tells your server which resources to start, and in what order. Every Oxide resource uses the same four-tier order:

server.cfg
# 1. Framework — everything your framework requires (qb-core, es_extended, qbx_core, etc.)
ensure <framework>

# 2. Scripts — your other resources (phone, inventory, garages, etc.)
ensure <your-other-scripts>

# 3. o-link — the bridge. Starts after your framework/scripts, before any Oxide resource.
ensure o-link

# 4. Oxide resources — keep these together, ideally in a folder named [oxide]
ensure oxide-pills

ox_lib and oxmysql belong with your framework in tier 1. There's no required order inside a tier. What matters is that o-link starts after your framework and scripts, and before oxide-pills. If all your Oxide resources are in an [oxide] folder, you can write ensure [oxide] once instead of one line per resource.

3. Database: nothing to do

The resource creates its own database tables the first time it starts, so there is no SQL script to run. (sql/install.sql in the resource folder is still included if you'd rather create the tables yourself or want to see the schema. The files in sql/migrations/ are only for servers that manage their schema by hand. A fresh install doesn't need them, and neither does an update: any missing columns are added automatically when the resource starts.)

These are the tables it creates:

TableWhat it stores
drugs_progressionEach character's pill level, XP, and totals made and sold
drug_equipmentEvery workbench and machine placed in the world
pills_workbenchesThe table model and number of machine slots each workbench was placed with
pills_machine_mountsWhich workbench slot each benchtop machine sits in
pills_machine_stateHow dirty each press die is, and any tray of tablets left on a press, so nothing is lost on restart
pills_die_wearHow worn each punch die set is, so wear follows the die and survives a restart
pills_batchesThe hidden quality numbers of every batch ever pressed
pill_brandsPlayer brands, with their reputation, sales, seizures and overdoses
pills_lotsWhat each lot of mystery powder really is
pills_seizuresPills police have logged as evidence, and where
pills_serialsPress serial numbers (only used if you turn on the serial trail)
oxide_settingsThe live configuration you edit in game with /pills settings (see Configuration)

It's safe to run oxide-pills alongside oxide-weed, oxide-meth, oxide-cocaine, or any other Oxide resource. drugs_progression, drug_equipment, and oxide_settings are shared on purpose (each resource keeps its own rows), and each resource only creates them if they don't exist yet.

4. Add the items to your inventory

oxide-pills needs every item below to exist in your inventory with the exact spelling shown. One typo in an item name breaks that step of the lab. None of the names clash with the items stock QBCore or QBX already ship (such as oxy, xtcbaggy or painkillers), so you don't need to rename anything.

The tables list every item so you can check nothing is missing. You don't need to type them in by hand: the ready-made files for each inventory are in Register the items below.

"Must be usable" means a player can use the item from their inventory. The script decides what happens when it's used; your inventory just has to allow it.

Workbench and machines

Using one of these starts placement: the player positions the station and confirms.

ItemLabelWeight (g)Must be usable
pill_workbenchLab Workbench8000Yes
pill_crank_pressCrank Press6000Yes
pill_grinderBlade Grinder2000Yes
pill_mixerStand Mixer4000Yes
pill_lightboxLight Box2000Yes
pill_engraverDie Engraver6000Yes
pill_electric_pressElectric Press10000Yes
pill_label_printerLabel Printer3000Yes
pill_hardness_testerHardness Tester3000Yes
pill_capsule_boardCapsule Filling Board2000Yes
pill_ball_millBall Mill6000Yes
pill_blister_sealerBlister Sealer8000Yes
pill_reject_binReject Bin2000Yes
pill_dust_collectorDust Collector8000Yes
pill_drum_tumblerDrum Tumbler12000Yes
pill_pan_coaterPan Coater12000Yes
pill_rotary_pressRotary Press25000Yes

Hand tools and tooling

ItemLabelWeight (g)Must be usable
pill_mortarMortar & Pestle1500Yes
pill_manualPressing Manual300Yes
pill_sieveGranulating Screen600Yes
dust_maskDust Mask100Yes
reagent_kitReagent Test Kit300Yes
punch_die_setPunch & Die Set1200No
cleaning_brushCleaning Brush200No
cleaning_solventCleaning Solvent500No

Make punch_die_set and reagent_kit unstackable (one per inventory slot). Each die set tracks its own wear and shape, and each kit tracks how many tests it has left. In ox_inventory that's stack = false; in qb-inventory it's unique = true. The ready-made item files already do this. On qb-inventory more items need it, see Keeping batches apart on qb-inventory.

Ingredients

ItemLabelWeight (g)
party_powderParty Powder100
pharma_bottlePharma Bottle200
mystery_powderMystery Powder100
pill_fillerTablet Filler100
pill_binderTablet Binder100
pill_lubricantDie Lubricant100
pill_dyeTablet Dye100
granulating_fluidGranulating Fluid300
coating_solutionCoating Solution400
empty_capsuleEmpty Capsule5

None of these need to be usable.

In-between products

ItemLabelWeight (g)
pill_milledMilled Powder100
pill_blendPressing Blend100

Neither needs to be usable.

Finished pills

All five are usable, because players can take them.

ItemLabelWeight (g)Must be usable
party_pillParty Pill20Yes
bar_pillNOVA Bar20Yes
blue_pillVELA 3020Yes
perc_pillAXIA 1020Yes
molly_capsuleAURA Capsule20Yes

Packed pills

Packing a batch turns its pills into the packed item for that line, so bags, bottles and blister cards each show their own name and icon. The count is still tablets: a bottle of 30 NOVA Bars is 30 bar_pill_bottle. All of them are usable, so players can take a pill from a pack.

ItemLabelWeight (g)Must be usable
party_pill_bagBagged Party Pill20Yes
party_pill_blisterBlister-Packed Party Pill20Yes
bar_pill_bagBagged NOVA Bar20Yes
bar_pill_bottleBottled NOVA Bar20Yes
bar_pill_blisterBlister-Packed NOVA Bar20Yes
blue_pill_bagBagged VELA 3020Yes
blue_pill_bottleBottled VELA 3020Yes
blue_pill_blisterBlister-Packed VELA 3020Yes
perc_pill_bagBagged AXIA 1020Yes
perc_pill_bottleBottled AXIA 1020Yes
perc_pill_blisterBlister-Packed AXIA 1020Yes
molly_capsule_bagBagged AURA Capsule20Yes
molly_capsule_blisterBlister-Packed AURA Capsule20Yes

Packaging and leftovers

ItemLabelWeight (g)
pill_baggiePill Baggie10
pill_bottlePill Bottle30
blister_sheetBlister Sheet10
pill_rejectsReject Tablets50

None of these need to be usable. The baggie, bottle and blister sheet are the empty packaging that gets used up when pills are packed. Packed pills are the packed pill items above.

Keeping batches apart on qb-inventory

When qb-inventory adds an item that isn't unique, it puts it on the first stack with the same name and throws away the new item's information. Without a fix, a second batch of pills would land on the first batch's stack and turn into that batch, and a second milled powder or blend would lose its grind and recipe. Players can also drag two stacks of the same item together.

The QBCore block below sets unique = true on every item that carries information: the five pills, all thirteen packed pills, pill_milled, pill_blend, pill_rejects, mystery_powder, punch_die_set and reagent_kit. A unique item still holds a count (one slot can hold 48 pills of a batch), but each batch keeps its own inventory slot, and qb-inventory won't let players merge them. If you already added the items without it, change those items to unique = true and restart the server.

ox_inventory keeps items with different information in separate stacks on its own, so two batches never merge there.

Register the items

Pick your framework. The item files ship in the resource folder, so you only paste.

Using qb-inventory (QBCore's default): add the items to qb-core/shared/items.lua. Open that file, paste the block below just above its final closing }, and save. useable = true is set on every item marked "must be usable" above, and unique = true on every item that carries information (see Keeping batches apart on qb-inventory).

qb-core/shared/items.lua
-- Workbench and machines
pill_workbench = { name = 'pill_workbench', label = 'Lab Workbench', weight = 8000, type = 'item', image = 'pill_workbench.png', unique = false, useable = true, shouldClose = true, description = 'A sturdy bench that holds pill lab machines' },
pill_crank_press = { name = 'pill_crank_press', label = 'Crank Press', weight = 6000, type = 'item', image = 'pill_crank_press.png', unique = false, useable = true, shouldClose = true, description = 'A hand-cranked single-punch tablet press' },
pill_grinder = { name = 'pill_grinder', label = 'Blade Grinder', weight = 2000, type = 'item', image = 'pill_grinder.png', unique = false, useable = true, shouldClose = true, description = 'Grinds powder finer than a mortar can' },
pill_mixer = { name = 'pill_mixer', label = 'Stand Mixer', weight = 4000, type = 'item', image = 'pill_mixer.png', unique = false, useable = true, shouldClose = true, description = 'Blends a powder batch evenly' },
pill_lightbox = { name = 'pill_lightbox', label = 'Light Box', weight = 2000, type = 'item', image = 'pill_lightbox.png', unique = false, useable = true, shouldClose = true, description = 'A backlit box and loupe for inspecting tablets' },
pill_engraver = { name = 'pill_engraver', label = 'Die Engraver', weight = 6000, type = 'item', image = 'pill_engraver.png', unique = false, useable = true, shouldClose = true, description = 'Engraves custom stamps onto punch tooling' },
pill_electric_press = { name = 'pill_electric_press', label = 'Electric Press', weight = 10000, type = 'item', image = 'pill_electric_press.png', unique = false, useable = true, shouldClose = true, description = 'A motorised single-punch tablet press' },
pill_label_printer = { name = 'pill_label_printer', label = 'Label Printer', weight = 3000, type = 'item', image = 'pill_label_printer.png', unique = false, useable = true, shouldClose = true, description = 'Prints pharmacy-style bottle labels' },
pill_hardness_tester = { name = 'pill_hardness_tester', label = 'Hardness Tester', weight = 3000, type = 'item', image = 'pill_hardness_tester.png', unique = false, useable = true, shouldClose = true, description = 'Measures how much force a tablet takes to break' },
pill_capsule_board = { name = 'pill_capsule_board', label = 'Capsule Filling Board', weight = 2000, type = 'item', image = 'pill_capsule_board.png', unique = false, useable = true, shouldClose = true, description = 'Fills a tray of capsules in one pass' },
pill_ball_mill = { name = 'pill_ball_mill', label = 'Ball Mill', weight = 6000, type = 'item', image = 'pill_ball_mill.png', unique = false, useable = true, shouldClose = true, description = 'A roller jar that mills powder evenly fine' },
pill_blister_sealer = { name = 'pill_blister_sealer', label = 'Blister Sealer', weight = 8000, type = 'item', image = 'pill_blister_sealer.png', unique = false, useable = true, shouldClose = true, description = 'Heat-seals tablets into blister packs' },
pill_reject_bin = { name = 'pill_reject_bin', label = 'Reject Bin', weight = 2000, type = 'item', image = 'pill_reject_bin.png', unique = false, useable = true, shouldClose = true, description = 'Catches tablets that fail inspection' },
pill_dust_collector = { name = 'pill_dust_collector', label = 'Dust Collector', weight = 8000, type = 'item', image = 'pill_dust_collector.png', unique = false, useable = true, shouldClose = true, description = 'Pulls airborne powder away from the press' },
pill_drum_tumbler = { name = 'pill_drum_tumbler', label = 'Drum Tumbler', weight = 12000, type = 'item', image = 'pill_drum_tumbler.png', unique = false, useable = true, shouldClose = true, description = 'A rotating drum for large, even blends' },
pill_pan_coater = { name = 'pill_pan_coater', label = 'Pan Coater', weight = 12000, type = 'item', image = 'pill_pan_coater.png', unique = false, useable = true, shouldClose = true, description = 'Tumbles tablets through a coating spray' },
pill_rotary_press = { name = 'pill_rotary_press', label = 'Rotary Press', weight = 25000, type = 'item', image = 'pill_rotary_press.png', unique = false, useable = true, shouldClose = true, description = 'A multi-station rotary tablet press' },

-- Hand tools and tooling
pill_mortar = { name = 'pill_mortar', label = 'Mortar & Pestle', weight = 1500, type = 'item', image = 'pill_mortar.png', unique = false, useable = true, shouldClose = true, description = 'A heavy stone bowl for grinding powder by hand' },
pill_manual = { name = 'pill_manual', label = 'Pressing Manual', weight = 300, type = 'item', image = 'pill_manual.png', unique = false, useable = true, shouldClose = true, description = 'A dog-eared guide to formulating and pressing tablets' },
pill_sieve = { name = 'pill_sieve', label = 'Granulating Screen', weight = 600, type = 'item', image = 'pill_sieve.png', unique = false, useable = true, shouldClose = true, description = 'A mesh screen for working a wet blend into granules. Use it at a workbench' },
dust_mask = { name = 'dust_mask', label = 'Dust Mask', weight = 100, type = 'item', image = 'dust_mask.png', unique = false, useable = true, shouldClose = true, description = 'Keeps airborne powder out of your lungs' },
reagent_kit = { name = 'reagent_kit', label = 'Reagent Test Kit', weight = 300, type = 'item', image = 'reagent_kit.png', unique = true, useable = true, shouldClose = true, description = 'A few drops tell you what a powder or pill really is' },
punch_die_set = { name = 'punch_die_set', label = 'Punch & Die Set', weight = 1200, type = 'item', image = 'punch_die_set.png', unique = true, useable = false, shouldClose = true, description = 'The tooling a press strikes tablets with. Wears down with use' },
cleaning_brush = { name = 'cleaning_brush', label = 'Cleaning Brush', weight = 200, type = 'item', image = 'cleaning_brush.png', unique = false, useable = false, shouldClose = true, description = 'A stiff brush for clearing residue off a punch face' },
cleaning_solvent = { name = 'cleaning_solvent', label = 'Cleaning Solvent', weight = 500, type = 'item', image = 'cleaning_solvent.png', unique = false, useable = false, shouldClose = true, description = 'Cuts through baked-on residue a brush cannot shift' },

-- Ingredients
party_powder = { name = 'party_powder', label = 'Party Powder', weight = 100, type = 'item', image = 'party_powder.png', unique = false, useable = false, shouldClose = true, description = 'An imported active powder. Needs milling before it is any use' },
pharma_bottle = { name = 'pharma_bottle', label = 'Pharma Bottle', weight = 200, type = 'item', image = 'pharma_bottle.png', unique = false, useable = false, shouldClose = true, description = 'A stock bottle of diverted prescription tablets' },
mystery_powder = { name = 'mystery_powder', label = 'Mystery Powder', weight = 100, type = 'item', image = 'mystery_powder.png', unique = true, useable = false, shouldClose = true, description = 'An unlabelled active powder. Only a reagent test says what it is' },
pill_filler = { name = 'pill_filler', label = 'Tablet Filler', weight = 100, type = 'item', image = 'pill_filler.png', unique = false, useable = false, shouldClose = true, description = 'Inert bulking powder that brings a dose into range' },
pill_binder = { name = 'pill_binder', label = 'Tablet Binder', weight = 100, type = 'item', image = 'pill_binder.png', unique = false, useable = false, shouldClose = true, description = 'Holds a pressed tablet together' },
pill_lubricant = { name = 'pill_lubricant', label = 'Die Lubricant', weight = 100, type = 'item', image = 'pill_lubricant.png', unique = false, useable = false, shouldClose = true, description = 'Stops powder sticking to the punch face' },
pill_dye = { name = 'pill_dye', label = 'Tablet Dye', weight = 100, type = 'item', image = 'pill_dye.png', unique = false, useable = false, shouldClose = true, description = 'Colours a batch of party pills' },
granulating_fluid = { name = 'granulating_fluid', label = 'Granulating Fluid', weight = 300, type = 'item', image = 'granulating_fluid.png', unique = false, useable = false, shouldClose = true, description = 'Wets a blend so it can be worked into granules' },
coating_solution = { name = 'coating_solution', label = 'Coating Solution', weight = 400, type = 'item', image = 'coating_solution.png', unique = false, useable = false, shouldClose = true, description = 'Sprayed over tablets in a pan coater to seal and finish them' },
empty_capsule = { name = 'empty_capsule', label = 'Empty Capsule', weight = 5, type = 'item', image = 'empty_capsule.png', unique = false, useable = false, shouldClose = true, description = 'A two-piece gelatin shell, waiting to be filled' },

-- In-between products
pill_milled = { name = 'pill_milled', label = 'Milled Powder', weight = 100, type = 'item', image = 'pill_milled.png', unique = true, useable = false, shouldClose = true, description = 'Ground active, ready to formulate' },
pill_blend = { name = 'pill_blend', label = 'Pressing Blend', weight = 100, type = 'item', image = 'pill_blend.png', unique = true, useable = false, shouldClose = true, description = 'A formulated, blended powder waiting for the press' },

-- Finished pills
party_pill = { name = 'party_pill', label = 'Party Pill', weight = 20, type = 'item', image = 'party_pill.png', unique = true, useable = true, shouldClose = true, description = 'A pressed tablet. What is in it is anyone\'s guess until it is checked' },
bar_pill = { name = 'bar_pill', label = 'NOVA Bar', weight = 20, type = 'item', image = 'bar_pill.png', unique = true, useable = true, shouldClose = true, description = 'A scored white bar' },
blue_pill = { name = 'blue_pill', label = 'VELA 30', weight = 20, type = 'item', image = 'blue_pill.png', unique = true, useable = true, shouldClose = true, description = 'A small round blue tablet made to pass for the pharmacy kind' },
perc_pill = { name = 'perc_pill', label = 'AXIA 10', weight = 20, type = 'item', image = 'perc_pill.png', unique = true, useable = true, shouldClose = true, description = 'A round white tablet made to pass for the pharmacy kind' },
molly_capsule = { name = 'molly_capsule', label = 'AURA Capsule', weight = 20, type = 'item', image = 'molly_capsule.png', unique = true, useable = true, shouldClose = true, description = 'A capsule of powder. Only a test says how much of it is real' },

-- Packed pills (the count is still tablets)
party_pill_bag = { name = 'party_pill_bag', label = 'Bagged Party Pill', weight = 20, type = 'item', image = 'party_pill_bag.png', unique = true, useable = true, shouldClose = true, description = 'Party pills in a zip baggie' },
party_pill_blister = { name = 'party_pill_blister', label = 'Blister-Packed Party Pill', weight = 20, type = 'item', image = 'party_pill_blister.png', unique = true, useable = true, shouldClose = true, description = 'Party pills sealed in a blister card' },
bar_pill_bag = { name = 'bar_pill_bag', label = 'Bagged NOVA Bar', weight = 20, type = 'item', image = 'bar_pill_bag.png', unique = true, useable = true, shouldClose = true, description = 'NOVA Bars in a zip baggie' },
bar_pill_bottle = { name = 'bar_pill_bottle', label = 'Bottled NOVA Bar', weight = 20, type = 'item', image = 'bar_pill_bottle.png', unique = true, useable = true, shouldClose = true, description = 'NOVA Bars in a labelled pharmacy bottle' },
bar_pill_blister = { name = 'bar_pill_blister', label = 'Blister-Packed NOVA Bar', weight = 20, type = 'item', image = 'bar_pill_blister.png', unique = true, useable = true, shouldClose = true, description = 'NOVA Bars sealed in a blister card' },
blue_pill_bag = { name = 'blue_pill_bag', label = 'Bagged VELA 30', weight = 20, type = 'item', image = 'blue_pill_bag.png', unique = true, useable = true, shouldClose = true, description = 'VELA 30s in a zip baggie' },
blue_pill_bottle = { name = 'blue_pill_bottle', label = 'Bottled VELA 30', weight = 20, type = 'item', image = 'blue_pill_bottle.png', unique = true, useable = true, shouldClose = true, description = 'VELA 30s in a labelled pharmacy bottle' },
blue_pill_blister = { name = 'blue_pill_blister', label = 'Blister-Packed VELA 30', weight = 20, type = 'item', image = 'blue_pill_blister.png', unique = true, useable = true, shouldClose = true, description = 'VELA 30s sealed in a blister card' },
perc_pill_bag = { name = 'perc_pill_bag', label = 'Bagged AXIA 10', weight = 20, type = 'item', image = 'perc_pill_bag.png', unique = true, useable = true, shouldClose = true, description = 'AXIA 10s in a zip baggie' },
perc_pill_bottle = { name = 'perc_pill_bottle', label = 'Bottled AXIA 10', weight = 20, type = 'item', image = 'perc_pill_bottle.png', unique = true, useable = true, shouldClose = true, description = 'AXIA 10s in a labelled pharmacy bottle' },
perc_pill_blister = { name = 'perc_pill_blister', label = 'Blister-Packed AXIA 10', weight = 20, type = 'item', image = 'perc_pill_blister.png', unique = true, useable = true, shouldClose = true, description = 'AXIA 10s sealed in a blister card' },
molly_capsule_bag = { name = 'molly_capsule_bag', label = 'Bagged AURA Capsule', weight = 20, type = 'item', image = 'molly_capsule_bag.png', unique = true, useable = true, shouldClose = true, description = 'AURA Capsules in a zip baggie' },
molly_capsule_blister = { name = 'molly_capsule_blister', label = 'Blister-Packed AURA Capsule', weight = 20, type = 'item', image = 'molly_capsule_blister.png', unique = true, useable = true, shouldClose = true, description = 'AURA Capsules sealed in a blister card' },

-- Packaging and leftovers
pill_baggie = { name = 'pill_baggie', label = 'Pill Baggie', weight = 10, type = 'item', image = 'pill_baggie.png', unique = false, useable = false, shouldClose = true, description = 'A small zip bag for a dozen tablets' },
pill_bottle = { name = 'pill_bottle', label = 'Pill Bottle', weight = 30, type = 'item', image = 'pill_bottle.png', unique = false, useable = false, shouldClose = true, description = 'An amber pharmacy bottle with a child-proof cap' },
blister_sheet = { name = 'blister_sheet', label = 'Blister Sheet', weight = 10, type = 'item', image = 'blister_sheet.png', unique = false, useable = false, shouldClose = true, description = 'Formed plastic and foil backing for ten tablets' },
pill_rejects = { name = 'pill_rejects', label = 'Reject Tablets', weight = 50, type = 'item', image = 'pill_rejects.png', unique = true, useable = false, shouldClose = true, description = 'Broken and malformed tablets. Worth regrinding' },

Running ox_inventory on QBCore? Skip the block above and follow the QBX tab instead: the file and the steps are the same.

QBX runs ox_inventory by default. Every item above is ready to paste in items/ox_inventory_items.lua in the resource folder.

  1. Open items/ox_inventory_items.lua from the oxide-pills folder in a text editor.
  2. Copy every line between return { and the final }.
  3. Open ox_inventory/data/items.lua and paste those lines just above that file's final closing }.
  4. Save the file.

ox_inventory has no separate "usable" switch to set: pills registers the items it uses itself. ox_inventory also keeps items with different information in separate stacks on its own, so two batches never merge. The file already sets stack = false on punch_die_set and reagent_kit. If your items.lua already has an item with the same name, keep only one copy.

Check the laundering setting on QBX. The default takes black_money items, which matches a stock Qbox install. If your server uses markedbills instead, change it after the first start (see step 7).

ESX needs ox_inventory. The steps are the same as on QBX:

  1. Open items/ox_inventory_items.lua from the oxide-pills folder in a text editor.
  2. Copy every line between return { and the final }.
  3. Open ox_inventory/data/items.lua and paste those lines just above that file's final closing }.
  4. Save the file.

ox_inventory has no separate "usable" switch to set: pills registers the items it uses itself, and keeps different batches in separate stacks on its own.

ESX's own built-in inventory and hex_4_inventory can't run oxide-pills. o-link doesn't support ESX's built-in inventory, so pills can't give or take any item with it, and hex_4_inventory drops item metadata. Install ox_inventory. See Which inventory you need.

Notes:

  • The labels, weights and descriptions are suggestions. Change them to taste. The names must match exactly.
  • Don't skip the in-between items. If pill_milled doesn't exist, grinding fails, and so does every step after it.

5. Copy the item images

Copy all the .png files from items/ in the resource folder into your inventory's image folder:

  • qb-inventory: qb-inventory/html/images/
  • ox_inventory: ox_inventory/web/images/

The file names already match the item names, so no renaming is needed.

When steps 4 and 5 are done, restart your server so the inventory loads the new items and images.

6. Give players a way to get ingredients

Nothing in oxide-pills sells the raw materials. Decide how players get them: a shop, a black-market dealer, a job reward, or anything else your server uses. These are the items players can't make themselves:

  • Stations and tools: every item in the Workbench and machines and Hand tools and tooling tables
  • Raw actives: party_powder, pharma_bottle, mystery_powder
  • Additives: pill_filler, pill_binder, pill_lubricant, pill_dye, granulating_fluid, coating_solution, empty_capsule
  • Packaging: pill_baggie, pill_bottle, blister_sheet

Players make everything else (pill_milled, pill_blend, the five pills and their packed items, and pill_rejects).

7. Configure it (in-game)

The defaults work out of the box, so you can open to players right away and adjust later.

When you do want to change something, do it in game, not by editing files. As an admin, run /pills settings to open the settings menu. Almost every setting is there (stage timings, level requirements, quality tuning, packaging, pills breaking, overdoses, the delivery run, police alerts, and the in-world placement zones). Changes apply live and save automatically. See Configuration for how this works, what every setting does, and the few settings that live only in the config files.

Config files are factory defaults

The shared/config files are the resource's factory defaults: the values it starts from on first launch. After the first start, the database holds your settings, and editing those files won't change most of them. Change settings with /pills settings instead.

Worth a look first, all in the menu:

  • Core → General: ownership, distances
  • Core → Placement Zones: where stations may be placed
  • Lab → Machines: which level each machine needs
  • Players → World: pills breaking, dust, overdoses, reagent tests, police evidence
  • Players → Selling: brand reputation and the pharma delivery run
  • Advanced → Dispatch: police alerts (off by default)

Laundering uses your framework's dirty money. Each delivery drop can wash dirty money. The default setting (The framework default) takes markedbills items (with their worth stored on each bill) on QBCore, black_money items on QBX, and the black_money account on ESX. If your server keeps dirty money some other way, change Players → Selling → Pharma delivery run → Laundering. See Configuration.

Optional Integrations

Everything below is optional. Pills runs fine without any of it.

ResourceWhat it addsWhat happens without it
oxide-drugselling (2.0.0 or newer)Players sell pills to NPCs on the street. Each buyer judges the batch in their own way, and branded pills build their brand's reputation.Pills can still be taken, and blister-packed pharma pills can still go on the delivery run.
oxide-tabletThe Pill ID lookup appears as an app on the tablet./pills pillid opens Pill ID on its own.
oxide-medicalAn overdose is recorded as a medical condition that EMS can see and treat.Pills plays its own overdose effect: a stumble, a blurred screen and lost health.
A dispatch resource o-link supports (oxide-dispatch, ps-dispatch, cd_dispatch, qs_dispatch, lb-tablet and others)Police alerts go into your dispatch system, at the station's location.Players with the alert job get a short notification and a 30-second map blip on the lab.
oxide-gangsGang territories can speed up the machines that run themselves, increase output, and raise delivery pay. Delivery drops are reported to oxide-gangs as drug sales.Nothing changes.

oxide-drugselling

Street sales need oxide-drugselling 2.0.0 or newer. That's the first version that knows about pills; older versions can't sell them at all.

To check yours, open oxide-drugselling/shared/config/drugs.lua in a text editor and look for a section that starts with Config.Drugs.pills. If it's there, your version is new enough: pills are sellable and priced by oxide-pills. If it isn't, update oxide-drugselling to 2.0.0 or newer.

That section sets each pill's base street price. oxide-pills then adjusts the price for every sale, based on who the buyer is, how good the batch is, how it's packed, and how well known its brand is. See Selling: Buyers and Brand Reputation.

Check That It Works

Start the server and confirm ox_lib, oxmysql, o-link, and oxide-pills all start without errors in the console.

Give yourself admin permission (see Admin), make room in your inventory, then run /pills kit in game.

  • The kit is one of every station plus tools, ingredients and packaging. With the suggested weights it comes to about 139 kg, which is more than a default player inventory holds on QBCore, QBX or ESX. Anything that doesn't fit, or that isn't registered in your inventory yet, is skipped, and you see "N of M kit items could not be given (inventory full or item not registered)" instead of "Pill lab kit received".
  • This check only needs these items: pill_workbench, pill_mixer, pill_crank_press, pill_mortar, party_powder, pill_filler, pill_binder, pill_lubricant, punch_die_set and one pill_baggie (about 21 kg together).
  • If any of them is missing, put the items you don't need in a stash or drop them, then give yourself the missing ones with your inventory's give-item command. On both qb-inventory and ox_inventory that's /giveitem [your server ID] [item name] [amount], for example /giveitem 1 pill_mixer 1.

Use the Lab Workbench item and place it. The workbench holds 2 machines by default, and the kit only gives you one, so for this test place just the Stand Mixer and the Crank Press on it.

Stand at the workbench and use the Mortar & Pestle item. If it asks what you're grinding (it does when you carry more than one kind of powder), pick Party Powder. Move the mouse in circles until the grind meter reaches the good zone, then press ENTER. You should receive Milled Powder.

At the mixer, choose Mix a Batch. Pick up each container and hold the left mouse button over the bowl to pour: the powder, filler, binder and a little lubricant. Press SPACE to put the bowl on, then SPACE again to start. You should receive a Pressing Blend.

At the press, choose Press Tablets. Set the fill depth and force with the arrow keys, press SPACE, then circle the mouse to turn the crank. Press ENTER to stop early if you like.

Sort the tray: hover a tablet to read it, click to throw it out, then press ENTER. You should receive Party Pills.

Aim at the workbench itself, choose Bag Tablets, and bag a dozen.

Run /pills stats and check your level and XP card appears.

If any step fails, see Troubleshooting.

Good To Know

  • Each workbench holds 2 machines by default (Lab → Workbench). There are 11 benchtop machines, so a full lab needs several workbenches. The workbench itself is also where the mortar and sieve are used and where pills are bagged.
  • Quality stays with the batch. A batch's hidden numbers are stored in the database against a batch ID that rides on every pill, so the quality travels with the pill when it's traded or stored.
  • Pills made any other way have no batch. A pill spawned with an admin give-item command has no batch ID. It can't be inspected, packed or tested, and taking it uses it up with no effect. Use the lab to make test pills.
  • This resource streams its own models. Everything in stream/ loads automatically. The workbench is a base-game table (bkr_prop_weed_table_01b).
  • The interface ships ready. There is nothing to build, compile, or install for it.
  • Translations: the on-screen notifications, prompts, and menu text live in locales/en.json and can be translated there.

Next Steps