⚙️Concepts & How It Works

This guide shows how to create and reuse shop themes in MarketV2. Define visuals (NPC/blip/marker/UI), payments, access rules, and full item catalogs, then style the UI via CSS theme tokens.......

Risk Market V2 — Shops & Themes

1) Overview

  • Shops are world instances you place (in Config.MarketShops, WeaponShops, BlackMarkets, JobShops).

  • Each shop points to a theme: Config.MarketThemes[THEME_KEY].

  • A theme defines visuals (NPC/blip/marker/UI colors), economy (payments), access, and the catalog (categories + items).


2) Theme Anatomy

Define under Config.MarketThemes:

Config.MarketThemes = Config.MarketThemes or {}

Config.MarketThemes.mytheme = {
  distance = 3.0,
  npc   = { enabled = true, model = 'a_m_y_business_01' },
  blip  = { enabled = true, sprite = 52, color = 48, scale = 0.8, name = 'My Themed Shop' },
  marker= { enabled = true, type = 29, scale = 0.5, color = { r=255,g=255,b=0,a=150 } },

  ui = {
    title = 'MY THEME',
    description = 'Welcome!',
    showStock = true  -- show stock to customers on owned shops
  },

  payment = {
    cash = true, bank = true, blackmoney = false
    -- itemCurrency = { enabled = true, item = 'gold_token', per = 100 }
  },

  -- Optional access control:
  -- access = {
  --   jobs = 'all' or { 'police','ambulance' },
  --   minGrade = 0,
  --   minGrades = { police = 3 },
  --   useLicense = true,
  --   licenseMode = 'item' or 'esx',
  --   licenseItem = 'weapon_license',
  --   esxLicenseType = 'weapon'
  -- },

  weaponsAsItems = false, -- false=give to loadout, true=inventory item

  -- Optional per-job category gates:
  -- categoryAccess = {
  --   police = { { minGrade = 2, categories = { 'Restricted' } } }
  -- },

  categories = {
    { name = 'Snacks', icon = 'img/items/hamburger.png' },
    -- { name = 'Restricted', icon = 'img/categories/crown.png' }
  },

  items = {
    Snacks = {
      { name='bread',     label='Bread',     price={min=10,max=15}, image='img/items/bread.png',     description='Fresh loaf.' },
      { name='hamburger', label='Hamburger', price={min=25,max=25}, image='img/items/hamburger.png', description='Juicy & quick.' }
    }
    -- Restricted = { ... }
  }
}

Rules

  • Category names in items must match categories.

  • price can be a number (price=100) or {min,max}.

  • image can be img/... (NUI) or https://....


3) Attach Theme to a Shop

Config.MarketShops = Config.MarketShops or {}
table.insert(Config.MarketShops, {
  coords = {
    {
      id='myshop_1', theme='mytheme',
      pos=vector4(24.50,-1347.06,29.50,264.45),
      ownable=true,
      mgmt=vector4(30.03,-1339.62,29.49,7.72),
      price=200000
    }
  }
})
  • IDs must be unique across all shops.

  • Switch look/catalog by changing theme.


4) UI Styling (CSS)

Frontend adds theme-<key> on <body>. Define colors in html/css/config.css:

:root { --main-color:#FDFF18; --main-color-2:#FDFF1836; }
.theme-market  { --main-color:#FDFF18; --main-color-2:#FDFF1836; }
/* Your theme */
.theme-mytheme { --main-color:#2EE6A8; --main-color-2:#2EE6A836; }

5) Payments

  • Toggle cash, bank, blackmoney in payment.

  • Item currency:

    payment = { itemCurrency = { enabled=true, item='gold_token', per=100 } } -- each token = $100
  • QBCore black money as item:

    Config.BlackMoneyAsItem = { name='black_money', per=1 }

6) Ownership, Pricing & Stock

  • Unowned: prices read from theme each open (min..max random if range). No DB stock.

  • Owned:

    • Prices freeze to DB on purchase (owner can edit).

    • Stock tracked per item (show in UI if ui.showStock=true).

    • Sales credit shop balance; purchases decrement stock.


7) Access Control & Weapons

  • Restrict by job/grade via access (and optionally categoryAccess).

  • Licenses:

    • ESX: useLicense=true, licenseMode='esx', esxLicenseType='weapon'

    • Item: useLicense=true, licenseMode='item', licenseItem='weapon_license'

  • Weapons:

    • weaponsAsItems=false → given to loadout (duplicates blocked).

    • weaponsAsItems=true → added as inventory items.


