Documentation

Shop

Digital Den, the shop studio, backplates and finishes, laptops on show, staff storages and door locks.

Players buy laptops, parts, tools and accessories at a shop counter, and put a new finish or backplate on a laptop they carry. Every offer and price is in shared/unlocked/shop.lua, under Config.Shop, and the server checks each purchase against it.

#Digital Den

The default shop is Digital Den, a laptop showroom at the Morningwood strip mall. It stands inside zyke_digitalden, a free interior that comes with the laptop. The counter, the staff shelves under storages, the doors under doorlock and the laptops under displays are all placed for that interior.

zyke_digitalden is optional. The laptop starts without it, so a server whose map already fills that unit can leave it out and move the shop.

lua
locations = {
    {
        label = "Digital Den",
        model = "a_m_y_business_02",
        coords = vec4(-1318.56, -401.81, 36.64, 33.95),
        scenario = "WORLD_HUMAN_STAND_IMPATIENT",
        blip = {sprite = 521, color = 3, scale = 0.7},
    },
},

Each location gets a shop keeper and a blip. coords is where the shop keeper stands, at eye height, and the heading it faces. Leave model out for a counter without a shop keeper, and blip out for no blip.

With interaction = "point" in shared/unlocked/config.lua, the shop keeper has an interaction point where E opens the shop. With "target", Browse laptops is in the target menu instead, the same setting placed laptops use.

#The Shop Studio

The shop opens as a studio. What the player looks at stands on the counter in front of the shop keeper: a new laptop in the finish and backplate they pick, their own laptop, a part or an accessory. A side panel lists the offers, with the player's money in each account the shop takes.

The studio closes when the player walks more than 4 metres from the counter, dies or falls over.

SectionWhat it sells
LaptopsA new laptop, built in three steps: Model with its specs, Finish, and Backplate. Each finish and backplate is priced on top of the laptop and paid in the same purchase. I looks inside the laptop at the Model step.
CustomiseA new backplate or finish for a laptop the player carries. Pick the laptop first; the one in the player's hands is listed first. The Backplates and Finish tabs each show only while that service is on.
PartsNew parts at full condition, grouped by kind. Pointing at another part compares their specs. Memory and coolers show Fits your laptop or Doesn't fit your laptop against the laptops the player carries.
ExtrasTools and accessories, bought by the amount, up to 10 at a time.

A section the shop sells nothing in is hidden. Customise shows a hint to buy a laptop first when the player carries none.

After each purchase a short moment plays: what was bought turns once under a light, and a new laptop or item is lifted off the counter. Any key skips it.

#Studio Keys

The keys are shown on screen. Only the keys the open section uses are listed.

ActionKey
RotateMouse drag
ZoomScroll wheel
BrowseArrow keys
Change the amount, when buying by the amountLeft and right arrows (up and down browse)
Previous or next categoryQ or E
Next step, or switch between Backplates and FinishTab
Look inside a new laptop, or close it upI
Open or close the lidL
Auto-turn, or stop turningT
BuyEnter
BackBackspace

Escape clears the backplate search, or closes the studio when the search is empty.

#Items in the Studio

Parts and the laptop's own tools and accessories are shown as their props. A tool or accessory of your own shows its inventory image, or a prop when its entry sets prop:

lua
tools = {
    enabled = true,
    items = {
        {item = "laptop_screwdriver", price = 45},
        {item = "my_toolkit", price = 80, prop = "prop_tool_box_04"},
    },
},

The studio finds the counter top in front of the shop keeper by itself. When it can't, set studio = vec4(x, y, z, heading) on the location: the spot on the counter, and the heading from it towards the customer.

#Offers & Prices

