Documentation

Example Sites

The example sites that ship with the laptop, and how to build your own from one.

The laptop ships with three example sites in its examples/ folder. They start with the laptop and each is a starting point to copy for your own site or app.

ExampleAddressWhat it shows
Mechaniclscustoms.comA business site with photos and a button that sets a GPS route to the nearest shop.
Policelspd.govA police academy application form, and an LSPD Recruitment app for officers to accept or decline applications.
Arcadesnake.com, 2048.comSnake and 2048, each with an all-time leaderboard.

#Turning Them On or Off

Each example has an enabled switch and its settings in shared/unlocked/examples.lua, under Config.Examples.

KeyDefaultDescription
mechanic.enabledtrueRegisters lscustoms.com.
police.enabledtrueRegisters lspd.gov and the recruitment app, and creates its table.
arcade.enabledtrueRegisters snake.com and 2048.com, and creates the scores table.

An example that is turned off registers nothing and creates no table.

#Mechanic

mechanic.site holds the address, title and the Lucide icon shown in search results. mechanic.locations lists the shops on the site, each {label, coords}. The route button sets a GPS route to the shop closest to the player.

This example runs on the client only, with no server script or table.

#Police

The site is a public application form. A character can have one pending application at a time, and after a declined one waits cooldownHours (48 by default) before applying again.

The LSPD Recruitment app lists the applications by status and lets a reviewer accept or decline them. A note is required when declining. Reviewers can't review their own application. An applicant who is online gets a laptop notification when their application is reviewed.

KeyDefaultDescription
police.sitelspd.govAddress and title of the site.
police.job"police"Job whose members review applications.
police.reviewGrade3Lowest grade of that job that gets the recruitment app.
police.cooldownHours48Hours after a declined application before the character can apply again.
police.fieldsLength limits of each form field and the reviewer's note, checked on the page and again on the server. age is the allowed age range.

The app is only registered on the clients of officers of reviewGrade and up, and follows job changes. The server checks the job and grade again on every reviewer request. With the app store on, officers download the app from the store on Facade, Lumen and Solace. Add example_police_app to Config.Settings.appStore.preinstalled to have it installed from the start.

Applications are kept in zyke_laptop_police_applications, created on start. status is pending, accepted or declined.

#Application Reviewed

Fires on the server after a reviewer accepts or declines an application. The application is never hired automatically; listen here to set the job yourself.

The application carries id, identifier (the applicant's character), name, status, age, phone, availability, experience, motivation, note, reviewerName, createdAt and reviewedAt (seconds since the epoch).

Example:

lua
---@param application table
AddEventHandler("zyke_laptop:examples:police:ApplicationReviewed", function(application)
    if (application.status ~= "accepted") then return end

    local plyId = Z.getPlayerId(application.identifier)
    if (not plyId) then return end -- Offline, hire them on their next login

    -- Set the job through your framework or job script here, for example to police grade 0
end)

#Arcade

Every game is a site listed in arcade.games, with its id, url, title, page, the rules handed to the page, and limits the server checks scores against.

KeyDefaultDescription
arcade.boardSize10Entries on each game's all-time board.
arcade.submitCooldown4000Milliseconds between two submitted scores.
arcade.clockTolerance2000Milliseconds a game may claim to have lasted beyond what the server measured, for latency.
arcade.debugfalsePrints rejected scores and why to the server console.

The server only saves scores that are possible in the time the game took: under maxScore, not faster than minMsPerPoint allows, and passing the game's own check function when it has one. Each character keeps their best score per game in zyke_laptop_arcade_scores, created on start.

Arcade scores come from the player's browser. Don't hang money or items on them.

To add a game, add an entry to arcade.games, an examples/arcade/web/<id>/ folder with its page, and <id>: keys in every examples/arcade/locales/*.lua file, at least <id>:description for the search results.

#How They Are Built

Each folder holds what a resource of its own would:

  • A client script that registers the site with RegisterSite, and RegisterApp for the police app. See Apps & Sites.
  • Server scripts that check every request.
  • Its text in locales/.
  • The pages in web/, as plain HTML, CSS and JavaScript with no build step.

The pages read their text through an NUI callback and post to the client script with Laptop.post from the JavaScript SDK. The client script passes requests on to the server where needed. Every callback name starts with the example's name, such as mechanic:GetSite, so they never clash with the laptop's own.

The server takes nothing from a page on trust. Every server callback checks IsAtLaptop, the police reviewer callbacks check the job and grade again, and the arcade checks every score.

#Making Your Own Site From One

Move the folder into a resource of your own, then:

  1. Load its scripts in your fxmanifest.lua, with locales/*.lua and web/**/* under files.
  2. Copy its block from shared/unlocked/examples.lua into a config of your own.
  3. Read the text with zyke_lib's T and Translations instead of LoadLaptopExampleLocale.
  4. Change the ui paths to the folder's place in your resource, give the site a unique id and url, and rename the callbacks, events and table.
  5. Register the site again on the client event zyke_laptop:Ready, which fires when the laptop restarts, as well as when your resource starts.

Pages are served from the resource, so restart it after editing them. Set the example's enabled to false in the laptop so the original doesn't run next to your copy.

#Working on a Page in a Browser

Pages can be worked on in a normal browser, outside the game.

  1. Serve your resources folder over HTTP, for example by running python3 -m http.server in it.
  2. Open a page, such as zyke_laptop/examples/mechanic/web/index.html.

Outside the game, each page answers its own requests from its mock.js (standalone.js for the arcade), with the English text from its locales/en.lua.

  • The police pages take ?state= to pick what the server would answer: pending, accepted, declined, cooldown, serverErrors or notAtLaptop on the site, and empty, noAccess or conflict in the app. ?lang=de picks another language.
  • The arcade brings its own stand-in for the SDK, and the police pages find the laptop's copy themselves.
  • The mechanic page loads the SDK from https://cfx-nui-zyke_laptop/nui/sdk/laptop.js, which only resolves in game. Point that script tag at ../../../nui/sdk/laptop.js while working in a browser, and put it back before restarting.