Features
Sale flow, pricing behavior, risk mechanics, and lifecycle details for Oxide Drugselling.
Sale Flow
The selling interaction follows this sequence:
oxide-drugsellingregisters a global ped interaction througholink.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.inventorybefore 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.
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 define 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.
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.