lua
Config.Shop = {
    enabled = true,
    accounts = {"cash", "bank"},
    laptops = {enabled = true, finishMarkup = 150, items = {{prebuilt = "basic", price = 900}, ...}},
    refinish = {enabled = true, price = 400},
    skins = {enabled = true, price = 250, prices = {}, bought = true},
    parts = {enabled = true, items = {{item = "laptop_ram", model = "d5_16", price = 140}, ...}},
    tools = {enabled = true, items = {{item = "thermal_paste", price = 25}, ...}},
    accessories = {enabled = true, items = {{item = "usb_drive", price = 40}, {item = "recovery_usb", price = 2500, enabled = false}, ...}},
}
KeyWhat it sells
laptopsOne laptop per entry, built as a prebuilt from shared/unlocked/hardware.lua. Each comes in the stock graphite and in every finish for finishMarkup more. finishMarkup = false sells graphite only. See Hardware & Servicing.
refinishA new finish, graphite included, on a laptop the player carries.
skinsA backplate on a laptop the player carries, or on a new laptop. See Backplates.
partsNew parts at full condition. model is the part's id in the board catalogue.
tools, accessoriesItems bought by the amount, new and with all their uses. A power bank comes fully charged.

A category or a single entry with enabled = false is gone from the studio and refused by the server. Prices are whole numbers of 0 or more; an entry without a valid price is not sold. The recovery stick is listed but off by default.

Laptops, finishes and backplates live on the laptop item, so with item = false they are not sold.

#Payment & Server Checks

accounts lists the accounts a purchase is paid from. The first account that holds the full price pays it. cash and bank show as Cash and Bank in the studio; other accounts show their id.

The server checks every purchase:

  1. The player stands within 5 metres of the location.
  2. The offer is in the config, and the price is the config's, never the client's.
  3. CanBuyLaptopShopItem allows it. A backplate bought with a new laptop is checked as well, and so is CanUseLaptopSkin for any backplate.
  4. The player can carry the item.

The money is taken first and the balance read back, so a framework that allows an overdraft can't be tricked. The item is given after. Whatever did not reach the inventory is refunded, and so is a finish or backplate the laptop item refused. A finish or backplate the laptop already has is refused before any money is taken.

A laptop bought at the counter is registered to the buyer once it is in their inventory. See Ownership & Recovery.

Each purchase fires the server event zyke_laptop:LaptopShopBought(plyId, offerId, total) and is logged as ShopPurchase. A new laptop bought with a backplate fires it twice, once for each offer at its own price.

#Backplates: Free or Bought

A backplate is a skin on the back of the lid, picked by category; see Using the Laptop. It is kept on the laptop item as skin, so it goes with the laptop when it is handed over.

skins.bought = true, the default, makes backplates something players buy. Settings only offers the stock lid and the backplate on that laptop, with a hint to get more at an electronics store, and the server refuses any other. Every backplate is paid in full, also one the laptop had before: one taken off in Settings, or replaced by another, has to be bought again.

skins.bought = false makes every skin free to pick in Settings. The shop still puts one on for a player who would rather pay than open Settings. Without a laptop item, item = false, skins are always free.

Every backplate costs skins.price. skins.prices sets another price for whole categories:

lua
skins = {enabled = true, price = 250, prices = {material = 400, space = 350}, bought = true},

#Finishes

The chassis comes in anodised colours next to the stock Graphite:

IdName
silverSilver
blackBlack
midnightMidnight Blue
sky_blueSky Blue
sageSage Green
goldGold
rose_goldRose Gold
redRed
lavenderLavender

The finish is kept on the laptop item as finish and everyone nearby sees it, held or put down. Players do not pick a finish themselves; it comes from the shop or from the SetLaptopFinish export.

The shop offers the ids under finishes in shared/unlocked/config.lua. Delete an id to stop selling it; laptops that had it show graphite again. Only the finishes above exist, as each is a colour row built into the laptop's props.

#Laptops on Show

displays stands real laptops open on the shop's tables and shelves, each built as a prebuilt in a finish and, optionally, with a backplate. The defaults fill Digital Den's showroom with 27 of them.

