Can-Check Hooks
Hook functions that run before laptop actions are executed. Return false to deny with an optional reason.
Can-check hooks allow you to control player actions in zyke_laptop before they execute. Return false with an optional locale key to deny an action and notify the player. A few hooks answer a question instead, such as which placement area a spot is in or what a domain costs; their return values are described with each hook.
Unless noted otherwise, hooks allow everything by default. Hooks for a feature are only asked while that feature is on, such as Config.Domains.enabled or Config.Racks.enabled.
#File Locations
| Side | File |
|---|---|
| Server | server/unlocked/hooks.lua |
| Client | client/unlocked/hooks.lua |
Both files are unencrypted, ignored by escrow, and safe to edit directly. Edit the existing functions inside the laptop.
Types / Classes
All types / classes can be found in
types/*.lua.plyIdis the requesting player's server ID.ownerIdis a laptop id: the holder's server id for a held laptop, or a placed laptop's negative id. Slot ids areboard,cooler,ram0,ram1,ssd,wifi, andbattery.hostis a domain such asmysite.com.
#How Reason Strings Work
#Server-side reasons
The server sends the reason to the client, which shows it as a notification or in the app, so use a locale key. Each hook falls back to its own key when no reason is given:
| Hook | Default key |
|---|---|
CanPlaceLaptop | laptopPlaceNotAllowed |
CanPlaceComputer | computerPlaceNotAllowed |
CanRemoveComputer | computerRemoveNotAllowed |
CanUseLaptopSkin | settings:skinNotAllowed in Settings, shopNotAllowed at the shop |
CanJoinHotspot | wifi:cannotJoin |
CanPlugLaptopDevice, CanUnplugLaptopDevice | ports:notAllowed |
CanCastToScreen | cast:error:notAllowed |
CanServiceLaptop, CanInstallPart, CanRemovePart | hardware:service:notAllowed |
CanUseCasino | casino:error:notAllowed |
CanRegisterDomain | domains:error:notAllowed |
CanTransferDomain | domains:error:transferNotAllowed |
CanUseDomainProvider | domains:error:providerNotAllowed |
CanEditSite | sites:error:editNotAllowed |
CanPublishSite | sites:error:publishNotAllowed |
CanSubmitForm | sites:form:error:notAllowed |
CanBook | sites:booking:error:notAllowed |
CanChangeVehiclePlate | dmv:error:notAllowed |
CanUseCrypto, CanBuyCrypto, CanSellCrypto, CanSendCrypto | crypto:error:notAllowed |
CanUseMessenger | messengerServer:notAllowed |
CanUseMail | mailServer:notAllowed |
CanSendMail, CanCreateMailbox | mail:error:notAllowed |
CanBuyLaptopShopItem | shopNotAllowed |
CanOpenLaptopStorage | storageNotAllowed |
CanStartHack | hacking:reason:denied |
CanPlaceRack | racks:world:notAllowed |
CanUseRack | racks:world:notAllowed at the rack, racks:error:notAllowed in Hive |
CanSellBusinessItem | mdt:error:notAllowed |
Hooks that return only a boolean, such as CanUseMdt or CanWriteListingReview, take no reason; the app shows its own refusal.
#Client-side reasons
CanUseLaptop also returns a locale key and falls back to laptopCantUse. It gives immediate feedback; enforce trusted restrictions in the server hooks. Add custom reason keys to every locale file in locales/.
This server-side example only lets the Digital Den job plug devices into laptops that are not their own.
Example:
lua---@param plyId integer ---@param ownerId integer ---@param portId string ---@param itemName string ---@param metadata table ---@return boolean allowed ---@return string? reason function CanPlugLaptopDevice(plyId, ownerId, portId, itemName, metadata) if (exports["zyke_laptop"]:IsLaptopOwner(plyId, ownerId)) then return true end if (Z.isJob(plyId, "digitalden")) then return true end return false, "ports:notAllowed" end
#Execution & Validation
Keep hooks synchronous and free of side effects. Returning true continues the normal flow; built-in reach, job, limit, item, and power checks still apply. Hooks marked keep it fast run very often, so never wait on anything in them. A few hooks note that they may yield; the laptop reads its state again after them.
Dead, downed, and cuffed players are kept off the laptop by IsPlayerIncapacitated before any other hook is asked. Admin commands such as laptop_computer skip the player hooks where noted.
#Server-Sided Hooks
All server-sided hooks are in server/unlocked/hooks.lua.
#Is Player Incapacitated
Return
truewhile a player can't use a laptop: dead, downed, or cuffed. The server then refuses to take a laptop out, put one down, pick one up, open its lid, screen, ports, or service view, or touch a rack, andIsAtLaptopreturnsfalse. The client hook of the same name is asked as well, through a state bag, and a player carrying a box off Digital Den's shelves is refused too; this hook covers what the server knows itself.By default it checks handcuffs on the ped and
Z.isPlayerDead. Replace or extend it for a death or restraint scriptzyke_libdoesn't know.Example:
lua---@param plyId integer ---@return boolean incapacitated function IsPlayerIncapacitated(plyId) local ped = GetPlayerPed(plyId) if (ped ~= 0 and IsPedHandcuffed(ped)) then return true end -- A state bag your own death script sets if (Player(plyId).state.isDowned == true) then return true end return Z.isPlayerDead(plyId) end
#Get Laptop Placement Area
Returns the area a laptop is put down in and how many laptops one character may have put down there, for rooms with a limit of their own such as a crypto farm's server room or a housing interior. Laptops in an area count only against that area's limit, never against
placement.maxPerCharacteror another area's. Returnnilfor anywhere else, wheremaxPerCharacterapplies.The area id is text of up to 64 characters; anything else counts as outside an area and prints a warning. The limit is a number, or
falsefor no limit, so returning any area id withfalselifts the limit wherever you decide. A laptop keeps the area it was put down in until it is picked up.Asked as a laptop is put down, after the reach check and before
CanPlaceLaptop. By default it returns the first matching area inConfig.Settings.placement.areas.Example:
lua---@param plyId integer ---@param coords vector4 @ The spot picked, w is the heading ---@param bucket integer @ The player's routing bucket ---@return string? areaId ---@return integer | false | nil limit function GetLaptopPlacementArea(plyId, coords, bucket) -- my_mining stands for your own resource local room = exports["my_mining"]:GetRoomAt(coords.xyz, bucket) if (room) then return "mining:" .. room.id, room.maxLaptops end local area = FindLaptopPlacementArea(coords.xyz, bucket) if (not area) then return nil end return area.id, area.limit end
#Can Place Laptop
Called before a player puts a laptop down, after the reach and placement limit checks. Use it to keep laptops off other players' property or out of an event's bucket.
Example:
lua---@param plyId integer ---@param coords vector4 @ The spot picked, w is the heading ---@param bucket integer @ The player's routing bucket, the laptop stands in it ---@return boolean allowed ---@return string? reason function CanPlaceLaptop(plyId, coords, bucket) -- Bucket 500 is an event instance on this server if (bucket == 500) then return false, "laptopPlaceNotAllowed" end return true end
#Can Place Computer
Called before a player sets up a desktop computer from its item, after the job and
computer.maxPerCharacterchecks. Staff setting one up with thelaptop_computercommand skip it.Example:
lua---@param plyId integer ---@param coords vector4 @ The spot picked, w is the heading ---@param bucket integer ---@return boolean allowed ---@return string? reason function CanPlaceComputer(plyId, coords, bucket) -- Only inside the Mission Row station if (#(coords.xyz - vector3(441.0, -982.0, 30.7)) > 40.0) then return false, "computerPlaceNotAllowed" end return true end
#Can Remove Computer
Called before a player takes a desktop computer away, once they are the character who set it up or staff. Use it to keep a business's own computers in place.
Example:
lua---@param plyId integer ---@param ownerId integer @ The computer's laptop id ---@return boolean allowed ---@return string? reason function CanRemoveComputer(plyId, ownerId) local laptop = exports["zyke_laptop"]:GetLaptop(ownerId) -- Computers in the station stay where they are if (laptop and laptop.coords and #(laptop.coords.xyz - vector3(441.0, -982.0, 30.7)) < 40.0) then return false, "computerRemoveNotAllowed" end return true end
#Is Laptop Bucket Persistent
Return
truefor a routing bucket laptops stay put down in across restarts. A laptop in any other bucket stays only while the character who put it down is in that bucket, and goes back into their inventory after that. A computer set up in such a bucket tells the player it is temporary, and a laptop rack can't be set down there at all. Returntruefor buckets that always mean the same place, such as housing that uses the property id as the bucket, never for instances handed out as needed, whose numbers are reused for strangers.Defaults to
Config.Settings.placement.persistentBuckets. Keep it fast: it is asked every two seconds for each placed laptop.Example:
lua---@param bucket integer ---@return boolean persistent function IsLaptopBucketPersistent(bucket) -- Housing uses buckets 1000 to 1999, one per property if (bucket >= 1000 and bucket < 2000) then return true end local buckets = Config.Settings.placement.persistentBuckets or {0} for i = 1, #buckets do if (buckets[i] == bucket) then return true end end return false end
#Can Use Laptop Skin
Called before a player puts a backplate skin on their laptop in Settings, before a skin is bought or fitted at the shop or the Digital Den counter, and for the skin kept on the client when laptops have no item. Use it to keep some skins for donors or a job.
Example:
lua---@param plyId integer ---@param skinId? string @ Id listed under Config.Settings.skins, nil for the stock lid ---@return boolean allowed ---@return string? reason function CanUseLaptopSkin(plyId, skinId) if (skinId == "carbon_gold" and not IsPlayerAceAllowed(tostring(plyId), "zyke_laptop.donor")) then return false, "settings:skinNotAllowed" end return true end
#Can Join Hotspot
Called when a player joins a Wi-Fi hotspot, after the range and password checks. Use it to keep a station's network for its job.
Example:
lua---@param plyId integer ---@param hotspotId string @ Id from shared/unlocked/hotspots.lua ---@return boolean allowed ---@return string? reason function CanJoinHotspot(plyId, hotspotId) if (hotspotId == "mission_row_pd" and not Z.isJob(plyId, "police")) then return false, "wifi:cannotJoin" end return true end
#Can Plug Laptop Device
Called before a device goes into a laptop's port, from the port view and the
PlugDeviceexport.metadatais a copy of the item's metadata. See the example under How Reason Strings Work.Example:
lua---@param plyId integer ---@param ownerId integer @ Laptop id ---@param portId string @ Such as "usba", see shared/locked/ports.lua ---@param itemName string ---@param metadata table ---@return boolean allowed ---@return string? reason function CanPlugLaptopDevice(plyId, ownerId, portId, itemName, metadata) return true end
#Can Unplug Laptop Device
Called before a device is pulled out of a laptop's port, from the port view and the
UnplugDeviceexport. Anyone at a placed laptop can pull a device out by default, as with a real laptop.Example:
lua---@param plyId integer ---@param ownerId integer @ Laptop id ---@param portId string ---@param itemName string ---@param metadata table @ A copy of the device's metadata ---@return boolean allowed ---@return string? reason function CanUnplugLaptopDevice(plyId, ownerId, portId, itemName, metadata) -- Only the owner pulls a power bank out if (itemName == "laptop_powerbank" and not exports["zyke_laptop"]:IsLaptopOwner(plyId, ownerId)) then return false, "ports:notAllowed" end return true end
#Can Cast To Screen
Called when a cast starts, after the cable and range checks. Use it to keep a briefing room's screen for the police.
Example:
lua---@param plyId integer ---@param screenId string @ "config:<id>" for a screen in the config, "placed:<number>" for one placed with the creator ---@return boolean allowed ---@return string? reason function CanCastToScreen(plyId, screenId) -- mrpd_briefing is a screen added to Config.Settings.cast.screens if (screenId == "config:mrpd_briefing" and not Z.isJob(plyId, "police")) then return false, "cast:error:notAllowed" end return true end
#Can Create Screen
Return
trueto let a player place, change, and delete screens with the screen creator. Asked on every change the creator makes; only exactlytrueallows it. By default the players with the creator's ace permission,Config.Settings.cast.creator.permission.Example:
lua---@param plyId integer ---@return boolean allowed function CanCreateScreen(plyId) return Z.hasPermission(plyId, Config.Settings.cast.creator.permission) or Z.isJob(plyId, "realestate") end
#Can Service Laptop
Called before a player opens the service view of a laptop they didn't put down. Only asked while
hardware.service.othersis on; owners always may. Use it to leave repairs to a repair shop's job.Example:
lua---@param plyId integer ---@param ownerId integer @ Laptop id ---@return boolean allowed ---@return string? reason function CanServiceLaptop(plyId, ownerId) if (not Z.isJob(plyId, "digitalden")) then return false, "hardware:service:notAllowed" end return true end
#Can Install Part
Called before a part goes into a laptop, in the service view and through the
SetLaptopHardwareSlotexport.metadatais a copy of the part's metadata. When called through the export,ownerIdis the player's own server id.Example:
lua---@param plyId integer ---@param ownerId integer @ Laptop id ---@param slotId string ---@param itemName string ---@param metadata table ---@return boolean allowed ---@return string? reason function CanInstallPart(plyId, ownerId, slotId, itemName, metadata) -- Boards only go in at the repair shop if (slotId == "board" and not Z.isJob(plyId, "digitalden")) then return false, "hardware:service:notAllowed" end return true end
#Can Remove Part
Called before a part comes out of a laptop, in the service view and through the
SetLaptopHardwareSlotexport. When called through the export,ownerIdis the player's own server id.Example:
lua---@param plyId integer ---@param ownerId integer @ Laptop id ---@param slotId string ---@param itemName string ---@param metadata table @ A copy of the part's metadata ---@return boolean allowed ---@return string? reason function CanRemovePart(plyId, ownerId, slotId, itemName, metadata) if (slotId == "board" and not Z.isJob(plyId, "digitalden")) then return false, "hardware:service:notAllowed" end return true end
#Can Use Casino
Called before every casino action. Use it to require a job, a licence, or your own age rule;
Config.Settings.casino.minAgeis checked separately. Keep it fast.Example:
lua---@param plyId integer ---@return boolean allowed ---@return string? reason function CanUseCasino(plyId) -- my_licences stands for your own licence resource if (exports["my_licences"]:HasLicence(plyId, "gambling")) then return true end return false, "casino:error:notAllowed" end
#Can Post Job Listing
Return
trueto let a player post listings on the Hireline job board and handle the applicants for their current job.rankedis whether they hold one of the job's boss grades or the grade inConfig.Settings.jobs.posting.grades; return it as it is to keep that rule. Only exactlytrueallows it. Jobs inposting.excludednever get here. Asked on every employer action. Keep it fast.Example:
lua---@param plyId integer ---@param jobName string ---@param ranked boolean ---@return boolean allowed function CanPostJobListing(plyId, jobName, ranked) -- The government posts through the mayor's office only if (jobName == "government") then return Z.getJob(plyId).grade.level >= 4 end return ranked end
#Can Write Listing Review
Return
falseto stop a character from reviewing a listing on Ratewell, for example new characters. Asked after the laptop's own rules: the listing is active, it isn't one they manage, and they don't hold its job.allowedis therefore alwaystrue; return it as it is to keep the default. Only exactlytrueallows the review. Asked on every review written. Keep it fast.
listingis{id, kind, ref, slug, name}.kindisbusiness,job,site,place, orplayer, andrefis the department id, job name, host, or place id.Example:
lua---@param plyId integer ---@param listing ReviewListingInfo ---@param allowed boolean ---@return boolean allowed function CanWriteListingReview(plyId, listing, allowed) -- Police can't review their own station's competitors if (listing.kind == "business" and listing.ref == "sheriff" and Z.isJob(plyId, "police")) then return false end return allowed end
#Can Manage Review Listing
Decides who manages a
businessorjoblisting on Ratewell: replies, reports, and edits. Only asked for players who hold one of the listing's jobs, at any grade.rankedis whether they hold a boss grade or reachConfig.Reviews.owners.grades; return it as it is to keep that rule. Only exactlytrueallows it. Site, place, and player listings are run by their owner and never get here. Asked on every owner action. Keep it fast.Example:
lua---@param plyId integer ---@param listing ReviewListingInfo ---@param ranked boolean ---@return boolean allowed function CanManageReviewListing(plyId, listing, ranked) -- Every Burger Shot employee may answer reviews if (listing.kind == "business" and listing.ref == "burgershot") then return true end return ranked end
#Can Use MDT
Return
falseto keep a player out of their department's MDT, for example without a badge item or a certain licence. Asked once the player's job belongs to the department and itsrequireDutypassed; only exactlytruelets them in. Asked on every MDT action. Keep it fast.Example:
lua---@param plyId integer ---@param department string @ A key of Config.Mdt.departments, such as "lspd" ---@return boolean allowed function CanUseMdt(plyId, department) if (department == "lspd" and not Z.hasItem(plyId, "police_badge")) then return false end return true end
#Can Use MDT Permission
Return
trueorfalseto change what a member may do in the MDT.allowedis the answer from the department'spermissionsinConfig.Mdt, where boss grades have every permission; return it as it is to keep that rule. Only exactlytrueallows it. Asked once per permission and action, and the answer is kept for that action. Keep it fast.Example:
lua---@param plyId integer ---@param department string @ A key of Config.Mdt.departments ---@param permission MdtPermission @ Such as "reports.create" or "warrants.approve" ---@param allowed boolean ---@return boolean allowed function CanUseMdtPermission(plyId, department, permission, allowed) -- Warrants need a judge's sign-off on this server if (permission == "warrants.approve") then return Z.isJob(plyId, "judge") end return allowed end
#Can Register Domain
Called before a player registers a domain at Namestead, after the name, reserved, availability, and
maxPerCharacterchecks and before the money is taken. Use it to keep some endings for businesses or to require a licence. It may yield.Example:
lua---@param plyId integer ---@param host string @ Such as "mysite.com" ---@param mode "buy" | "rent" ---@return boolean allowed ---@return string? reason function CanRegisterDomain(plyId, host, mode) if (host:find("%.gov$") and not Z.isJob(plyId, "government")) then return false, "domains:error:notAllowed" end return true end
#Get Domain Price
Returns what a domain costs this player.
priceis the ending's price inConfig.Domains.tldswith the premium for short names and common words; forrentit is the price of one period. Return a whole number of 0 or more; anything else usesprice. Asked for every search result and again at purchase. Keep it fast.Example:
lua---@param plyId integer ---@param host string @ Such as "mysite.com" ---@param mode "buy" | "rent" ---@param price integer ---@return integer price function GetDomainPrice(plyId, host, mode, price) -- Weazel News gets half off if (Z.isJob(plyId, "weazelnews")) then return math.floor(price / 2) end return price end
#Can Transfer Domain
Called before an owner offers their domain to another character, after the receiver's domain limit is checked. The receiver still has to accept the offer. It may yield.
Example:
lua---@param plyId integer ---@param host string @ Such as "mysite.com" ---@param targetId integer @ The player the domain is offered to ---@param price integer @ What the receiver pays, 0 for a gift ---@return boolean allowed ---@return string? reason function CanTransferDomain(plyId, host, targetId, price) if (host:find("%.gov$")) then return false, "domains:error:transferNotAllowed" end return true end
#Can Use Domain Provider
Called before an owner points their domain at a provider in My domains, and before a Sitewright site is published on a domain pointed elsewhere, with the provider id
sitewright. Use it to keep a provider for a job.Example:
lua---@param plyId integer ---@param host string @ Such as "mysite.com" ---@param providerId string @ The id the providing resource registered with RegisterDomainProvider ---@return boolean allowed ---@return string? reason function CanUseDomainProvider(plyId, host, providerId) -- my_news stands for a resource that registered a news site provider if (providerId == "my_news" and not Z.isJob(plyId, "weazelnews")) then return false, "domains:error:providerNotAllowed" end return true end
#Can Edit Site
Called when the owner or a co-editor opens a domain's site in Sitewright, and before every save and delete. Use it to require a job or a licence. Keep it fast.
Example:
lua---@param plyId integer ---@param host string @ Such as "mysite.com" ---@param role SiteRole @ "owner", "publisher" or "editor" ---@return boolean allowed ---@return string? reason function CanEditSite(plyId, host, role) if (Player(plyId).state.inJail == true) then return false, "sites:error:editNotAllowed" end return true end
#Can Publish Site
Called before the owner or a publisher puts a Sitewright site live, after the staff lock and provider checks.
docis the saved draft that would go live; seenui_source/src/sites/doc/types.tsfor its fields. Use it to filter words or hold sites for staff review. It may yield.Example:
lua---@param plyId integer ---@param host string @ Such as "mysite.com" ---@param doc SiteDoc ---@param role SiteRole @ "owner" or "publisher" ---@return boolean allowed ---@return string? reason function CanPublishSite(plyId, host, doc, role) -- Only staff publish on .gov if (host:find("%.gov$") and not Z.hasPermission(plyId, "admin")) then return false, "sites:error:publishNotAllowed" end return true end
#Can Submit Form
Called before a visitor sends a contact form on a Sitewright site, after the answers are checked and before the site's limit is counted. Use it to filter words or mute a player.
Example:
lua---@param plyId integer ---@param host string @ Such as "mysite.com" ---@param answers {name: string, phone: string, subject: string, message: string} @ Empty for the fields the form hides ---@return boolean allowed ---@return string? reason function CanSubmitForm(plyId, host, answers) if (answers.message:lower():find("free money")) then return false, "sites:form:error:notAllowed" end return true end
#Can Book
Called before a visitor books a time with a Sitewright site's booking block, after the time is checked against the block's hours and before its places are counted. Use it to require a licence for a service or to keep someone from booking. It may yield.
Example:
lua---@param plyId integer ---@param host string @ Such as "mysite.com" ---@param booking {block: string, service: string, serviceName: string, startsAt: integer, endsAt: integer, name: string, note: string} @ Times are os.time() values ---@return boolean allowed ---@return string? reason function CanBook(plyId, host, booking) -- my_licences stands for your own licence resource if (booking.service == "flight_lesson" and not exports["my_licences"]:HasLicence(plyId, "medical")) then return false, "sites:booking:error:notAllowed" end return true end
#Can Change Vehicle Plate
Called before a player changes a plate on the DMV site, after the format check and before the garage resource checks the plate. Asked on the site's availability check and again before the fee is taken. Use it to keep custom plates for donors or refuse words you don't want on the road.
Example:
lua---@param plyId integer ---@param vehicleId string @ The garage resource's id for the vehicle, the VIN with zyke_garages ---@param plate string @ The new plate, uppercase and in the site's format ---@return boolean allowed ---@return string? reason function CanChangeVehiclePlate(plyId, vehicleId, plate) if (plate:find("LSPD")) then return false, "dmv:error:notAllowed" end return true end
#Get Vehicle Plate Price
Returns what a plate change costs this player for this vehicle.
priceisConfig.Dmv.price. Return a whole number; anything else usesprice, and a negative one is free. Asked for every vehicle the site lists. Keep it fast.Example:
lua---@param plyId integer ---@param vehicleId string @ The garage resource's id for the vehicle, the VIN with zyke_garages ---@param price integer ---@return integer price function GetVehiclePlatePrice(plyId, vehicleId, price) if (Z.isJob(plyId, "police")) then return 0 end return price end
#Can Use Crypto
Called before every request Satchel, the crypto app, makes, before the rate limit. Use it to require a job or a phone item. Keep it fast.
Example:
lua---@param plyId integer ---@return boolean allowed ---@return string? reason function CanUseCrypto(plyId) if (not Z.hasItem(plyId, "phone")) then return false, "crypto:error:notAllowed" end return true end
#Can Buy Crypto
Called before a player buys a coin, after the order size and price checks, before the daily caps and before the money is taken. It may yield; the price is read again after it.
Example:
lua---@param plyId integer ---@param coinId string @ Such as "BSN" ---@param fiat integer @ The money the order spends, fee included ---@return boolean allowed ---@return string? reason function CanBuyCrypto(plyId, coinId, fiat) -- Large orders need a trading licence if (fiat > 50000 and not Z.hasItem(plyId, "trading_licence")) then return false, "crypto:error:notAllowed" end return true end
#Can Sell Crypto
Called before a player sells a coin, before the price, order size, and daily cap checks and before the coins leave their wallet.
Example:
lua---@param plyId integer ---@param coinId string @ Such as "BSN" ---@param units integer @ Base units sold, 100000000 to a Bastion ---@return boolean allowed ---@return string? reason function CanSellCrypto(plyId, coinId, units) return true end
#Can Send Crypto
Called before a player sends coins to another wallet, after the amount, fee, and recipient checks, before the coins leave the wallet. Use it to block transfers between characters of the same player.
Example:
lua---@param plyId integer ---@param coinId string @ Such as "BSN" ---@param units integer @ Base units sent, without the network fee ---@param recipient string @ The receiving character's identifier, who may be offline ---@return boolean allowed ---@return string? reason function CanSendCrypto(plyId, coinId, units, recipient) -- my_accounts stands for your own multicharacter resource if (exports["my_accounts"]:IsSamePlayer(Z.getIdentifier(plyId), recipient)) then return false, "crypto:error:notAllowed" end return true end
#Can Use Messenger
Called when Natter starts its session and before every request. Use it to require a phone item or a job. Keep it fast.
Example:
lua---@param plyId integer ---@return boolean allowed ---@return string? reason function CanUseMessenger(plyId) if (Player(plyId).state.inJail == true) then return false, "messengerServer:notAllowed" end return true end
#Can Read Messenger Metadata
Return
falseto stop a player from reading who messaged whom and when through theGetMessengerMetadataexport. Message content is never part of it. Every request is allowed by default, since only server resources can call the export.Example:
lua---@param plyId integer @ The player asking, such as an officer ---@param number string @ The account looked into ---@return boolean allowed function CanReadMessengerMetadata(plyId, number) return Z.isJob(plyId, "police") end
#Can Use Mail
Called when Mail starts its session and before every request. A refusal on a request also ends the session; the reason is only shown when the session starts. Use it to require a job or a licence. Keep it fast.
Example:
lua---@param plyId integer ---@return boolean allowed ---@return string? reason function CanUseMail(plyId) if (Player(plyId).state.inJail == true) then return false, "mailServer:notAllowed" end return true end
#Can Send Mail
Called before a player sends a mail, after every address and the text are checked. Use it to keep a company address for outgoing mail on duty only.
Example:
lua---@param plyId integer ---@param from string @ The address it goes out from, such as "[email protected]" ---@param recipients string[] @ Every To, Cc and Bcc address ---@return boolean allowed ---@return string? reason function CanSendMail(plyId, from, recipients) if (from:find("@lspd%.gov$") and not Z.getJob(plyId).onDuty) then return false, "mail:error:notAllowed" end return true end
#Can Create Mailbox
Called before a player makes an address on a domain, for the domain's owner and for the characters they let make their own addresses, after the name and limit checks. Use it to keep names such as
ceo@for the owner.Example:
lua---@param plyId integer ---@param address string @ Such as "[email protected]" ---@return boolean allowed ---@return string? reason function CanCreateMailbox(plyId, address) if (address:find("^ceo@") or address:find("^admin@")) then return false, "mail:error:notAllowed" end return true end
#Can Buy Laptop Shop Item
Called before something is bought at the shop, before the money is taken, and for each offer rung up at the Digital Den counter, where
plyIdis the customer. Use it to keep flagship laptops for donors or parts for a repair job. A new laptop bought with a backplate asks again for theskin:<id>offer.Example:
lua---@param plyId integer ---@param offerId string ---@return boolean allowed ---@return string? reason function CanBuyLaptopShopItem(plyId, offerId) if (offerId:find("^laptop:flagship:") and not IsPlayerAceAllowed(tostring(plyId), "zyke_laptop.donor")) then return false, "shopNotAllowed" end return true end
The offer ids offerId takes:
| Offer | Id |
|---|---|
| A laptop in its stock finish | laptop:<prebuilt>:stock |
| A laptop in a finish | laptop:<prebuilt>:<finish>, such as laptop:performance:midnight |
| Refinishing a laptop | refinish:stock, refinish:<finish> |
| A backplate | skin:<skinId> |
| A part | part:<item>:<model>, such as part:laptop_ram:d5_16 |
| A tool | tools:<item>, such as tools:thermal_paste |
| An accessory | accessories:<item>, such as accessories:usb_drive |
#Can Open Laptop Storage
Called every time a player opens one of the shop's storages, after the storage's
jobsare checked. Use it to require being on duty or a rank.Example:
lua---@param plyId integer ---@param storageId string @ The storage's id in Config.Shop.storages ---@return boolean allowed ---@return string? reason function CanOpenLaptopStorage(plyId, storageId) -- The stock room is for managers and up if (storageId == "digitalden_stock" and Z.getJob(plyId).grade.level < 3) then return false, "storageNotAllowed" end return true end
#Send Laptop Cloud Alert
Texts a character about their Nimbus account while
Config.Settings.cloud.alertsis on. By default it sends a phone text through the phone bridge; without a phone resource that takes texts nothing is sent. Replace it to send these through your own phone, mail, or notification resource. It runs in its own thread, so it may yield.
kindisnewDevice(a laptop was linked),resetAsked,signedOut(a laptop left the account), orreset.data.deviceis the laptop's name fornewDeviceandsignedOut.Example:
lua---@param identifier string @ The account's character, who may be offline ---@param kind "newDevice" | "resetAsked" | "signedOut" | "reset" ---@param data {device?: string} function SendLaptopCloudAlert(identifier, kind, data) -- Only the text of this kind is built, the device texts need a device local texts = { newDevice = function() return T("cloud:server:alert:newDevice", {data.device}) end, resetAsked = function() return T("cloud:server:alert:resetAsked") end, signedOut = function() return T("cloud:server:alert:signedOut", {data.device}) end, reset = function() return T("cloud:server:alert:reset") end, } -- my_mail stands for your own mail resource exports["my_mail"]:SendMail(identifier, T("cloud:server:sender"), texts[kind]()) end
#Can Start Hack
Called when another resource asks for an intrusion job with
RequestHackorStartHack, after the busy and cooldown checks, and again when the player starts the job on their laptop. Whether the player may work on the target at all is the calling resource's check. It may yield.A refusal gives the calling resource the reason, and the player sees it. A reason with a
:is used as a locale key as it is; a plain code becomeshacking:reason:<code>.Example:
lua---@param plyId integer ---@param job LaptopHackJob @ target, coords, bucket, game, mode, difficulty, timeLimit and needsAdapter ---@param resource string @ The resource that asked for the job ---@return boolean allowed ---@return string? reason function CanStartHack(plyId, job, resource) -- Police can't run jobs while on duty if (Z.isJob(plyId, "police") and Z.getJob(plyId).onDuty) then return false, "hacking:reason:denied" end return true end
#On Hack Trace
Called with a job's trace, for police alerts. Called when the job starts with
trace.reasonset tostarted, and again when it ends with the result, both times with the sametrace.id. Which fields are filled depends on the player's OS, VPN, and Onion, and onConfig.Hacking.traces. The server eventzyke_laptop:HackTracegets the same. It returns nothing and can't change the job's result. Does nothing by default.Example:
lua---@param plyId integer ---@param trace LaptopHackTrace function OnHackTrace(plyId, trace) if (trace.reason ~= "started" or not trace.coords) then return end -- my_dispatch stands for your own dispatch resource exports["my_dispatch"]:Alert("police", "Network intrusion", trace.coords) end
#Can Place Rack
Called before a player sets a laptop rack down, after the
Config.Racks.maxPerCharacterand persistent bucket checks. Use it to keep racks out of public places or limit them to a job.Example:
lua---@param plyId integer ---@param coords vector4 @ w is the heading ---@param bucket integer ---@return boolean allowed ---@return string? reason function CanPlaceRack(plyId, coords, bucket) -- Racks only go in housing buckets if (bucket < 1000 or bucket >= 2000) then return false, "racks:world:notAllowed" end return true end
#Can Use Rack
Called before a player does something to a rack. At the rack,
actionisinsert,take,power, orpickUp; it is asked after the reach andConfig.Racks.physicalAccesschecks. In Hive, it isopen,power,profile,shares,rename,addMember, orremoveMember, asked after the member, owner-only, and rate limit checks. Only a rack's owner and members run it from Hive. A rack picked up through another resource is asked withpickUptoo.Example:
lua---@param plyId integer ---@param rackId integer ---@param action RackAction ---@return boolean allowed ---@return string? reason function CanUseRack(plyId, rackId, action) -- Only the crypto farm job changes a rack's profile if (action == "profile" and not Z.isJob(plyId, "cryptofarm")) then return false, "racks:error:notAllowed" end return true end
#Can Remove Rack
Return
trueto let a player who doesn't own an empty rack pick it up, for example the police. Asked afterCanUseRackwithpickUpand the empty check; the owner always may. Returnsfalseby default. Everyone may take the laptops out of it first whileConfig.Racks.physicalAccessisanyone. A refusal showsracks:world:notOwner.Example:
lua---@param plyId integer ---@param rackId integer ---@param owner string @ Identifier of the character who set it down ---@return boolean allowed function CanRemoveRack(plyId, rackId, owner) return Z.isJob(plyId, "police") end
#Get Rack Power Supply
Returns the watts a rack may draw before its breaker trips.
supplyisConfig.Racks.power.supply. Return 0 to cut its power, such as while a property's electricity is off, or more for a warehouse with three-phase power; a negative number counts as 0, and anything else usessupply. TheSetRackPowerSupplyexport overrides it per rack. Keep it fast: it is asked every tick for every rack.Example:
lua---@param rackId integer ---@param owner string ---@param coords vector4 ---@param bucket integer ---@param supply number @ The configured supply ---@return number supply function GetRackPowerSupply(rackId, owner, coords, bucket, supply) -- my_power stands for your own electricity resource if (not exports["my_power"]:HasPower(bucket)) then return 0 end return supply end
#Can Make Laptop Payment
Called before a player pays through the laptop's pay sheet for another resource's app or site, after the method and connection checks and before the money moves. A refusal answers the app with the code
notAllowed; the returned reason is not passed on.Example:
lua---@param plyId integer ---@param payment SdkPayment @ id, resource, app, label, amount, currency, payee and more ---@return boolean allowed ---@return string? reason function CanMakeLaptopPayment(plyId, payment) if (Player(plyId).state.inJail == true) then return false end return true end
#Can Use Laptop App
Return
falseto hide an app or site of another resource from a player and refuse its server calls, after its own access rules passed. Only exactlyfalserefuses. The answer is kept for 5 seconds per player and app, and cleared when their job changes. It also decides theCanUseAppexport.Example:
lua---@param plyId integer ---@param kind "app" | "site" ---@param id string ---@param resource string ---@return boolean allowed function CanUseLaptopApp(plyId, kind, id, resource) -- my_darkweb stands for your own resource if (resource == "my_darkweb" and Z.isJob(plyId, "police")) then return false end return true end
#Can Sell Business Item
Called once per line before a business's bill goes out, at the MDT register and in its website order block. Use it to card customers for alcohol or cap how much one customer buys.
staffIdisnilfor a website order, which shows its own refusal instead of the reason.Example:
lua---@param staffId? integer @ The member at the register, nil for a website order ---@param customerId integer ---@param departmentId string @ The business, see shared/unlocked/business.lua ---@param entry MdtCatalogueEntry @ kind "goods" names its item, "service" moves none ---@param qty integer ---@return boolean allowed ---@return string? reason function CanSellBusinessItem(staffId, customerId, departmentId, entry, qty) -- my_licences stands for your own licence resource if (entry.item == "beer" and not exports["my_licences"]:IsAdult(customerId)) then return false, "mdt:error:notAllowed" end return true end
#Client-Sided Hooks
All client-sided hooks are in client/unlocked/hooks.lua.
#Can Use Laptop
Called before the player uses a laptop.
IsPlayerIncapacitatedis checked first. The actions:
Action When takeOutTaking a laptop out use,pickUpUsing or picking up a placed laptop or a demo unit portsOpening a laptop's ports, or plugging into a charging outlet serviceOpening the service view screenAnother resource opens the screen with the OpenScreenexportmountUsing a vehicle's mounted laptop placeComputer,removeComputerSetting a computer up or taking one away placeRack,rackSetting a laptop rack down or working at one placeRouter,routerSetting a router or extender down or picking one up placePrinter,printerSetting a printer down or using one By default it refuses while swimming, and in a vehicle for every action but
takeOut,mount,ports, andscreen. Laptops in vehicles followshared/unlocked/vehicles.lua; returnfalsefortakeOutormountwhileIsPedInAnyVehicleto keep them out of vehicles, or out of some of them.Example:
lualocal inVehicleActions = {takeOut = true, mount = true, ports = true, screen = true} ---@param action "takeOut" | "use" | "pickUp" | "ports" | "service" | "screen" | "mount" | "placeComputer" | "removeComputer" | "placeRack" | "rack" | "placeRouter" | "router" | "placePrinter" | "printer" ---@return boolean allowed ---@return string? notification function CanUseLaptop(action) local ped = PlayerPedId() if (IsPedSwimming(ped)) then return false, "laptopCantUse" end if (IsPedInAnyVehicle(ped, true) and not inVehicleActions[action]) then return false, "laptopCantUse" end -- my_minigames stands for your own resource if (exports["my_minigames"]:IsPlaying()) then return false, "laptopCantUse" end return true end
#Is Player Incapacitated
Return
truewhile the player can't use a laptop with their hands: dead, downed, or cuffed. It is checked before they start using a laptop, a few times a second while one is in use, and once a second while one is near. A laptop in use is put away or closed as soon as it turnstrue, and the server is told through a state bag so it refuses its own entry points meanwhile. Keep it fast.By default it checks death on the ped,
Z.isPlayerDead, which covers the death scriptszyke_libsupports and QBCore's last stand, andZ.isPlayerCuffed, which covers the game's cuffs and qb-policejob's. Replace or extend it for a death or restraint scriptzyke_libdoesn't know.Example:
lua---@return boolean incapacitated function IsPlayerIncapacitated() if (IsEntityDead(PlayerPedId())) then return true end -- A state bag your own restraint script sets if (LocalPlayer.state.ziptied == true) then return true end return Z.isPlayerDead() or Z.isPlayerCuffed() end