Features
Sale flow, pricing behavior, risk mechanics, and lifecycle details for Oxide Drugselling.
Sale Flow
The selling interaction follows this sequence:
oxide-drugselling registers a global ped interaction through olink.target.AddGlobalPed.
The interaction only appears when all runtime checks pass:
- selling is enabled
- no sale is already active
- the target is not a player ped
- the target is not a mission entity
- the target is not dead
- neither the target nor the player is in a vehicle
- the ped model is not blacklisted
- the player is not on cooldown
- the NPC location is not on cooldown
- the player has at least one configured sellable item
The NPC walks toward the player. If the ped cannot get within range, the interaction cancels.
The resource freezes the NPC, turns both entities to face each other, creates a scripted camera, and opens the sell panel.
The client requests sellable groups from the server, then resolves item image paths through o-link.inventory before sending data to the NUI.
The player selects a grouped item stack, quantity, and asking price.
The server validates the request, recalculates fair pricing from config, rolls risk outcomes, and returns the result.
The client shows the result state, plays the relevant NPC animation when needed, then cleans up the camera and interaction state.
Sell Panel
The packaged UI is a Vue 3 NUI panel that shows:
- grouped sellable inventory entries
- item image paths from
o-link.inventory.GetImagePath - quantity controls
- direct price input and slider-driven pricing
- fair-price feedback
- bonus tags from pricing modifiers
- result states for thinking, accept, counter, reject, and error
When the current NPC model is marked as a gang model, the panel also flags the deal as dangerous.
Pricing Pipeline
Fair price is calculated per unit with the internal pricing pipeline:
fairPrice = basePrice * product(all multiplier modifiers) * (1 + sum(all bonus modifiers))The pipeline is driven entirely by Config.Drugs[drugKey].modifiers.
Supported modifier types in the shipped config:
score_multipliergrade_multipliervariant_multipliertrait_bonusmetadata_bonus
Shipped Weed Pricing
Weed uses:
- base prices per item size
- a
score_multiplieronquality_score - a fallback grade mapping on
quality - a
trait_bonusthat callsoxide-weed:GetStrainData - a
metadata_bonusongeneration
Default weed base prices:
| Item | Base Price |
|---|---|
weed_1g | 25 |
weed_eighth | 75 |
weed_quarter | 140 |
weed_half | 260 |
weed_ounce | 480 |
weed_brick | 800 |
Shipped Meth Pricing
Meth uses:
- a shared base price of
140 - a
grade_multiplieronpurity - a
variant_multiplieronvariant
All shipped meth bag variants use the same base price and differentiate through modifier data.
Shipped Cocaine Pricing
Cocaine (from oxide-cocaine) uses:
- base prices per item
- a
grade_multiplieronpurity
Default cocaine base prices:
| Item | Base Price |
|---|---|
cocaine_bag | 200 |
crack_bag | 120 |
cocaine_brick | 2500 |
The purity grade then scales the price: Pure pays the most, down through High, Mid, Low, and Dirty. Players can sell cocaine at any level, and every sale still feeds experience back into oxide-cocaine.
Negotiation Rules
After the server calculates the fair total price for the selected quantity, it evaluates the sale in this order:
police-dispatch roll
flat rejection roll
robbery roll for gang NPCs only
price-ratio evaluation
Default pricing thresholds:
| Ratio | Outcome |
|---|---|
playerPrice / fairPrice <= 0.90 | auto-accept |
<= 1.25 | 50/50 accept or counter |
> 1.25 | reject |
Counter-offers use 95% of fair total price by default and expire after 60 seconds.
Risk Mechanics
Rejection
A flat 15% reject chance applies before price evaluation.
Robbery
Gang NPCs have a separate 15% robbery chance by default.
When robbery happens:
- up to
Config.Selling.risks.robberyItemLossitems are removed from the offered metadata group Config.Selling.risks.robberyCashPercentof the player's current dirty-money balance is taken- the robber draws a pistol, aims at the player, then flees
Gang models are configured in shared/config/selling.lua.
Police Dispatch
Every interaction has a separate 15% dispatch chance by default.
When dispatch fires, the client sends an alert through o-link.dispatch with:
- code
10-31 - locale key
sell_dispatch_message - player coordinates
- configured blip data
- configured police job recipients
- configured display time
Dirty Money Handling
Dirty money is abstracted behind two modes:
account: useso-link.moneywithConfig.DirtyMoney.accountitem: useso-link.inventorywithConfig.DirtyMoney.item
The same mode is used for both sale payouts and robbery deductions.
Progression Integration
Each drug type can optionally link back to the resource that produces it (like oxide-weed, oxide-meth, or oxide-cocaine) by defining a progression block with:
- source resource name
- level export
- XP export
- sold-count export
- XP per unit
Behavior is intentionally tolerant:
- if no progression block exists, the drug sells normally
- if the source resource is not started, level checks are skipped and tracking is skipped
- if a configured export fails, the sale flow continues
This lets oxide-drugselling run standalone while still supporting tighter integration with production resources.
Sale Hook
Every completed sale can notify your other systems, so you can build on top of selling without editing the resource's protected code — gang turf payouts, heat or reputation systems, statistics, Discord logs, and so on.
There are two ways to react, and you can use either or both:
- a
Config.OnDrugSoldfunction inshared/config.luawhere you write your own code - an
oxide-drugselling:drugSoldserver event you can listen for from any of your own resources
Both run on the server after the player is paid, and both receive the same details: who sold, the drug type and item, the quantity, the final price paid, and the payout mode. See Configuration and API Reference for the exact fields and examples.
Cooldowns and Runtime Guards
Current runtime guards:
- client-side sellable-item cache:
10seconds - player interaction cooldown:
5000ms - NPC location cooldown:
120000ms - distance monitor closes the sale if the NPC disappears or the player moves more than
5.0units away
Current server callback rate limits:
| Callback | Window |
|---|---|
oxide-drugselling:server:getSellableItems | 3000 ms |
oxide-drugselling:server:initSale | 5000 ms |
oxide-drugselling:server:acceptCounter | 5000 ms |
Lifecycle
The resource initializes from the current o-link lifecycle flow:
- waits for
olink:client:playerReady - cleans up on
olink:client:playerUnload - also performs a delayed fallback check with
olink.framework.GetIsPlayerLoaded()
This keeps the interaction registration tied to actual player readiness rather than resource start timing alone.