lua
displays = {
    {coords = vec4(-1327.918, -406.048, 36.540, 211.1), prebuilt = "basic", finish = "silver", replaces = "prop_laptop_01a"},
    {coords = vec4(-1327.236, -406.138, 36.540, 31.1), prebuilt = "standard", replaces = {"gr_prop_gr_laptop_01c", "ex_prop_ex_laptop_01a"}},
    {coords = vec4(-1327.643, -403.607, 36.090, 211.1), prebuilt = "flagship", skin = "carbon", replaces = "prop_laptop_01a"},
},
FieldDescription
coordsWhere the laptop stands, on the surface, and the heading someone using it would face. The screen faces the other way.
prebuiltA prebuilt id from shared/unlocked/hardware.lua.
finishOne of finishes. Leave it out for the stock graphite.
skinA backplate id from skins. Leave it out for the stock lid.
replacesA map prop model, or a list of them, standing within 0.6 metres, such as the laptop an interior has on its desk. Hidden while the resource runs.

Keep about 0.7 metres between two laptops in a row, so the sign beside each one fits.

#Spec Signs

Each display has an acrylic spec sign standing on its right. Its card shows the model, colour, processor, graphics, memory, storage, Wi-Fi, battery, backplate, and the shop's price, read from the prebuilt in shared/unlocked/hardware.lua. The price only shows while the shop is enabled and sells that laptop in that finish.

Players walk up to a sign and press E on Read spec card, or use the target menu with interaction = "target". The camera eases in to rest in front of the card. Backspace, E or the right mouse button steps back, and so does walking off.

The first 32 displays also show a small copy of their card from a distance; signs further down the list show their card once the player is near.

The displays are local props that nobody can use, pick up or carry. They exist on a client only within renderDistance from shared/unlocked/config.lua. They stay when the shop has enabled = false, without a price. Set displays = {} to have none.

#Staff Storages

storages puts a shared stash on a shelf for the shop's staff. The defaults are Digital Den's rack in the repair workshop and the one in the stock room:

lua
storages = {
    {id = "digitalden_workshop", label = "Workshop Shelf", coords = vec3(-1318.59, -406.73, 36.84), jobs = {"digitalden"}, slots = 50, weight = 200000},
    {id = "digitalden_stock", label = "Stock Room Shelf", coords = vec3(-1312.39, -402.99, 36.84), jobs = {"digitalden"}, slots = 100, weight = 500000},
},

A storage is opened like the shop, with E on its interaction point or from the target menu, and only players with one of its jobs see it. Leave jobs out to let anyone open it. weight is in grams. The id names the stash in your inventory as zyke_laptop_<id>, so a new id starts an empty stash. Storages stay when the shop has enabled = false.

The server checks the player's place at the shelf and their job before the stash opens. CanOpenLaptopStorage adds your own rule on top, such as being on duty or a rank.

#Staff Job

extras/jobs/ has the digitalden job. It has five grades: Trainee, Technician, Senior Technician, Manager, and Owner as the boss.

FileFrameworkHow
esx.sqlESX LegacyImport it into your database, then run /refreshjobs or restart the server.
qb-core.txtQBCorePaste the entry into qb-core/shared/jobs.lua and restart qb-core.
qbx_core.txtQboxPaste the entry into qbx_core/shared/jobs.lua and restart qbx_core.

Give the job with /setjob <playerId> digitalden <grade>. To use a job you already have, put its name in jobs on each storage and door instead.

#Door Locks

doorlock locks Digital Den's doors to the digitalden job:

DoorStarts
Front double doorsUnlocked, so customers can walk in
Repair roomLocked
StorageLocked

When the resource starts, the doors are added to ox_doorlock or rcore_doorlock, unless it already has a door by that name. Lock grades, auto-locking or anything else you change in your door lock stays.

