Configuration Reference
How Oxide Drugselling stores its settings, the in-game settings menu, and every setting — street selling, heat, witnesses, corners, the trap phone, clients, meets, dead drops, reputation, evidence, bulk buyers, stings, and the drugs file.
oxide-drugselling is configured in-game, from a settings menu. This page explains how that works, then lists every setting so you know what each one changes.
How Configuration Is Stored
oxide-drugselling keeps its live settings in your database, in a shared table called oxide_settings. The files in shared/ are the factory defaults: the values the resource ships with. The table is created automatically on first start.
- The first time the resource starts, the values from the config files are copied into
oxide_settings. If you edited the files before that first start, your edits are what gets copied. - From then on, the database is what the resource reads. Editing a config file on a server that has already started once does not change a setting that's stored in the database.
- When an update adds a new setting, it isn't in your database yet, so it's copied from the updated file on the next start. You don't need to do anything.
So: to change a setting on a running server, use the menu.
The in-game settings menu
As an admin, run /drugselling settings. Changes apply live and are saved to the database straight away, with no restart.
- Settings are grouped into four categories in the sidebar: Core (General, Street, Heat & Witnesses, Corners), Phone (Phone, Clients, Orders & Meets, Dead Drops, Reputation), Pressure (Evidence, Bulk Buyers, Stings) and Advanced (Compatibility).
- A Basic / Advanced switch at the top picks which settings you see. Basic shows the everyday settings. Advanced shows only Debug logging, Time of day, No-signal areas, Evidence bridges and Model swaps.
- A search box finds a setting in the view you're on.
- An edited but unsaved field is marked changed. Save Changes saves everything you edited.
- Drop spots have a Place in world button: the menu hides, you pick the spot on the ground, and the menu comes back. Teleport here takes you to a spot.
- No-signal areas are drawn in the world: Draw new zone, place the corners of the area, and confirm.
- Item fields have a picker listing the items your inventory knows about.
/drugselling corners and /drugselling drops open the same menu straight on the Corners and Dead Drops sections. See Admin.
Almost every change takes effect the moment you save. A few things to know:
- Turning Corners → Enabled off closes every corner currently being worked ("Corner selling was switched off"). Other corner changes apply the next time they're used, including on corners already being worked.
- Renaming Corners → Command takes effect after a restart.
- Renaming the phone, SIM, charger or sting phone item registers the new name live. The new item must exist in your inventory.
- Saving a setting that fails a check (for example corners switched on with an empty Demand list) is refused, and the field is highlighted with the reason.
Developers can also read and write settings from another resource. See Exports → GetSetting.
The setting names below (like Config.Selling.minPoliceOnline) are the names in the config files. The menu shows a plain label instead (like "Police needed online"). The tables list both.
Before you press Reset to default
Reset to default puts a whole setting back to its value in the config files and saves it straight away. It resets everything in that setting:
- Resetting Corners also resets its Demand list and its buyer model and vehicle lists.
- Resetting Dead drops replaces your drop spots with the 10 default spots.
- Resetting No-signal areas deletes every area you drew.
"Default" means the config file as it was when the server last started.
File-Only Settings
These are never stored in the database. The config file always wins. To change one: edit the file, save it, and restart the server.
| Setting | File | What it is |
|---|---|---|
Config.Drugs | shared/config/drugs.lua | Every sellable drug and how it's priced. See Drugs |
Config.OnDrugSold | shared/config.lua | Your own code that runs after every sale |
Config.OnPhoneExamined | shared/config.lua | Your own code that runs when an officer opens a seized phone |
Config.SettingsCommand | shared/config.lua | The command named in missing-model warnings ('/drugselling settings') |
Config.ModelHints | shared/config.lua | Marks models that ship with the resource or come from an add-on, for the missing-model warning. None ship |
A few values sit inside a setting the menu shows, but aren't shown themselves. They're marked (not in menu) in the tables below, and they're read from the config file on every start: Config.Selling.targetIcon, Config.Selling.targetLabel and Config.Selling.anims. (If a saved setting ever fails its check when the server starts, it's loaded exactly as saved and these file values are ignored until you save it again in the menu.)
Sale hook
Config.OnDrugSold runs on the server after every completed sale, whichever way it was made (for a dead drop, when the client collects the package). Leave it empty to do nothing. It gets one table:
| Field | Type | What it is |
|---|---|---|
source | number | The seller's server ID |
charId | string | The seller's character identifier |
drugType | string | The drug's key in Config.Drugs (for example weed) |
itemName | string | The item sold |
quantity | number | Units sold |
xpUnits | number | Progression units for this sale: quantity times the item's xpUnits, or quantity when that row leaves it out |
finalPrice | number | The sale price, after any gang territory bonus. For a dead drop, the cash paid or left at the drop |
metaKey | string | Which group the stack belonged to (strain, purity, batch) |
mode | string | The Sale money mode: 'auto', 'account' or 'item' |
method | string | How it was sold: 'street', 'corner', 'phone', 'drop' or 'bulk' |
Your code runs safely: a mistake in it is logged, and the sale still completes.
Config.OnDrugSold = function(saleData)
-- Example: pay a turf-control system on every street and corner sale
if saleData.method ~= 'street' and saleData.method ~= 'corner' then return end
local turfId = exports['op-crime']:getPlayerTurfZone(saleData.source)
if turfId then
TriggerEvent('op-crime:drugSold', saleData.source, turfId, saleData.finalPrice)
end
endPhone examined hook
Config.OnPhoneExamined runs on the server every time an officer opens a seized phone. src is the officer's server ID, and report holds the phone's number, contacts, meets and texts, plus report.text, a plain-text copy (see Evidence Bridge → The report).
Config.OnPhoneExamined = function(src, report)
endshared/config.lua is also sent to every player's game. Don't put passwords or webhook URLs in it. To send reports somewhere, use the server event from your own resource instead (see Evidence Bridge → Layer 2: Hooks).
If you'd rather not edit this file, both hooks are also server events. See Exports → Server Events.
The Config Files
| File | What's in it | Menu location |
|---|---|---|
shared/config.lua | Debug, sale money, model swaps, and the two hooks | Core → General, Advanced → Compatibility |
shared/config/selling.lua | Street selling, heat, witnesses, time of day | Core → Street, Core → Heat & Witnesses |
shared/config/corners.lua | Corners and corner buyers | Core → Corners |
shared/config/phone.lua | The phone, clients, orders, meets, dead drops, reputation, no-signal areas | Phone |
shared/config/pressure.lua | Evidence, evidence bridges, bulk buyers, stings | Pressure |
shared/config/drugs.lua | Sellable drugs and their pricing | File only |
General
Menu: Core → General
| Setting | Menu label | Type | Default | What it does |
|---|---|---|---|---|
Config.Debug | Debug logging (Advanced) | boolean | false | Writes extra detail to the server log. Leave off on a live server. |
Config.DirtyMoney.mode | Sale money → Paid as | string | 'auto' | Where sale money goes, and where robberies take it from. 'auto' (Framework dirty money): marked bills on QBCore, the black_money item on QBX, the black_money account on ESX. 'account' (An account): the account below. 'item' (An item ($1 each)): the item below. |
Config.DirtyMoney.account | Sale money → Account | string | 'black_money' | The account used when Paid as is An account. |
Config.DirtyMoney.item | Sale money → Item | string | 'black_money' | The item used when Paid as is An item. Each one is worth $1. It must exist in your inventory. |
Config.Debug = false
Config.DirtyMoney = {
mode = 'auto', -- 'auto' | 'account' | 'item'
account = 'black_money',
item = 'black_money',
}Street
Menu: Core → Street → Street selling. These settings also apply to corner buyers, unless the Corners section says otherwise.
General
| Setting | Menu label | Type | Default | What it does |
|---|---|---|---|---|
Config.Selling.enabled | Enabled | boolean | true | Turns the Offer Drugs option on pedestrians on or off. |
Config.Selling.targetIcon | (not in menu) | string | 'fas fa-hand-holding-dollar' | The Font Awesome icon on the Offer Drugs option. |
Config.Selling.targetLabel | (not in menu) | string | 'sell_offer_drugs' | The translation key for the option's text. The words are in locales/en.json. |
Config.Selling.targetDistance | Reach (m) | number | 5.0 | How close a player must be to a pedestrian to see Offer Drugs. 1 to 10. |
Config.Selling.panelTitle | Panel title | string | 'Street Sale' | The title at the top of the sell menu for street sales. |
Config.Selling.cooldown | Cooldown between sales (ms) | number | 5000 | How long after a sale before the player can offer to someone else. 5000 = 5 seconds. |
Config.Selling.npcCooldown | Same-buyer cooldown (ms) | number | 120000 | How long before anyone can sell to the same pedestrian again. 120000 = 2 minutes. Kept by the server. |
Config.Selling.policeJobs | Police jobs | table | { 'police', 'sheriff' } | The jobs that receive every police alert and count toward Police needed online. Must match your job names exactly. |
Config.Selling.minPoliceOnline | Police needed online | number | 0 | How many players in those jobs must be online before anyone can sell: on the street, on a corner, at a phone meet or by leaving a dead drop. 0 = no requirement. |
Config.Selling.onDutyOnly | Only on-duty police | boolean | true | Only on-duty officers count toward the minimum, can read seized phones and can use the sting phone. With most dispatch resources, alerts are passed on through an on-duty officer's game. Without a dispatch resource, alerts go to every player with a police job, on duty or not. Turn it off if your framework has no duty system, or no officer will ever count. |
Pricing
| Setting | Menu label | Type | Default | What it does |
|---|---|---|---|---|
Config.Selling.pricing.autoAcceptMax | Accepted at or below | number | 0.90 | An offer at or below this share of the fair price is accepted. 0.90 = 90%. |
Config.Selling.pricing.negotiateMax | Negotiable up to | number | 1.25 | An offer up to this share can still be accepted or countered. Above it, it's refused. Can't be lower than Accepted at or below. |
Config.Selling.pricing.negotiateAcceptChance | Accepted outright in that band | number | 0.5 | The chance an offer between the two limits is accepted instead of countered. 0.5 = 50%. |
Config.Selling.pricing.counterOffer | Counter-offer | number | 0.95 | The buyer's counter-offer, as a share of the fair price. |
Config.Selling.pricing.counterExpiry | Counter stays open (s) | number | 60 | Seconds the player has to accept a counter-offer. |
The sell menu's Their reaction meter uses these same limits, so what it shows always matches what the server does.
Risks
| Setting | Menu label | Type | Default | What it does |
|---|---|---|---|---|
Config.Selling.risks.policeCallChance | Police alert | number | 0.15 | The chance each street or corner offer alerts police. 0.15 = 15%. Heat adds to it. |
Config.Selling.risks.rejectChance | Refusal | number | 0.15 | The chance a buyer refuses before price is considered. Time of day, gang territory and heat change it. |
Config.Selling.risks.robberyChance | Robbery (gang NPCs) | number | 0.15 | The chance a gang-model buyer robs the player instead. Heat adds to it. |
Config.Selling.risks.robberyItemLoss | Units taken | number | 3 | The most units a street or corner robber takes. |
Config.Selling.risks.robberyCashPercent | Share of dirty money taken | number | 0.25 | The share of the player's dirty money a robber takes. Also used when a phone client robs a player at a meet. |
Config.Selling.risks.robberyWeapon | Robber weapon | string | 'WEAPON_PISTOL' | The weapon a street, corner or phone-client robber pulls. |
Police alert
These settings are used for every alert from a street sale, corner sale or phone meet.
| Setting | Menu label | Type | Default | What it does |
|---|---|---|---|---|
Config.Selling.dispatch.code | Code | string | '10-31' | The code shown on the alert. |
Config.Selling.dispatch.message | Message locale key | string | 'sell_dispatch_message' | The translation key for the alert text ("Suspicious activity reported"). |
Config.Selling.dispatch.blipData.sprite | Blip → Sprite | number | 469 | The map blip icon. |
Config.Selling.dispatch.blipData.color | Blip → Colour | number | 1 | The map blip colour (1 = red). |
Config.Selling.dispatch.blipData.scale | Blip → Scale | number | 0.8 | The map blip size. |
Config.Selling.dispatch.time | Blip time (ms) | number | 60000 | How long the alert stays. 60000 = 1 minute. |
Buyers, camera and animations
| Setting | Menu label | Type | Default | What it does |
|---|---|---|---|---|
Config.Selling.blacklistedModels | Never sell to | table | cops, sheriffs, rangers, marines, paramedics, security | Pedestrian models that never get Offer Drugs. |
Config.Selling.gangModels | Gang models | table | 16 gang members | Pedestrian models that can rob the player, and show the Dangerous warning. Used on corners too. |
Config.Selling.camera.interpTime | Camera → Blend (ms) | number | 500 | How long the camera takes to move in on the buyer. |
Config.Selling.camera.distance | Camera → Distance | number | 1.8 | How far in front of the buyer the camera sits. |
Config.Selling.camera.heightOffset | Camera → Height | number | 0.3 | How high the camera sits. |
Config.Selling.camera.fov | Camera → Field of view | number | 50.0 | The camera's zoom. Lower is closer. |
Config.Selling.anims | (not in menu) | table | see below | The buyer's thinking, accept and refuse animations. |
Model lists use plain model names in quotes:
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',
},
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 },
},Heat And Witnesses
Menu: Core → Heat & Witnesses. See Features → Heat for how heat plays out in game.
Heat
| Setting | Menu label | Type | Default | What it does |
|---|---|---|---|---|
Config.Heat.enabled | Enabled | boolean | true | Turns heat on or off. With heat off nothing gains heat, so once old heat has cooled off, police stings can't find a dealer. |
Config.Heat.cellSize | Area size (m) | number | 250.0 | The map is split into squares this wide. Each square has its own area heat. |
Config.Heat.max | Maximum heat | number | 100 | The most heat anything can have. The effects below are reached at this value. |
Config.Heat.gain.sale | Heat gained → Per sale | number | 2.0 | Heat every sale adds. |
Config.Heat.gain.perUnit | Heat gained → Per unit | number | 0.25 | Extra heat per unit sold. |
Config.Heat.gain.witness | Heat gained → Witness calls it in | number | 4.0 | Heat each snitching witness adds. |
Config.Heat.gain.alert | Heat gained → Police alert | number | 8.0 | Heat each police alert adds to the area. A completed sting adds double. |
Config.Heat.gain.corner | Heat gained → Corner multiplier | number | 1.5 | Corner sales multiply their heat by this. |
Config.Heat.gain.robbery | Heat gained → Robbery | number | 5.0 | Heat a robbery adds to the area. |
Config.Heat.decay.area | Lost per minute → Area | number | 1.5 | Area heat lost per real minute. |
Config.Heat.decay.corner | Lost per minute → Corner | number | 2.0 | Corner heat lost per real minute. |
Config.Heat.decay.player | Lost per minute → Dealer | number | 1.0 | A character's own heat lost per real minute. |
Config.Heat.decay.number | Lost per minute → Phone number | number | 0.75 | A phone number's heat lost per real minute. |
Config.Heat.effects.refusal | At maximum heat → Extra refusal chance | number | 0.25 | Extra refusal chance at maximum heat. Less heat, less extra, in a straight line. |
Config.Heat.effects.alert | At maximum heat → Extra alert chance | number | 0.30 | Extra police alert chance at maximum heat. |
Config.Heat.effects.robbery | At maximum heat → Extra robbery chance | number | 0.10 | Extra robbery chance at maximum heat. |
Config.Heat = {
enabled = true,
cellSize = 250.0, -- metres per map area
max = 100,
gain = { sale = 2.0, perUnit = 0.25, witness = 4.0, alert = 8.0, corner = 1.5, robbery = 5.0 },
decay = { area = 1.5, corner = 2.0, player = 1.0, number = 0.75 }, -- per real minute
effects = { refusal = 0.25, alert = 0.30, robbery = 0.10 }, -- at max heat
}Witnesses
| Setting | Menu label | Type | Default | What it does |
|---|---|---|---|---|
Config.Witnesses.enabled | Enabled | boolean | true | Turns witness checks on or off. |
Config.Witnesses.radius | Radius (m) | number | 15.0 | How far around a sale people count as witnesses. |
Config.Witnesses.crowdLimit | Too crowded at | number | 4 | With this many people nearby, the buyer won't deal. |
Config.Witnesses.snitchChance | Snitch chance per witness | number | 0.05 | Each witness's chance to call the police. 0.05 = 5%. |
Config.Witnesses = {
enabled = true,
radius = 15.0,
crowdLimit = 4,
snitchChance = 0.05,
}Time of day
Menu: Core → Heat & Witnesses → Time of day (Advanced). A list of in-game hour ranges, checked from the top; the first row that covers the hour is used. A range can wrap past midnight (21 to 4). Hours not covered use 1.0 for both.
| Field | Menu label | Type | What it does |
|---|---|---|---|
label | Label | string | A name for the row |
from | From hour | number | First hour, 0 to 23 |
to | To hour | number | The hour it ends. Can't equal From hour |
accept | Accept | number | Higher makes buyers more willing (the refusal chance is divided by it). 0.1 to 3 |
demand | Demand | number | Higher brings corner buyers faster and makes phone clients text more often. 0 to 5 |
Config.TimeOfDay = {
{ label = 'Night', from = 21, to = 4, accept = 1.2, demand = 1.3 },
{ label = 'Morning', from = 4, to = 10, accept = 0.7, demand = 0.6 },
{ label = 'Afternoon', from = 14, to = 18, accept = 1.0, demand = 1.1 },
}Corners
Menu: Core → Corners, or run /drugselling corners. Players start a corner wherever they stand; there is nothing to place.
| Setting | Menu label | Type | Default | What it does |
|---|---|---|---|---|
Config.Corners.enabled | Enabled | boolean | true | Turns corners on or off. |
Config.Corners.command | Command | string | 'corner' | The command players type to start and stop a corner, without the slash. A rename takes effect after a restart. |
Config.Corners.radial | Radial menu item | boolean | true | Adds Work corner / Stop corner to the radial menu. |
Config.Corners.radius | Radius (m) | number | 15.0 | How far from where they started the player may move. The corner closes once they're 5 m past it. |
Config.Corners.spacing | Spacing (m) | number | 40.0 | Two players can't work corners closer than this. 0 allows any distance. |
Config.Corners.idleTimeout | Closes after idle (s) | number | 180 | A corner closes for a player who goes this long without dealing with a buyer. |
Config.Corners.buyerMin | Shortest wait (s) | number | 25 | Fewest seconds between buyers. Can't be more than Longest wait. |
Config.Corners.buyerMax | Longest wait (s) | number | 70 | Most seconds between buyers. |
Config.Corners.mode | Buyers | string | 'both' | 'walk' (walk up), 'drive' (drive up) or 'both'. |
Config.Corners.substituteAccept | Accept rate for a substitute | number | 0.6 | When the player offers a different drug than the buyer asked for, the buyer accepts only this share as often. |
Config.Corners.anyDrugChance | Buyer asks for any drug | number | 0.15 | Most buyers ask for a drug the player carries. This share ask for anything on the demand list, even a drug the player doesn't have. |
Config.Corners.demand | Demand | table | weed, meth, cocaine, pills | What buyers ask for. See below. |
Config.Corners.walkModels | Walk-up buyers | table | 10 street models | Models for buyers who walk up when no local is nearby. |
Config.Corners.driveModels | Drivers | table | 4 street models | Models for buyers who drive up. |
Config.Corners.vehicles | Buyer vehicles | table | emperor, primo, manana, tornado, buccaneer, voodoo, glendale, washington | Cars that drive-up buyers use. Phone clients who drive to a meet use them too. |
Each Demand row:
| Field | Menu label | Type | What it does |
|---|---|---|---|
drug | Drug type | string | A drug key from Config.Drugs: weed, meth, cocaine or pills with the shipped config |
weight | How often | number | How often buyers ask for this drug compared to the other rows. 0 means never |
price | Price multiplier | number | What corner buyers pay for this drug against its fair price. 1 = normal, 1.5 = 50% more |
demand = {
{ drug = 'weed', weight = 3, price = 1.0 },
{ drug = 'meth', weight = 2, price = 1.1 },
{ drug = 'cocaine', weight = 2, price = 1.15 },
{ drug = 'pills', weight = 2, price = 1.05 },
},Phone
Menu: Phone → Phone → Trap phone.
| Setting | Menu label | Type | Default | What it does |
|---|---|---|---|---|
Config.Phone.enabled | Enabled | boolean | true | Turns trap phones on or off. Off: using a phone does nothing and no clients text. |
Config.Phone.item | Phone item | string | 'trap_phone' | The phone item. |
Config.Phone.simItem | SIM card item | string | 'trap_sim' | The item that gives a phone a new number. |
Config.Phone.chargerItem | Charger item | string | 'trap_charger' | The battery pack item. Empty means phones only charge in vehicles. |
Config.Phone.numberFormat | Number format | string | '555-####' | How numbers look. Every # becomes a random digit. 555-#### allows 10,000 numbers; add more # for a bigger server. |
Config.Phone.pinLength | PIN length | number | 4 | How many digits a PIN has. 3 to 8. |
Config.Phone.starterClients | Starter clients | number | 3 | Clients a phone gets the first time it's used. 0 to 10. |
Config.Phone.firstOrderDelay | First order after (s) | number | 20 | The first starter client (who answers at any hour) texts an order this many seconds after a new phone is first used. -1 waits for the normal order checks. |
Config.Phone.battery.standbyPerMinute | Battery → Standby drain (%/min) | number | 0.35 | Battery lost per real minute while switched on. |
Config.Phone.battery.perText | Battery → Per text sent (%) | number | 0.5 | Battery each sent text or reply uses. |
Config.Phone.battery.vehiclePerMinute | Battery → Vehicle charge (%/min) | number | 4.0 | Battery gained per real minute while sitting in any vehicle. 0 turns vehicle charging off. |
Config.Phone.battery.chargerAmount | Battery → Charger adds (%) | number | 60 | Battery one battery pack adds. |
Config.Phone.battery.chargerTime | Battery → Charger time (ms) | number | 8000 | How long using a battery pack takes. |
Config.Phone.ringDistance | Ring heard within (m) | number | 12.0 | Everyone this close hears a phone ring when it isn't on silent. 0 turns the ring sound off; the holder still sees the text slide up. |
Config.Phone.indoorsNoSignal | No signal indoors | boolean | false | Makes every interior a no-signal area. |
Config.Phone.prop | Phone prop | string | 'prop_amb_phone' | The phone model held in the player's hand. |
Config.Phone.anim.dict | Texting animation → Dictionary | string | 'cellphone@' | The texting animation. |
Config.Phone.anim.clip | Texting animation → Clip | string | 'cellphone_text_read_base' | The texting animation clip. |
Config.Phone = {
enabled = true,
item = 'trap_phone',
simItem = 'trap_sim',
chargerItem = 'trap_charger',
numberFormat = '555-####', -- every # is a random digit
pinLength = 4,
starterClients = 3,
firstOrderDelay = 20,
battery = {
standbyPerMinute = 0.35, -- % per real minute while on
perText = 0.5,
vehiclePerMinute = 4.0,
chargerAmount = 60,
chargerTime = 8000, -- ms
},
ringDistance = 12.0,
indoorsNoSignal = false,
prop = 'prop_amb_phone',
anim = { dict = 'cellphone@', clip = 'cellphone_text_read_base' },
}No-signal areas
Menu: Phone → No-signal areas (Advanced).
| Setting | Menu label | Type | Default | What it does |
|---|---|---|---|---|
Config.DeadZones | No-signal areas | table | {} | Areas with no signal. Drawn in the world with Draw new zone. A phone inside one can't send, and incoming texts wait until it's back in signal. |
Each area has a Name, its corner points, and a bottom and top height.
Clients
Menu: Phone → Clients. See Features → Clients.
| Setting | Menu label | Type | Default | What it does |
|---|---|---|---|---|
Config.Clients.maxBase | Book size at the lowest tier | number | 5 | How many clients a phone holds at the lowest reputation tier. Tiers add more. |
Config.Clients.personas | Personas | table | 5 personas | The kinds of client. See below. At least one is required. |
Config.Clients.availability | Availability windows | table | 4 windows | The hours a client answers. Each new client gets one at random. The same hour in both fields means any time. |
Config.Clients.trust.start | Trust changes → New client starts at | number | 40 | A new client's trust. |
Config.Clients.trust.deal | Trust changes → Completed deal | number | 5 | Trust for every completed deal. |
Config.Clients.trust.onTime | Trust changes → On time | number | 3 | Extra trust for being on time. |
Config.Clients.trust.late | Trust changes → Late | number | -6 | Trust for being late. Doubled for very late. Also used when you cancel a meet or take your own drop back. |
Config.Clients.trust.noShow | Trust changes → No-show | number | -18 | Trust for not showing up. |
Config.Clients.trust.goodBatch | Trust changes → Good batch | number | 4 | Trust for good quality. |
Config.Clients.trust.badBatch | Trust changes → Bad batch | number | -8 | Trust for bad quality. |
Config.Clients.trust.stolen | Trust changes → Drop stolen | number | -12 | Trust when their dead drop was taken by someone else. |
Config.Clients.trust.leaveBelow | Trust changes → Stops answering at or below | number | 5 | A client at or below this trust leaves the book. |
Config.Clients.quality.good | Quality memory → Good batch above | number | 1.15 | A batch whose fair price is at least this share of its base price counts as good. |
Config.Clients.quality.bad | Quality memory → Bad batch below | number | 0.8 | A batch at or below this share counts as bad. |
Config.Clients.acquire.streetChance | New clients → Street buyer asks for your number | number | 0.15 | Chance after a completed street sale. |
Config.Clients.acquire.cornerChance | New clients → Corner buyer asks for your number | number | 0.20 | Chance after a completed corner sale. |
Config.Clients.acquire.introChance | New clients → Introduction after a deal | number | 0.25 | Chance a trusted client introduces a friend after a deal. |
Config.Clients.acquire.introMinTrust | New clients → Introduces at trust | number | 70 | Trust a client needs to introduce someone. |
Config.Clients.acquire.introMinDeals | New clients → After this many deals | number | 3 | Deals a client needs to introduce someone. |
Config.Clients.acquire.homeRadius | New clients → Lives within (m) | number | 350.0 | A new client's home is within this distance of where they were met. Meets happen near their home. |
Config.Clients.aliases | Names | table | 30 names | Names clients are given. |
Config.Clients.models | Ped models | table | 14 models | Models phone clients spawn as. Clients met on the street keep the look they had. |
Personas
| Field | Menu label | What it does |
|---|---|---|
key | Key | An ID. Required |
label | Label | Shown on the Contacts screen |
weight | How common | How often new clients get this persona, compared to the others |
budgetMin / budgetMax | Lowest / Highest budget | Their opening offer, as a share of the order's base value |
counters | Counters before walking | How many times they haggle before giving up |
tolerance | Goes above their offer by | How far above their opening offer they'll go. 1.12 = 12% |
qtyMin / qtyMax | Smallest / Largest order | How many units they order |
personas = {
{ key = 'broke', label = 'Broke', weight = 20, budgetMin = 0.45, budgetMax = 0.75, counters = 1, tolerance = 1.05, qtyMin = 1, qtyMax = 2 },
{ key = 'cheap', label = 'Cheap', weight = 25, budgetMin = 0.70, budgetMax = 0.90, counters = 3, tolerance = 1.08, qtyMin = 1, qtyMax = 3 },
{ key = 'rushed', label = 'Rushed', weight = 15, budgetMin = 0.90, budgetMax = 1.10, counters = 1, tolerance = 1.10, qtyMin = 1, qtyMax = 3 },
{ key = 'regular', label = 'Regular', weight = 30, budgetMin = 0.90, budgetMax = 1.05, counters = 2, tolerance = 1.12, qtyMin = 1, qtyMax = 4 },
{ key = 'rich', label = 'Rich', weight = 10, budgetMin = 1.15, budgetMax = 1.60, counters = 2, tolerance = 1.20, qtyMin = 2, qtyMax = 6 },
},
availability = {
{ label = 'Evenings', from = 17, to = 1 },
{ label = 'Late nights', from = 21, to = 5 },
{ label = 'Days', from = 9, to = 18 },
{ label = 'Any time', from = 0, to = 0 },
},The broke, rushed and rich keys also have their own order texts in locales/en.json, used most of the time (6 in 10 orders). Other personas always use the general lines.
Orders And Meets
Menu: Phone → Orders & Meets.
Orders
| Setting | Menu label | Type | Default | What it does |
|---|---|---|---|---|
Config.Orders.tickSeconds | Check every (s) | number | 30 | How often clients get a chance to text an order. |
Config.Orders.chancePerTick | Chance per client per check | number | 0.10 | Each available client's chance per check, before reputation and time of day. |
Config.Orders.clientCooldown | Client cooldown (s) | number | 420 | Seconds after a client's last order ended before they order again. Half as long after Not now or Don't got it. Bulk buyers wait twice as long. |
Config.Orders.maxOpen | Open orders per phone | number | 4 | The most orders a phone can have going at once. |
Config.Orders.replyTimeout | Client waits for a reply (s) | number | 300 | Seconds a client waits for an answer, and again for a meet time, before giving up. |
Config.Orders.menu | What clients ask for | table | 9 rows | What clients can order. See below. |
Each What clients ask for row:
| Field | Menu label | What it does |
|---|---|---|
key | Key | An ID. Required |
drug | Drug type | The drug key in Config.Drugs. Required |
label | What clients call it | The word clients use in texts ("grams", "rock") |
items | Items that count | Any of these items fills the order. The cheapest one's base price sets the client's opening offer |
A row is only used when at least one of its items exists on your server and is listed in Config.Drugs.
menu = {
{ key = 'weed_small', drug = 'weed', label = 'grams', items = { 'weed_1g' } },
{ key = 'weed_eighth', drug = 'weed', label = 'eighths', items = { 'weed_eighth' } },
{ key = 'meth', drug = 'meth', label = 'meth', items = { 'meth_baggy', 'meth_baggy_glass', --[[ every meth baggy ]] } },
{ key = 'coke', drug = 'cocaine', label = 'coke', items = { 'cocaine_bag' } },
{ key = 'crack', drug = 'cocaine', label = 'rock', items = { 'crack_bag' } },
{ key = 'party', drug = 'pills', label = 'party pills', items = { 'party_pill', 'party_pill_bag', --[[ ... ]] } },
{ key = 'bars', drug = 'pills', label = 'bars', items = { 'bar_pill', --[[ ... ]] } },
{ key = 'blues', drug = 'pills', label = 'blues', items = { 'blue_pill', --[[ ... ]] } },
{ key = 'percs', drug = 'pills', label = 'percs', items = { 'perc_pill', --[[ ... ]] } },
},The full lists are in shared/config/phone.lua.
Meets
Times are in-game minutes. On a server with the standard GTA clock, one in-game minute passes in about two real seconds.
| Setting | Menu label | Type | Default | What it does |
|---|---|---|---|---|
Config.Meets.slots | Times offered | number | 3 | How many times the client offers when booking. |
Config.Meets.leadMinutes | Earliest slot ahead | number | 120 | The first time offered is at least this far ahead. |
Config.Meets.slotSpacing | Gap between slots | number | 120 | Gap between the times offered. |
Config.Meets.earlyWindow | Client waits early | number | 30 | The client is at the meet this long before the time, and the hand-off or drop can happen from then. |
Config.Meets.lateGrace | Late grace | number | 60 | Up to this long after the time counts as late. After that it's very late. Can't be longer than No-show after. |
Config.Meets.noShowAfter | No-show after | number | 180 | After this long the client gives up: a no-show. |
Config.Meets.spawnDistance | Client appears within (m) | number | 150.0 | The client appears when the player carrying the phone comes this close. |
Config.Meets.approachDistance | Client comes over within (m) | number | 25.0 | Once the player is this close to the client (and inside the hand-over radius), a client on foot walks over and a client in a car pulls up to the kerb. |
Config.Meets.meetRadius | Hand-over radius (m) | number | 30.0 | How close to the meet point the player must be to hand over. |
Config.Meets.arriveByCar | Client drives up | number | 0.35 | Chance the client arrives by car. |
Config.Meets.pointMin | Meet point at least (m) from home | number | 40.0 | The closest a hand-off point can be to the client's home. |
Config.Meets.pointMax | Meet point at most (m) from home | number | 220.0 | The farthest. |
Config.Meets.robberyChance | Robbery at trust 0 | number | 0.25 | Chance a client robs the player at a meet, at trust 0. |
Config.Meets.robberyTrust | No robbery from trust | number | 35 | At this trust and above, clients never rob. In between, the chance falls in a straight line. |
Config.Meets = {
slots = 3, leadMinutes = 120, slotSpacing = 120, -- in-game minutes
earlyWindow = 30, lateGrace = 60, noShowAfter = 180,
spawnDistance = 150.0, approachDistance = 25.0, meetRadius = 30.0, arriveByCar = 0.35,
pointMin = 40.0, pointMax = 220.0,
robberyChance = 0.25, robberyTrust = 35,
}Dead Drops
Menu: Phone → Dead Drops, or run /drugselling drops.
| Setting | Menu label | Type | Default | What it does |
|---|---|---|---|---|
Config.Drops.enabled | Enabled | boolean | true | Turns dead drops on or off. Off: clients only offer hand-offs. |
Config.Drops.spots | Drop spots | table | 10 spots in south Los Santos | Where drops happen, each with a Name and a Spot (use Place in world). A drop always uses the spot nearest the client's home, however far. With no spots, clients only offer hand-offs. |
Config.Drops.pickupDelay | Client collects after (s) | number | 60 | Real seconds after the package is left before the client collects it. |
Config.Drops.payout | Payment | string | 'pickup' | 'pickup' (Left at the drop): cash is left where the bag was, for the player to collect. 'instant' (Paid on collection): paid straight to the player if they're online with the phone, otherwise left at the drop. |
Config.Drops.cashWindow | Cash waits (s) | number | 600 | Real seconds the cash waits at the drop before it's gone. |
Config.Drops.visibleRange | Visible within (m) | number | 60.0 | How close players must be to see bags and cash at drops. |
Config.Drops.markerRange | Marker shown within (m) | number | 20.0 | How close the player with the drop must be to see the marker over the spot, their package and their cash. 0 hides the markers. |
Config.Drops.prop | Package prop | string | 'prop_food_bag2' | The bag model. |
Config.Drops.cashProp | Cash prop | string | 'prop_cash_envelope_01' | The cash model. |
Config.Drops = {
enabled = true,
spots = {
{ coords = vector3(124.97, -1681.01, 28.45) },
-- ...9 more in south Los Santos
},
pickupDelay = 60, -- real seconds
payout = 'pickup', -- 'pickup' | 'instant'
cashWindow = 600, -- real seconds
visibleRange = 60.0,
markerRange = 20.0,
prop = 'prop_food_bag2',
cashProp = 'prop_cash_envelope_01',
}Reputation
Menu: Phone → Reputation → Reputation tiers. Every change in a client's trust moves the phone's reputation by the same amount. Tiers must be listed from lowest to highest.
| Field | Menu label | What it does |
|---|---|---|
label | Label | The tier's name, shown on the phone |
rep | From reputation | Reputation needed |
orderRate | Order rate | Multiplies how often clients text |
clients | Extra clients | Book slots added on top of Book size at the lowest tier |
tolerance | Price tolerance | Multiplies how far clients go above their offer |
bulk | Bulk buyers deal | Bulk buyers deal with phones at this tier |
Config.Reputation = {
tiers = {
{ rep = 0, label = 'Nobody', orderRate = 1.0, clients = 0, tolerance = 1.00, bulk = false },
{ rep = 60, label = 'Corner Boy', orderRate = 1.0, clients = 2, tolerance = 1.03, bulk = false },
{ rep = 180, label = 'Hustler', orderRate = 1.15, clients = 4, tolerance = 1.06, bulk = false },
{ rep = 400, label = 'Plug', orderRate = 1.3, clients = 7, tolerance = 1.10, bulk = true },
{ rep = 800, label = 'Kingpin', orderRate = 1.5, clients = 10, tolerance = 1.15, bulk = true },
},
}Evidence
Menu: Pressure → Evidence → Seized phones. See Features → Police: Seized Phones.
| Setting | Menu label | Type | Default | What it does |
|---|---|---|---|---|
Config.Evidence.enabled | Enabled | boolean | true | Turns Evidence View on or off. Off: officers use seized phones like anyone else (a PIN still locks them out). |
Config.Evidence.jobs | Jobs | table | { 'police', 'sheriff' } | Jobs that get Evidence View on a phone that isn't theirs (on duty, if Only on-duty police is on). |
Config.Evidence.minGrade | Minimum grade | number | 0 | The lowest job grade that gets Evidence View. |
Config.Evidence.unlock.mode | Getting in → Mode | string | 'timed' | 'none' (Opens straight away), 'pin' (Needs the PIN, if the phone has one) or 'timed' (Timed extraction). |
Config.Evidence.unlock.duration | Getting in → Extraction time (ms) | number | 15000 | How long a timed extraction takes. 0 opens straight away. |
Config.Evidence.unlock.item | Getting in → Tool needed | string | '' | An item the officer must carry for a timed extraction. Empty: no tool needed. |
Config.Evidence.trace.enabled | Number trace → Enabled | boolean | true | Turns number traces on or off. |
Config.Evidence.trace.radius | Number trace → Blurred to (m) | number | 150.0 | The radius of the circle a trace shows. The phone is somewhere inside it. |
Config.Evidence.trace.duration | Number trace → Blip stays (s) | number | 60 | How long the trace stays on the map. |
Config.Evidence.trace.cooldown | Number trace → Cooldown (s) | number | 120 | Seconds between traces, per officer. |
Config.Evidence.trace.showOwner | Number trace → Names the registered owner | boolean | false | Shows the phone's registered owner (its first user) on traces, in Evidence View and in reports. Off keeps burners anonymous. |
Config.Evidence = {
enabled = true,
jobs = { 'police', 'sheriff' },
minGrade = 0,
unlock = { mode = 'timed', duration = 15000, item = '' }, -- 'none' | 'pin' | 'timed'
trace = { enabled = true, radius = 150.0, duration = 60, cooldown = 120, showOwner = false },
}Evidence bridges
Menu: Pressure → Evidence → Evidence bridges (Advanced).
| Setting | Menu label | Type | Default | What it does |
|---|---|---|---|---|
Config.Integrations.evidence | Bridges | table | { { name = 'oxide-police', enabled = true } } | The MDTs that can receive seized-phone reports, tried in order. The first one that's switched on, has a bridge file, and whose resource is running gets the report. Resource must be the MDT's resource name. |
Config.Integrations = {
evidence = {
{ name = 'oxide-police', enabled = true },
},
}To connect another MDT, see Evidence Bridge.
Bulk Buyers
Menu: Pressure → Bulk Buyers. See Features → Bulk Buyers.
| Setting | Menu label | Type | Default | What it does |
|---|---|---|---|---|
Config.Bulk.enabled | Enabled | boolean | true | Turns bulk buyers on or off. |
Config.Bulk.chancePerTick | Reaches out, per order check | number | 0.02 | Chance per order check that a new bulk buyer texts a phone at a bulk tier. Also each bulk contact's chance to order again. |
Config.Bulk.maxBuyers | Bulk contacts per phone | number | 2 | The most bulk contacts a phone can have. |
Config.Bulk.discount | Pays this share of fair | number | 0.8 | Bulk buyers open at this share of the order's base value, and go at most 5% higher. |
Config.Bulk.ambushChance | Ambush at standing 0 | number | 0.12 | Chance the bodyguards attack, at trust 0. Falls to nothing at trust 100. |
Config.Bulk.bodyguards | Bodyguards | number | 2 | Bodyguards who come to a bulk meet. 0 to 4. At a bulk meet, up to this many bodyguards standing within 15 m of the buyer aren't counted as witnesses. |
Config.Bulk.weapon | Bodyguard weapon | string | 'WEAPON_PISTOL' | The weapon the bulk buyer and their bodyguards pull in an ambush. |
Config.Bulk.models | Ped models | table | 4 gang models | Models bulk buyers and their bodyguards use. Only peds with these models are left out of the witness count. |
Config.Bulk.vehicles | Vehicles | table | baller, cavalcade, granger | Cars bulk buyers arrive in. |
Config.Bulk.names | Names | table | 5 names | Names bulk buyers are given. |
Config.Bulk.menu | What they buy | table | 3 rows | Like What clients ask for, plus Smallest order (qtyMin) and Largest order (qtyMax). |
menu = {
{ key = 'weed_bulk', drug = 'weed', label = 'ounces', items = { 'weed_ounce', 'weed_brick' }, qtyMin = 3, qtyMax = 8 },
{ key = 'meth_bulk', drug = 'meth', label = 'meth', items = { 'meth_baggy', 'meth_baggy_glass', 'meth_baggy_blue_sky', 'meth_baggy_ice' }, qtyMin = 20, qtyMax = 50 },
{ key = 'coke_bulk', drug = 'cocaine', label = 'bricks', items = { 'cocaine_brick' }, qtyMin = 1, qtyMax = 3 },
},Stings
Menu: Pressure → Stings → Police stings. See Features → Police: Stings And The Heat Map.
| Setting | Menu label | Type | Default | What it does |
|---|---|---|---|---|
Config.Stings.enabled | Enabled | boolean | true | Turns the sting phone on or off. |
Config.Stings.item | Sting phone item | string | 'sting_phone' | The sting phone item. |
Config.Stings.jobs | Jobs | table | { 'police', 'sheriff' } | Jobs that can use the sting phone (on duty, if Only on-duty police is on). Players who can use it are never picked as the target dealer. |
Config.Stings.cooldown | Server-wide cooldown (s) | number | 1800 | Seconds between stings, for the whole server. 1800 = 30 minutes. |
Config.Stings.minHeat | Only dealers with heat from | number | 10 | Dealers with less heat than this are never picked. |
Config.Stings.heatMap.enabled | Heat map → Enabled | boolean | true | Shows the Heat map option on the sting phone. |
Config.Stings.heatMap.threshold | Heat map → Shows areas from heat | number | 30 | Areas with at least this much heat show up. |
Config.Stings.heatMap.delay | Heat map → Delay (s) | number | 300 | The map shows heat as it was this many seconds ago. |
Config.Stings.heatMap.duration | Heat map → Blips stay (s) | number | 60 | How long the heat map stays. Each officer can open it once every 30 seconds. |
Config.Stings.heatMap.blip.color | Heat map → Blip → Colour | number | 1 | The circles' colour. |
Config.Stings.heatMap.blip.alpha | Heat map → Blip → Opacity | number | 110 | How see-through the circles are. 10 to 255. |
Config.Stings.alert.code | Hand-over alert → Code | string | '10-66' | The code on the alert when a dealer hands over to an undercover buyer. |
Config.Stings.alert.message | Hand-over alert → Message locale key | string | 'sting_alert_message' | The translation key for the alert text ("Undercover buy completed"). |
Config.Stings.alert.blipData | Hand-over alert → Blip | table | sprite 161, colour 1, scale 1.0 | The alert's map blip. |
Config.Stings.alert.time | Hand-over alert → Blip time (ms) | number | 120000 | How long the alert stays. |
Config.Stings = {
enabled = true,
item = 'sting_phone',
jobs = { 'police', 'sheriff' },
cooldown = 1800, -- seconds, server-wide
minHeat = 10,
heatMap = { enabled = true, threshold = 30, delay = 300, duration = 60, blip = { color = 1, alpha = 110 } },
alert = {
code = '10-66',
message = 'sting_alert_message',
blipData = { sprite = 161, color = 1, scale = 1.0 },
time = 120000,
},
}Compatibility
Menu: Advanced → Compatibility → Model swaps (Advanced).
| Setting | Menu label | Type | Default | What it does |
|---|---|---|---|---|
Config.ModelSwaps | Model swaps | table | {} | Replaces a model this resource uses with one your game build has. Each row has Shipped model (from) and Your model (to). |
If a player's game can't load a model (a buyer, a car, a prop), online admins get a notification naming the model, pointing at /drugselling settings. Add a swap here to fix it.
Config.ModelSwaps = {
-- { from = 'a_m_y_stbla_01', to = 'a_m_y_stbla_02' },
}Drugs (Config File Only)
shared/config/drugs.lua defines every sellable drug. It's never stored in the database: edit the file, save it, and restart the server. The shipped file has weed, meth, cocaine and pills.
Drug shape
Config.Drugs.drugkey = {
label = 'Display Name',
items = { ... }, -- item name -> { label, basePrice, xpUnits }
metaKeyFormat = '{field1}:{field2}',
groupLabel = 'metadata_field',
gradeDisplay = { ... },
progression = { ... }, -- optional
requiredLevel = 0,
modifiers = { ... },
}The key (drugkey) is what Drug type fields in the menu refer to (corner demand, order rows).
Items
items maps item names to a label and a base price. Only items listed here can be sold.
items = {
weed_1g = { label = 'Weed (1g)', basePrice = 25, xpUnits = 1 },
weed_eighth = { label = 'Weed (1/8 oz)', basePrice = 75, xpUnits = 3 },
},| Field | What it does |
|---|---|
label | The name shown in the sell menu |
basePrice | The price for one, before modifiers |
xpUnits | Optional. How many progression units one of this item counts as when it's sold, so bigger packages can pay more XP. Leave it out and each item counts as 1. It must be a number above 0; anything else counts as 1 and the server log shows one warning for that item |
A sale pays xpPerUnit × quantity × xpUnits XP, rounded to a whole number (see Progression). The sold count sent to the drug's resource is always the number of items, whatever xpUnits says.
The shipped weed items set xpUnits to their weight in grams. Meth, cocaine and pills don't set it, so each item counts as 1.
The default 10 XP per gram is intentional: a 35 g brick pays 350 XP, exactly as 35 single grams do. Packaging does not reduce XP.
| Weed item | xpUnits | XP per item at the default xpPerUnit (10) |
|---|---|---|
weed_1g | 1 | 10 |
weed_eighth | 3 | 30 |
weed_quarter | 6 | 60 |
weed_half | 12 | 120 |
weed_ounce | 24 | 240 |
weed_brick | 35 | 350 |
A brick is pressed from 10 dried buds, and each bud trims to 2–5 grams, so 35 is about what a brick holds on average.
Grouping
metaKeyFormat keeps different qualities apart in the sell menu, so they're never sold as one stack. Each {field} is replaced with that value from the item's metadata.
| Drug | metaKeyFormat | groupLabel |
|---|---|---|
| weed | '{strain_id}:{quality}' | strain_name |
| meth | '{variant}:{purity}' | variant_name |
| cocaine | '{purity}' | not set (uses the item label) |
| pills | '{batch}:{bagged}:{packed}' | brand |
groupLabel is the metadata field shown as the group's name. Leave it out to use the item's own label.
Grade display
| Field | What it does |
|---|---|
gradeMeta | The metadata field holding the grade text (for example quality) |
scoreMeta | An optional number field shown next to the grade (for example quality_score) |
colors | A colour for each grade, as Tailwind CSS class names like text-purple-400 |
Progression
progression is optional. It links a drug to the resource that makes it.
| Field | What it does |
|---|---|
resource | The resource to call |
levelExport | Its export that returns the player's level |
addXpExport | Its export that gives XP |
addSoldExport | Its export that counts units sold |
xpPerUnit | XP per progression unit sold. One item is one unit, unless the item sets xpUnits |
requiredLevel is checked on every way of selling (street, corner, phone hand-offs and dead drops), but only while the resource is running. If it isn't, the sale goes through and XP is skipped.
| Drug | requiredLevel | xpPerUnit |
|---|---|---|
| weed | 2 | 10 per gram |
| meth | 0 | 15 per item |
| cocaine | 0 | 15 per item |
| pills | 0 | 3 per item |
Weed XP follows weight: a gram pays 10 XP and an ounce pays 240, so a crop earns the same XP whether it's sold as single grams or packed into ounces and bricks. How fast a player levels depends on how much weed they sell, not how many sales they make. To slow or speed up weed levelling, change weed's xpPerUnit; to change how one package compares with another, change its xpUnits.
Modifiers
modifiers is an ordered list. Each one adds a multiplier or a bonus to the fair price:
fairPrice = basePrice * (all multipliers) * (1 + all bonuses)Every modifier has a type and a category ('multiplier' or 'bonus').
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
A 0–100 score on a curve between a lowest and highest multiplier. If the item has no score, the fallback is used.
{
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
Looks up traits from another resource's export and adds a bonus for each trait at or above its threshold, up to maxBonus.
{
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,
}The shipped weed entry lists seven traits: thc, potency, flavor, frost, density, aroma and cbd.
metadata_bonus
A bonus for each step a number on the item is at or above startValue, up to maxBonus.
{
type = 'metadata_bonus',
category = 'bonus',
field = 'generation',
startValue = 2,
bonusPerStep = 0.05,
maxBonus = 0.25,
display = { label = 'Crossbred', color = 'text-cyan-400' },
}export_multiplier
Asks another resource for the multiplier. The export gets the item's metadata and who is buying where, so it can price each sale differently. If the resource isn't running, the multiplier is 1.0. This is how pills are priced by oxide-pills.
{
type = 'export_multiplier',
category = 'multiplier',
exportResource = 'oxide-pills',
exportName = 'PriceFor',
}See Exports → PriceFor for what the export must return.
Adding your own drug
Config.Drugs in shared/config/drugs.lua.metaKeyFormat to the metadata your items carry (use a fixed text like 'none' if they carry none).gradeDisplay and the modifiers you want.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 },
},
},
}Next Steps
Installation Guide
Step-by-step setup for Oxide Drugselling — prerequisites, supported inventories, load order, the four phone items, dirty money, database, first-time setup in game, and verification.
Features
How every part of Oxide Drugselling works — street selling, the sell menu, negotiation, heat, witnesses, corners, the TrapFone trap phone, clients, meets, dead drops, reputation, bulk buyers, burner life, and police evidence and sting tools.