8) Delivery Missions (Owner Restock)

  • Start from owner UI → server calls:

    exports['risk-marketv2']:StartDelivery(source, shopId)
  • Truck spawns at Config.Delivery.Spawns[shopId].spawn.

  • Load at loadMarker (progress), return to shop, unload → stock increases.

  • Configure per shop:

    Config.Delivery.Spawns = {
      ['247_1']   = { spawn=vector4(...), loadMarker=vector3(...) },
      ['myshop_1']= { spawn=vector4(...), loadMarker=vector3(...) }
    }

9) Owner Flow (Bank & Procurement)

  • Buy: charge bank; create risk_mv2_balances row; freeze prices; init stock rows.

  • Fund/Withdraw: bank ↔ shop balance (fee via Config.FundFeePercent).

  • Set price: DB update + live NUI refresh.

  • Procure: buy stock from balance at wholesale = min(price) * (1 - Discount%).


10) Exports & Events

Server exports

exports('StartDelivery', function(src, shopId) end)
exports('GetShopSnapshot', function(shopId) -> table end) -- { id, balance, items[{name,label,stock,price,image,min}], recentSales, popular }
exports('CanManage', function(src, shopId) -> bool end)

Core events

  • risk_markets:open → open UI for shop id (server).

  • risk_markets:purchase → process cart/payment (server).

  • Owner actions: risk-mv2a:setItemPrice, risk-mv2a:fundAccount, risk-mv2a:withdrawAccount, risk-mv2a:procureItem.

  • Delivery flow: risk-markets:delivery:* (spawn/load/unload/done).


11) Images & Paths

  • Prefer local img/... under the resource’s html folder.

  • Remote images: full https://....

  • If you rename the resource, update any nui://<resource>/... paths used elsewhere.


12) ox_target vs. Markers

  • If Config.UseOxTarget = true and ox_target is running → NPC/box-zone context options.

  • Otherwise → 3D marker + “E” prompt.


13) Best Practices

  • Keep catalog & visuals in themes; reuse themes across many shops.

  • Ensure unique shop IDs.

  • Show stock to players only where it’s meaningful (ui.showStock=true).

  • Start access simple (jobs='all'), add minGrade/categoryAccess later.


14) Gotchas & Troubleshooting

  • Duplicate IDs: don’t reuse (e.g., 247_2 twice) — rename one (e.g., 247_4).

  • UI won’t open: check ox_target state or stand in marker and press E.

  • Images missing: bad path; verify relative to html or use https://.

  • Prices not persistent: only persist after ownership (unowned uses theme config).

  • Stock not changing: tracked only for owned shops.


15) Minimal Example Theme + Shop

Theme

Config.MarketThemes.neon = {
  distance = 3.0,
  npc = { enabled = true, model = 'a_f_y_hipster_02' },
  blip = { enabled = true, sprite = 52, color = 27, scale = 0.8, name = 'Neon Store' },
  marker = { enabled = true, type = 29, scale = 0.5, color = { r=0,g=200,b=255,a=140 } },

  ui = { title = 'NEON MART', description = 'Open late. Shine bright.', showStock = true },
  payment = { cash = true, bank = true, blackmoney = false },
  weaponsAsItems = false,

  categories = {
    { name='Drinks',  icon='img/items/water.png' },
    { name='Gadgets', icon='img/items/usb.png' }
  },

  items = {
    Drinks = {
      { name='water',        label='Water Bottle', price={min=5,max=5},   image='img/items/water.png',  description='Stay hydrated.' },
      { name='energy_drink', label='Energy Drink', price={min=18,max=22}, image='img/items/energy.png', description='Quick boost.' }
    },
    Gadgets = {
      { name='usb_stick', label='USB Stick', price={min=18,max=22}, image='img/items/usb.png', description='Portable storage.' },
      { name='radio',     label='Radio',     price={min=95,max=110},image='img/items/radio.png', description='Stay connected.' }
    }
  }
}

CSS

.theme-neon{ --main-color:#00E5FF; --main-color-2:#00E5FF36; }

Shop

table.insert(Config.MarketShops, {
  coords = {
    {
      id='neon_1', theme='neon',
      pos=vector4(1160.0, -324.0, 69.2, 90.0),
      ownable=true, mgmt=vector4(1159.2, -319.7, 69.2, 178.0), price=240000
    }
  }
})

Last updated