Door lockWhat to do
ox_doorlockNothing. The doors are added when the resource starts.
rcore_doorlockChange pin in doorlock first. rcore_doorlock can't take a job from another resource, so the doors open with that PIN until you give them the job in its /doors menu.
qb-doorlockCopy extras/doorlock/qb-doorlock.lua into qb-doorlock/configs/, restart qb-doorlock, and set doorlock.enabled = false.
cd_doorlockPaste extras/doorlock/ox_doorlock.sql into cd_doorlock's doorlock migration tool, and set doorlock.enabled = false.
lua
doorlock = {
    enabled = true,
    pin = "1234",
    locks = {
        {
            name = "digitalden front doors",
            label = "Digital Den Front Doors",
            jobs = {"digitalden"},
            locked = false,
            distance = 2.5,
            doors = {
                {model = "v_ilev_csr_door_l", coords = vec3(-1320.2831, -398.3739, 36.8857), heading = 31.1},
                {model = "v_ilev_csr_door_r", coords = vec3(-1318.6459, -397.3864, 36.8911), heading = 31.06},
            },
        },
    },
},

jobs takes job names, or {name = "digitalden", minGrade = 2} for a lowest grade. locked is how a new door starts, and distance, in metres, how close a player has to stand. Each door is its model and the coords and heading it stands at, two for a double door. Set enabled = false to use your own door setup; extras/doorlock/ox_doorlock.sql has the same doors to import yourself.

#Your Own Rules

CanBuyLaptopShopItem(plyId, offerId) in server/unlocked/hooks.lua is asked before the money is taken. Return false and a locale key to refuse, for example to keep flagships for donors:

lua
function CanBuyLaptopShopItem(plyId, offerId)
    if (offerId:find("^laptop:flagship:") and not IsPlayerAceAllowed(plyId, "laptop.flagship")) then return false, "shopNotAllowed" end

    return true
end
OfferId
New laptoplaptop:<prebuilt>:<finish or stock>, such as laptop:performance:midnight
Refinishrefinish:<finish or stock>
Backplateskin:<id>
Partpart:<item>:<model>, such as part:laptop_ram:d5_16
Tooltools:<item>
Accessoryaccessories:<item>

#Moving the Shop

To run the shop somewhere other than Digital Den, such as when your map fills that unit:

  1. Set the location's coords to where the shop keeper stands, at eye height. Add studio if the laptop doesn't stand on the counter.
  2. Move or remove the storages.
  3. Replace the displays with your own, or set displays = {}.
  4. Change the doors under doorlock.locks, or set doorlock.enabled = false.
  5. Leave zyke_digitalden out of your server config.

Add more entries to locations for more counters; each sells the same offers.

#A Player-Run Shop

Set enabled = false to take the counters away and leave the selling to players, such as a player-run Digital Den. Storages, displays and door locks stay. Your business script takes the money its own way and gives the goods with the server exports:

lua
-- A laptop built as a prebuilt, in a finish and with a backplate. owner registers it to that character
local metadata = exports["zyke_laptop"]:CreateLaptopMetadata("performance", {finish = "midnight", skin = "carbon", owner = identifier})
exports.ox_inventory:AddItem(plyId, "laptop", 1, metadata)

-- A new part at full condition
exports.ox_inventory:AddItem(plyId, "laptop_ssd", 1, exports["zyke_laptop"]:CreatePartMetadata("laptop_ssd", "nv4_1tb"))

-- Tools and sticks need no metadata. A power bank gets its serial and charge on its own
exports.ox_inventory:AddItem(plyId, "thermal_paste", 2)

-- A new finish or backplate on the laptop item in the customer's slot, nil puts the stock one back
exports["zyke_laptop"]:SetLaptopFinish(plyId, slot, "gold")
exports["zyke_laptop"]:SetLaptopSkin(plyId, slot, "carbon")

With skins.bought = true, the backplate SetLaptopSkin puts on is the one Settings offers next to the stock lid, so it works the same with the shop turned off.

See Create Laptop Metadata, Create Part Metadata, Set Laptop Finish and Set Laptop Skin.