Configuration Reference
Reference for the shipped configuration files and pricing definitions in Oxide Drugselling.
Reference for the shipped config files in oxide-drugselling.
shared/config.lua
Debug
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Debug | boolean | false | Enables debug logging for sale flow, inventory grouping, pricing, and export fallbacks. |
Config.Debug = falseDirty Money
| Setting | Type | Default | Description |
|---|---|---|---|
Config.DirtyMoney.mode | string | 'account' | 'account' uses o-link.money; 'item' uses o-link.inventory. |
Config.DirtyMoney.account | string | 'cash' | Account name used in account mode. The default is clean cash — see the Installation guide for the dirty-money setting for your framework. |
Config.DirtyMoney.item | string | 'black_money' | Item name used in item mode. |
Config.DirtyMoney = {
mode = 'account',
account = 'cash',
item = 'black_money',
}Sale Hook
| Setting | Type | Default | Description |
|---|---|---|---|
Config.OnDrugSold | function | empty | Runs on the server after every completed sale. Write whatever you need inside it — trigger an event, call another resource's export, send a webhook. Leave it empty to do nothing. |
The function receives a single saleData table: source, charId, drugType, itemName, quantity, finalPrice, metaKey, and mode. Your code is run safely — a mistake in here is logged and the sale still completes.
Config.OnDrugSold = function(saleData)
-- Example: pay a turf-control system on every sale
local turfId = exports['op-crime']:getPlayerTurfZone(saleData.source)
if turfId then
TriggerEvent('op-crime:drugSold', saleData.source, turfId, saleData.finalPrice)
end
endIf you would rather react from your own resource than edit this file, the same data is also broadcast as the oxide-drugselling:drugSold server event — see API Reference.
shared/config/selling.lua
General
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Selling.enabled | boolean | true | Master toggle for the selling system. |
Config.Selling.targetIcon | string | 'fas fa-hand-holding-dollar' | Icon sent to the configured o-link target backend. |
Config.Selling.targetLabel | string | 'sell_offer_drugs' | Locale key used as the interaction label. |
Config.Selling.targetDistance | number | 5.0 | Interaction distance. |
Config.Selling.panelTitle | string | 'Street Sale' | Title shown in the sell panel. |
Config.Selling = {
enabled = true,
targetIcon = 'fas fa-hand-holding-dollar',
targetLabel = 'sell_offer_drugs',
targetDistance = 5.0,
panelTitle = 'Street Sale',
}Cooldowns
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Selling.cooldown | number | 5000 | Player cooldown after cleanup of any interaction. |
Config.Selling.npcCooldown | number | 120000 | Cooldown applied to the NPC location key. |
Police
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Selling.policeJobs | table | { 'police', 'sheriff' } | Job names counted through o-link.job.GetPlayersWithJob. |
Config.Selling.minPoliceOnline | number | 0 | Minimum online police count required to sell. |
Pricing
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Selling.pricing.autoAcceptMax | number | 0.90 | Auto-accept threshold ratio. |
Config.Selling.pricing.negotiateMax | number | 1.25 | Maximum ratio that can still negotiate instead of rejecting. |
Config.Selling.pricing.counterOffer | number | 0.95 | Counter-offer fraction of fair total price. |
pricing = {
autoAcceptMax = 0.90,
negotiateMax = 1.25,
counterOffer = 0.95,
}Risks
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Selling.risks.policeCallChance | number | 0.15 | Chance to dispatch police on any interaction. |
Config.Selling.risks.rejectChance | number | 0.15 | Flat rejection chance before price logic. |
Config.Selling.risks.robberyChance | number | 0.15 | Robbery chance for gang NPCs only. |
Config.Selling.risks.robberyItemLoss | number | 3 | Maximum items stolen from the offered stack. |
Config.Selling.risks.robberyCashPercent | number | 0.25 | Percent of current dirty-money balance stolen. |
risks = {
policeCallChance = 0.15,
rejectChance = 0.15,
robberyChance = 0.15,
robberyItemLoss = 3,
robberyCashPercent = 0.25,
}Dispatch
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Selling.dispatch.code | string | '10-31' | Alert code sent through o-link.dispatch. |
Config.Selling.dispatch.message | string | 'sell_dispatch_message' | Locale key for the dispatch message. |
Config.Selling.dispatch.blipData.sprite | number | 469 | Map blip sprite. |
Config.Selling.dispatch.blipData.color | number | 1 | Map blip color. |
Config.Selling.dispatch.blipData.scale | number | 0.8 | Map blip scale. |
Config.Selling.dispatch.time | number | 60000 | Alert duration in milliseconds. |
dispatch = {
code = '10-31',
message = 'sell_dispatch_message',
blipData = { sprite = 469, color = 1, scale = 0.8 },
time = 60000,
}Ped Model Lists
blacklistedModels defines models that can never be used for selling.
gangModels defines models that can roll the robbery outcome and display the dangerous-state UI.
Both lists use GTA model hashes in backtick notation:
blacklistedModels = {
`s_m_y_cop_01`, `s_f_y_cop_01`, `s_m_y_sheriff_01`,
`s_m_y_ranger_01`, `s_m_y_marine_01`, `s_m_y_marine_02`,
`s_m_y_marine_03`, `s_m_m_paramedic_01`, `s_m_m_security_01`,
`csb_cop`,
}gangModels = {
`g_m_y_famca_01`, `g_m_y_famdnf_01`, `g_m_y_famfor_01`,
`g_m_y_ballaorig_01`, `g_m_y_ballaeast_01`, `g_m_y_ballasout_01`,
`g_m_y_lost_01`, `g_m_y_lost_02`, `g_m_y_lost_03`,
`g_m_y_mexgang_01`, `g_m_y_mexgoon_01`, `g_m_y_mexgoon_02`,
`g_m_y_salvaboss_01`, `g_m_y_salvagoon_01`,
`g_m_y_korean_01`, `g_m_y_korean_02`,
}Camera
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Selling.camera.interpTime | number | 500 | Camera transition time. |
Config.Selling.camera.distance | number | 1.8 | Forward offset from the NPC. |
Config.Selling.camera.heightOffset | number | 0.3 | Vertical camera offset. |
Config.Selling.camera.fov | number | 50.0 | Camera field of view. |
Animations
Each animation entry has dict, clip, and duration.
anims = {
npcThinking = { dict = 'amb@world_human_smoking@male@male_a@enter', clip = 'enter', duration = 2000 },
npcAccept = { dict = 'mp_common', clip = 'givetake1_a', duration = 1800 },
npcReject = { dict = 'gestures@m@standing@casual', clip = 'gesture_no_way', duration = 1000 },
}shared/config/drugs.lua
This file defines every sellable drug type. The shipped config includes weed, meth, and cocaine.
Drug Type Shape
Config.Drugs.drugkey = {
label = 'Display Name',
items = { ... },
metaKeyFormat = '{field1}:{field2}',
groupLabel = 'metadata_field',
gradeDisplay = { ... },
progression = { ... },
requiredLevel = 0,
modifiers = { ... },
}Items
items maps item names to display labels and base prices. Only configured items are sellable.
items = {
weed_1g = { label = 'Weed (1g)', basePrice = 25 },
weed_eighth = { label = 'Weed (1/8 oz)', basePrice = 75 },
}Metadata Grouping
metaKeyFormat controls how stacks are grouped. The format string is resolved by replacing {field} tokens with values from item metadata through BuildMetaKey.
Examples from the shipped config:
- weed:
'{strain_id}:{quality}' - meth:
'{variant}:{purity}' - cocaine:
'{purity}'(a single field is fine — it groups bags and bricks by purity grade only)
groupLabel defines which metadata field is shown as the primary label for a group. It is optional — if you leave it out (as the shipped cocaine config does), each item's own label is used instead.
Examples:
- weed:
strain_name - meth:
variant_name - cocaine: not set (falls back to the item label)
Grade Display
gradeDisplay controls how grades are shown in the panel.
| Field | Purpose |
|---|---|
gradeMeta | Metadata field containing the textual grade |
scoreMeta | Optional numeric score field shown in the panel |
colors | Tailwind classes keyed by grade |
Shipped examples:
- weed uses
quality+quality_score - meth uses
purityand no score field - cocaine uses
purityand no score field
Progression
progression is optional.
| Setting | Description |
|---|---|
resource | Resource name to call exports on |
levelExport | Export used to fetch player level |
addXpExport | Export used to award XP |
addSoldExport | Export used to track sold units |
xpPerUnit | XP awarded per unit sold |
requiredLevel applies only when the source resource is running and the level export succeeds. If the resource is not started, the sale is allowed and tracking is skipped.
Shipped defaults: the weed config sets requiredLevel = 2, so while oxide-weed is running, players below weed level 2 cannot sell weed (they get a "Requires level 2" notification). The meth and cocaine configs set requiredLevel = 0 — no level requirement (XP and sold-count tracking still flow back on every sale).
Modifiers
modifiers is an ordered array. Each modifier contributes either a multiplier or a bonus.
Required base fields:
| Setting | Description |
|---|---|
type | Modifier type |
category | 'multiplier' or 'bonus' |
Supported shipped modifier types:
grade_multiplier
{
type = 'grade_multiplier',
category = 'multiplier',
field = 'purity',
grades = { Pure = 1.4, High = 1.15, Mid = 1.0, Low = 0.75, Dirty = 0.5 },
}score_multiplier
{
type = 'score_multiplier',
category = 'multiplier',
field = 'quality_score',
minMultiplier = 0.4,
maxMultiplier = 1.5,
curve = 1.2,
fallback = {
type = 'grade_multiplier',
field = 'quality',
grades = { Gas = 1.4, High = 1.15, Mid = 1.0, Poor = 0.75, Dirt = 0.5 },
},
}variant_multiplier
{
type = 'variant_multiplier',
category = 'multiplier',
field = 'variant',
variants = {
crystal = 1.0,
glass = 1.3,
blue_sky = 1.6,
street_mix = 0.7,
},
display = { label = 'Variant', color = 'text-blue-400' },
}trait_bonus
{
type = 'trait_bonus',
category = 'bonus',
sourceField = 'strain_id',
exportResource = 'oxide-weed',
exportName = 'GetStrainData',
traitsPath = 'traits',
traits = {
thc = { threshold = 75, bonus = 0.10, label = 'THC' },
potency = { threshold = 70, bonus = 0.08, label = 'POT' },
},
maxBonus = 0.35,
}metadata_bonus
{
type = 'metadata_bonus',
category = 'bonus',
field = 'generation',
startValue = 2,
bonusPerStep = 0.05,
maxBonus = 0.25,
display = { label = 'Crossbred', color = 'text-cyan-400' },
}Adding a New Drug Type
To add a new drug type:
- add a new entry under
Config.Drugs - define item names that already exist in your inventory system
- define the metadata format used to group stacks
- define
gradeDisplay - add optional progression hooks
- define the ordered modifier pipeline
Example:
Weed, meth, and cocaine already ship configured (see Features). This example shows how you would add your own drug on top of them:
Config.Drugs.heroin = {
label = 'Heroin',
items = {
heroin_baggy = { label = 'Heroin Baggy', basePrice = 150 },
heroin_brick = { label = 'Heroin Brick', basePrice = 2200 },
},
metaKeyFormat = '{purity}',
groupLabel = 'purity_label',
gradeDisplay = {
gradeMeta = 'purity',
scoreMeta = nil,
colors = {
Pure = 'text-purple-400',
Cut = 'text-yellow-400',
Weak = 'text-red-400',
},
},
requiredLevel = 0,
modifiers = {
{
type = 'grade_multiplier',
category = 'multiplier',
field = 'purity',
grades = { Pure = 1.5, Cut = 1.0, Weak = 0.6 },
},
},
}No code changes are required if the config and item metadata are valid.
Configuration Strategy
Start by aligning these three areas with your server:
Config.DirtyMoneyfor payout behaviorConfig.Sellingfor interaction, police, and risk behaviorConfig.Drugsfor your actual item names, metadata shape, and source resource integrations