NPC Sales

Server-side NPC sales simulation for passive vending revenue in Oxide Vending.

Overview

The NPC sales system evaluates active vending machines on a timer and simulates purchases without spawning real sale entities. Revenue is based on machine type, time of day, location, pricing, stock variety, and nearby competition.

Simulation Flow

Every tick:

  1. Skip disabled simulation.
  2. Read the current hour.
  3. Iterate active machines.
  4. Skip machines with no stock.
  5. Calculate sale chance.
  6. Roll a random purchase.
  7. Process the sale if the roll succeeds.

Default tick settings:

  • TickInterval = 60
  • BaseChancePerTick = 0.05

Sale Chance Formula

saleChance = baseChance * time * location * type * price * stock * competition
FactorSource
baseChanceConfig.NPCSales.BaseChancePerTick
timeGetNPCTimeMultiplier(hour)
locationConfigured hotzone or default location multiplier
typeConfig.NPCSales.MachineTypeModifiers[machineType]
priceMachine average markup competitiveness
stockStocked slots divided by total machine slots
competitionNearby same-type competition score

Time Source

The current hour is read from:

exports["oxide-weather"]:GetTime()

when available. If oxide-weather is not running or does not return an hour, the system falls back to real server time via os.date("%H").

Time Multipliers

Configured GTA-hour multipliers range from quiet overnight periods to lunch and commute spikes.

Examples from the shipped config:

  • 00:00 -> 0.2
  • 08:00 -> 1.3
  • 12:00 -> 1.5
  • 17:00 -> 1.4
  • 23:00 -> 0.3

Hotzones

Hotzones live in Config.NPCSales.Hotzones and are simple radius-based location multipliers.

Current high-traffic examples include:

  • Legion Square
  • Del Perro Pier
  • Airport terminals
  • Vespucci Beach

Current low-traffic examples include:

  • Sandy Shores
  • Paleto Bay
  • Grapeseed
  • Harmony
  • Chumash

Machines outside all configured hotzones use:

Config.NPCSales.DefaultLocationMultiplier

Adding a Hotzone

{
    name = "custom_zone",
    coords = vector3(x, y, z),
    radius = 150.0,
    multiplier = 1.5,
}

Machine Type Weighting

Default machine multipliers:

TypeMultiplier
drinks1.5
snacks1.3
general0.8
electronics0.4

Price Competitiveness

Price weighting is based on average machine markup:

multiplier = 1.5 - ((avgMarkup - 1.0) * 0.5)

This value is clamped between 0.5 and 1.5.

Practical examples:

  • Base-price or cheaper machines get a strong bonus
  • Default 1.5x markup is still favorable
  • Very expensive machines are penalized

Stock Variety

Variety uses:

stockedSlots / totalSlots

This means a machine with more filled slots is more attractive to the NPC simulation than a machine with only one or two stocked products.

Competition

Competition compares nearby machines of the same type.

Default competition config:

Config.Competition = {
    Enabled = true,
    Radius = 500.0,
    SameOwnerCompetes = false,
    MinMultiplier = 0.3,
    MaxMultiplier = 1.8,
    CheapestBonus = 1.5,
    SuggestedPriceMargin = 0.95,
    SuggestedPriceMinMarkup = 0.85,
    AnalysisCacheTime = 300,
    ShowCompetitorNames = true,
    ShowExactPrices = true,
}

Key behavior:

  • Only same-type machines compete
  • Same-business machines do not compete by default
  • The cheapest machine can receive an extra CheapestBonus
  • Dashboard suggestions use competitor pricing data and the configured margin floor
  • Dashboard competition analysis is cached for 300 seconds by default

Item Selection and Quantity

When a sale occurs, the system selects an item by weighted random.

Default weighting favors:

  • Cheaper items
  • Items with more quantity on hand

Current purchase behavior:

  • MinQuantity = 1
  • MaxQuantity = 2
  • AvoidLastItem = true
  • PreferCheaperItems = true

If AvoidLastItem is enabled, the system avoids consuming the final item in a stack.

Revenue and Progression

Processed NPC sales:

  • Reduce stock
  • Add machine revenue
  • Log an npc_sale transaction with player_citizenid = "NPC"
  • Degrade machine durability using NPC wear settings
  • Grant progression XP and lifetime revenue
  • Check sales milestones

The progression XP formula is:

xp = Config.Progression.XPSources.npcSale + (saleAmount * Config.Progression.XPSources.salePerDollar)

Level rewards can also add an NPC revenue boost on top of the base sale value.

Visual Feedback

The actual sale logic is entity-free, but the resource can optionally animate nearby ambient peds when players are in range.

Current visual feedback settings:

VisualFeedback = {
    Enabled = true,
    PlayerRange = 50.0,
    PedSearchRadius = 30.0,
    MachineCooldown = 30000,
}

This is cosmetic only. Disabling it does not disable NPC revenue generation.