Racks & Hacking
Read and run laptop racks, register rack workloads, and ask for Shim intrusion jobs from another resource.
A laptop rack is a mining rig of stolen or spare laptops that players set down in the world and run from Hive, the rack app. Other resources read racks and what they draw, switch them and set their power supply, place and remove them, and register workloads that racks send compute to, such as a password cracker for a heist. The laptop's own mining pools are workloads registered the same way.
Shim is the laptop's intrusion toolkit. Another resource asks for a job on one of its own targets, such as a door panel or an ATM, and gets the result once the player has played the job's puzzle. Every job leaves a trace that police scripts can read.
The rack exports only exist while enabled is true in shared/unlocked/racks.lua. GetMiningNetwork also needs mining.enabled there and crypto, Config.Crypto.enabled, turned on. With them off, calling the exports fails, so a resource that works without racks wraps its calls in pcall.
The hacking exports always exist. While
enabledisfalseinshared/unlocked/hacking.luathey refuse every job with"disabled".
General Information
Every export on this page is a server export. Racks belong to characters and are kept in the
zyke_laptop_racksandzyke_laptop_rack_nodestables. A rack's id stays the same through restarts.Workloads belong to the resource that registered them and are removed when it stops. The laptop forgets them when it restarts, so register them on the server event
zyke_laptop:Readyas well as when your resource starts.Exports marked Yields wait on the database or on the player. Call them from a thread, an event handler or a command, not while your script loads.
The
CanPlaceRack,CanUseRack,CanRemoveRack,GetRackPowerSupplyandCanStartHackhooks inserver/unlocked/hooks.luaadd your own rules. See Can-Check Hooks.
Types / Classes
RackInfo,RackWork,RackWorkloadEarns,RackWorkloadInfoandMiningNetworkStateare intypes/racks.luainsidezyke_laptop.LaptopHackOptionsandLaptopHackTraceare intypes/hacking.lua.
#Rack Info
GetRack, GetRacks and GetCharacterRacks return racks as a RackInfo table.
| Field | Description |
|---|---|
id | The rack's id. |
name | The name its owner gave it in Hive. |
owner | Identifier of the character who set it down. |
members | Identifiers of the characters who run it besides its owner. |
coords | A vector4 on the floor under the rack, w the heading. |
bucket | Its routing bucket. |
on | Whether it is switched on. |
tripped | true while its breaker is tripped, until it is switched on again. |
profile | The power profile: "eco", "balanced" or "max" by default. |
laptops | Each laptop in it: {slot, serial, state, draw, compute, heat}. state is "off", "idle", "running", "throttled", "overheated", "noBoard" or "noMemory". |
slots | How many slots the rack has. |
shares | The percent of an algorithm's hashrate each workload gets, by workload id. |
draw | Watts it draws now, the controller's base load included while it is on. |
compute | The laptops' compute it gives now, per second. |
hash | The miner sticks' hashrate sent to workloads, by algorithm, such as {sha256 = 300}. |
supply | Watts it may draw before its breaker trips. |
The rack the server events and a workload's onWork get has laptops as a count and a serials list of {slot, serial} instead.
#Reading Racks
#Get Rack
Returns the rack, or
nilfor an unknown id.Example:
lua---@param rackId integer ---@return RackInfo? rack local rack = exports["zyke_laptop"]:GetRack(3)
#Get Racks
Returns every rack that matches the filter. All filters are optional and all of them have to match.
radiusonly counts together withcoords.membermatches the owner too. Without a filter it returns every rack.Example:
lua---@param filter? {owner?: string, member?: string, bucket?: integer, coords?: vector3, radius?: number, minDraw?: number, on?: boolean} ---@return RackInfo[] racks -- The racks near the power station that draw over 1 kW, for a power company's tip-off local racks = exports["zyke_laptop"]:GetRacks({ coords = vector3(2742.1, 1513.6, 24.5), radius = 150.0, minDraw = 1000, on = true, })
#Get Character Racks
Returns the racks a character owns or is a member of.
Example:
lua---@param identifier string ---@return RackInfo[] racks local racks = exports["zyke_laptop"]:GetCharacterRacks(Z.getIdentifier(plyId))
#Find Rack By Serial
Returns the id of the rack a laptop stands in and its slot, by the laptop's serial, such as to find a stolen laptop. Returns
nilwhen the laptop is in no rack.Example:
lua---@param serial string ---@return integer? rackId ---@return integer? slot local rackId, slot = exports["zyke_laptop"]:FindRackBySerial("ZL-4F7Q2K")
#Get Rack Power
Returns what the rack draws and gives now, and its supply. Returns
0, 0, 0for an unknown rack.Example:
lua---@param rackId integer ---@return number draw @ Watts ---@return number compute @ Per second ---@return number supply @ Watts it may draw before its breaker trips local draw, compute, supply = exports["zyke_laptop"]:GetRackPower(3)
#Is Rack Standing
Returns whether the rack still stands. Returns
niluntil the racks are loaded from the database on start.Example:
lua---@param rackId integer ---@return boolean? standing local standing = exports["zyke_laptop"]:IsRackStanding(3)
#Running Racks
#Set Rack On
Switches the rack on or off, as its power switch does. Switching it on resets a tripped breaker. Fires
zyke_laptop:racks:PowerChangedwith your resource's name as the cause. Returnsfalsefor an unknown rack.Example:
lua---@param rackId integer ---@param on boolean ---@return boolean set exports["zyke_laptop"]:SetRackOn(3, false)
#Set Rack Power Supply
Sets the watts one rack may draw, over the config's
power.supplyand theGetRackPowerSupplyhook, such as from a generator. Below 0 counts as 0, which keeps the rack from running.falsegoes back to the config and the hook. It is not kept through a restart. Returnsfalsefor an unknown rack or a value that isn't a number orfalse.Example:
lua---@param rackId integer ---@param watts number | false ---@return boolean set exports["zyke_laptop"]:SetRackPowerSupply(3, 5000)
#Set Rack Share
Sets the percent of the rack's hashrate of the workload's algorithm that the workload gets, 0 to 100, without asking the owner or the workload's
canUse. 0 takes the share away. The shares of one algorithm add up to 100 at most, and the rest idles.Returns
falseand"notFound"for an unknown rack,"invalid"for a share that isn't a whole number from 0 to 100,"unknownWorkload"for a share above 0 for a workload that isn't registered, or"overAllocated"when the algorithm's shares would add up to more than 100.The laptop's mining pools have the ids
mining:xmr,mining:btcandmining:scryptby default, frommining.poolsinshared/unlocked/racks.lua.Example:
lua---@param rackId integer ---@param workloadId string ---@param share integer ---@return boolean set ---@return string? err local set, err = exports["zyke_laptop"]:SetRackShare(3, "crack:vault", 50)
#Placing and Removing Racks
#Place Rack
Yields. Sets a rack down for the player on a spot your resource picked, such as a workshop's floor grid, after you cancelled the Placing event.
coordsis avector4on the floor with the heading asw. The rack goes into the player's routing bucket and the player owns it.It takes the rack item from the player, the one in
options.slotfirst, or none withoptions.free = true, for an admin. The spot is checked as for a rack set down by hand: the racks per character, a persistent routing bucket and theCanPlaceRackhook. The player's reach is not checked, so check where they stand yourself.Returns the rack's id, or
niland a locale key such as"racks:world:noRackItem"or"racks:world:placedLimit", after the player is told why. Returnsnilwithout a key while the racks are still loading, or for invalid coords or an unknown player.Example:
lua---@param plyId integer ---@param coords vector4 @ w is the heading ---@param options? {slot?: integer, free?: boolean} ---@return integer? rackId ---@return string? refusal @ Locale key local rackId, refusal = exports["zyke_laptop"]:PlaceRack(plyId, vector4(1003.2, -3105.8, -39.99, 90.0), {slot = slot})
#Pack Rack
Yields. Picks an empty rack up into the player's inventory, with the same rules as at the rack but no reach check. The player has to own it or be let by the
CanRemoveRackhook, andCanUseRackis asked with"pickUp". Returnstrue, orfalseand a locale key such as"racks:world:notEmpty","racks:world:notOwner"or"racks:world:noRoom", after the player is told why.Example:
lua---@param plyId integer ---@param rackId integer ---@return boolean pickedUp ---@return string? refusal @ Locale key local pickedUp, refusal = exports["zyke_laptop"]:PackRack(plyId, 3)
#Remove Rack
Yields. Takes a rack and its laptops out of the world without giving anything to anyone, such as for a police raid. Returns the items, so your resource decides where they go, such as an evidence locker: each laptop with its metadata, worn as it ran, and the rack item last. Returns
nilfor an unknown rack or one that is being changed.When the rack's row can't be deleted, the laptops still come out and the empty rack stays standing, without the rack item in the list. Fires
zyke_laptop:racks:Removed.Example:
lua---@param rackId integer ---@return {name: string, metadata?: table}[]? items local items = exports["zyke_laptop"]:RemoveRack(3) for _, item in ipairs(items or {}) do -- Put item.name with item.metadata in your evidence locker end
#Workloads
A workload is something racks send compute to. The rack calls its onWork every tick, every tickSeconds in shared/unlocked/racks.lua, with the work its share did. The owner, or SetRackShare, gives it a share of the rack in Hive.
#Register Rack Workload
Adds a workload, or replaces one your resource registered before. Returns
true, orfalseand"invalid"for a bad id, nolabelor noonWork, or"taken"when another resource registered the id. Both print a warning in the server console, except a missing label. Fireszyke_laptop:racks:WorkloadsChanged.The id is up to 48 letters, digits,
_,-and:. When your resource stops, the racks keep their shares for the workload and Hive shows it as offline until it is registered again.Example:
lua---@param id string ---@param definition table ---@return boolean registered ---@return string? err exports["zyke_laptop"]:RegisterRackWorkload("crack:vault", { label = "Vault cracker", description = "Brute-forces the Pacific vault's keypad codes.", icon = "key-round", algorithm = "cpu", earns = {perUnitHour = 0.002, unit = "codes"}, canUse = function(identifier, rack) return true end, onWork = function(work, rack, state) state = state or {done = 0} state.done = state.done + work.units return { status = ("%d%% cracked"):format(math.floor(math.min(1, state.done / 50000) * 100)), progress = state.done / 50000, state = state, } end, })
| Field | Default | Description |
|---|---|---|
label | Required | Shown in Hive, up to 48 characters. |
onWork | Required | function(work, rack, state), see below. |
description | None | Up to 200 characters. |
icon | "cpu" | A Lucide icon name, such as "key-round". |
algorithm | "cpu" | The hashrate it takes: "cpu" for the laptops' own compute, or a miner stick's, such as "sha256" or "scrypt". |
earns | None | {perUnitHour, unit, money?} or a list of up to 4, what one unit earns an hour. Hive shows it as an estimate. unit is up to 12 characters, and money is what perUnitHour is worth. |
getEarns | None | function() returning the same as earns, asked each time Hive shows it, for a rate that moves. |
canUse | Everyone | function(identifier, rack). Only true lets the character give the workload a share in Hive. |
onWork gets:
work:{units, rate, seconds, share}.unitsis the hashrate of its algorithm done since the last call,ratethe same per second, andsharethe percent the rack gives it.rack: the rack, withlaptopsas a count andserials, see Rack Info.state: what it returned asstatefor this rack last time, kept through restarts.
The arguments are copies, so return the state rather than changing it. onWork may return:
| Field | Description |
|---|---|
status | A line Hive shows, up to 120 characters. |
statusKey / statusArgs | A racks: locale key and its values, instead of status. |
progress | 0 to 1, shown as a bar. |
state | A table kept for the rack, 4 KiB of JSON at most. It is saved with the rack once a minute. |
save | true saves the state at once, such as right after paying something out. |
Every onWork runs on its own thread, so it may wait. A rack doesn't call it again while it still runs; the ticks it missed come with the next call, in units and seconds. An error in it is printed in the server console.
#Unregister Rack Workload
Removes a workload your resource registered. Does nothing for another resource's workload.
Example:
lua---@param id string exports["zyke_laptop"]:UnregisterRackWorkload("crack:vault")
#Get Rack Workloads
Returns every registered workload without its functions, sorted by label:
{id, label, description, icon, algorithm, earns, resource}.Example:
lua---@return RackWorkloadInfo[] workloads local workloads = exports["zyke_laptop"]:GetRackWorkloads()
#Mining
#Get Mining Network
Returns a coin's mining network as it pays now, or
nilfor a coin without a network inmining.networks.
perUnitHouris what one unit of hashrate mines an hour now, before the pool's fee.paysis the share of its full rate, 0 to 1, which drops the more of the server mines the coin.serveris the server's hashrate the difficulty counts,currentthe server's hashrate mining it now, andtrendis"rising","falling"or"steady".Example:
lua---@param coin string ---@return MiningNetworkState? network @ {coin, algorithm, perUnitHour, pays, server, current, outside, trend} local network = exports["zyke_laptop"]:GetMiningNetwork("SHD")
#Intrusion Toolkit
A job takes one of three ways in, its mode: localAccess with the laptop near the target, wired with a console cable from a put-down laptop, or wireless with a Wi-Fi adapter and a network. A player and a laptop have one job at a time, and the same character and the same target of your resource wait cooldownSeconds before the next one. Only pass coordinates and options from your own server config, never from a client.
The result comes later, so check the player and the target again before you open or pay anything.
#Request Hack
Yields until the job ends, which can be minutes. Asks for a job and opens Shim for the player once they are signed in. Returns whether it succeeded, the reason and the job's trace,
nilwhen it never started. A refused job returns at once withfalseand the reason. The player is told why a job was refused, except for"options".Example:
lua---@param plyId integer ---@param options LaptopHackOptions ---@return boolean success ---@return string reason ---@return LaptopHackTrace? trace local success, reason, trace = exports["zyke_laptop"]:RequestHack(plyId, { target = "pacific-vault-panel", coords = vec4(253.1, 228.4, 101.7, 160.0), game = "circuit", mode = "wired", difficulty = 3, }) if (success) then -- Check the player and the vault again, then open the door end
| Field | Default | Description |
|---|---|---|
target | Required | Your own id for the target, up to 96 characters, for the cooldown and the trace. |
coords | Required | The target as a vector3, or a vector4 of the port the cable goes into, w the heading it faces so the cable goes in straight. |
game | "sequence" | "sequence", "circuit", "bruteforce" or "portscan". |
mode | "localAccess" | "localAccess", "wired" or "wireless". |
difficulty | 2 | 1 to 5, before the cable and the signal change it. |
timeLimit | The game's own | Seconds, within timeLimit.min and timeLimit.max in shared/unlocked/hacking.lua, 10 to 180 by default. |
ownerId | None | The laptop id of a put-down laptop to use. Without it the job runs on the laptop the player uses or holds, or their own put-down laptop nearby. |
bucket | The player's | The routing bucket the job runs in. |
hardware | None | {adapter = false} lets a wireless job run on the laptop's own Wi-Fi card. |
#Start Hack
Asks for a job like
RequestHackbut returns at once, with the job's id, orniland why it was refused.callback(success, reason, trace)is called when an accepted job ends. A refused job never calls it. Returnsniland"options"without a callback. It waits while theCanStartHackhook does.Example:
lua---@param plyId integer ---@param options LaptopHackOptions ---@param callback fun(success: boolean, reason: string, trace?: LaptopHackTrace) ---@return string? jobId ---@return string? reason local jobId, reason = exports["zyke_laptop"]:StartHack(plyId, { target = "fleeca-atm-12", coords = vector3(147.6, -1035.8, 29.3), game = "bruteforce", }, function(success, reason, trace) if (success) then -- Check the player is still at the ATM, then pay out end end)
#Cancel Hack
Cancels the player's job, which ends with
"cancelled". Only the resource that asked for the job can cancel it. Returnsfalsewhen the player has no job with that id from your resource.Example:
lua---@param plyId integer ---@param jobId string ---@return boolean cancelled exports["zyke_laptop"]:CancelHack(plyId, jobId)
#Get Hack Traces
Yields. Returns the traces of past jobs, newest first. All filters are optional and all of them have to match.
sinceis a Unix time andlimit1 to 500, 100 by default. Traces are kept fortraces.retentionDaysdays, 14 by default.Which fields of a trace are filled follows the player's browsing tier: a direct connection gives the character, the laptop's serial, the network, where they stood and the serials of the parts and plugged devices, a VPN only the serial and the hardware, Onion routing nothing. A wired job always leaves the serial and the hardware.
Example:
lua---@param filter? {id?: string, target?: string, resource?: string, serial?: string, character?: string, since?: integer, limit?: integer} ---@return LaptopHackTrace[] traces local traces = exports["zyke_laptop"]:GetHackTraces({ target = "pacific-vault-panel", since = os.time() - 86400, limit = 20, })
#Hack Reasons
| Reason | Meaning |
|---|---|
success | The player won the job. |
failed | The player lost the puzzle. |
timeout | The player wasn't set up within prepareSeconds, or ran out of time. |
cancelled | CancelHack or the player cancelled it. |
stopped | Your resource or the laptop stopped. |
disconnected | The player left. |
busy | The player already has a job. |
cooldown | The character or the target is still on cooldown. |
disabled | Hacking is turned off. |
options | Wrong options. The player isn't told. |
unavailable | The player isn't there or has no character. |
denied | CanStartHack refused without a key of its own. A key it returns is passed on as the reason. |
laptop, locked, os, hardware, placed, cable, adapter, network, range | What the setup lacks. |
#Events
Listen with AddEventHandler on the server. They cannot block an action; use the hooks for that.
#Rack Events
| Event | Arguments | When |
|---|---|---|
zyke_laptop:racks:Placing | {source, item, slot} | The rack item is used. CancelEvent() to set the rack down your own way with PlaceRack. |
zyke_laptop:racks:Ready | The racks are loaded on start. | |
zyke_laptop:racks:Placed | source, rack | A rack is set down. |
zyke_laptop:racks:PickedUp | source, rack | A rack is picked up. |
zyke_laptop:racks:Removed | rack, items, resource | RemoveRack took a rack away. |
zyke_laptop:racks:LaptopInserted | source, rackId, slot, serial | A laptop goes into a slot. |
zyke_laptop:racks:LaptopRemoved | source, rackId, slot, serial | A laptop comes out of a slot. |
zyke_laptop:racks:PowerChanged | rackId, on, cause | A rack is switched. cause is "switch", "app" or the resource that called SetRackOn. |
zyke_laptop:racks:Tripped | rackId, draw, supply | A rack's breaker trips. |
zyke_laptop:racks:SharesChanged | rackId, shares | A rack's shares change. |
zyke_laptop:racks:MembersChanged | rackId, identifier, added | A member is added or removed. |
zyke_laptop:racks:WorkloadsChanged | A workload is registered or removed. | |
zyke_laptop:racks:Tick | rack, seconds | Every tick, for every rack that is on. |
lua-- Sets racks down on your own floor grid instead of the laptop's placement
AddEventHandler("zyke_laptop:racks:Placing", function(data)
local spot = MyGrid.getFreeSpot(data.source)
if (not spot) then return end
CancelEvent()
CreateThread(function()
exports["zyke_laptop"]:PlaceRack(data.source, spot, {slot = data.slot})
end)
end)
#Hack Trace
Fires when a job starts, with reason
"started", and again with the result, both times with the sametrace.id. TheOnHackTracehook gets the same, for police alerts.Example:
lua---@param plyId integer ---@param trace LaptopHackTrace AddEventHandler("zyke_laptop:HackTrace", function(plyId, trace) if (trace.reason == "started" and trace.coords) then -- Alert the police near trace.coords end end)
The hacking example in shared/unlocked/examples.lua sets up a test barrier on the LSIA apron with a panel for each game and way in. Its server side, examples/hacking/server.lua, shows StartHack from a panel the player uses.