diff --git a/README.md b/README.md index 126dd8d..2a41fc2 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ The closed-source Mod remains authoritative for generated client and server API ## Structure -- `guides/` contains scripting concepts and tutorials. Each subdirectory is a sidebar section named after the directory (`guides/Getting started/first-resource.md` is served at `/guides/getting-started/first-resource/`). Sections are ordered by the lowest `sidebar.order` among their pages, so each section owns a range: Getting started 10–19, Core concepts 20–29, Players 30–39, NPCs and creatures 40–49, World 50–59, Hosting a server 90–99. +- `guides/` contains scripting concepts and tutorials. Each subdirectory is a sidebar section named after the directory (`guides/Getting started/first-resource.md` is served at `/guides/getting-started/first-resource/`). Sections are ordered by the lowest `sidebar.order` among their pages, so each section owns a range: Getting started 10–19, Core concepts 20–29, Players 30–39, NPCs and creatures 40–49, World 50–59, User interface 60–69, Hosting a server 90–99. - Image directories live beside the Markdown document that references them. - `docs.config.json` owns the generator pin, branding, links, navigation, and community-content mapping. - `scripts/sync_contract.mjs` downloads and verifies the public scripting contract. diff --git a/guides/User interface/hud-and-nametags.md b/guides/User interface/hud-and-nametags.md new file mode 100644 index 0000000..ab7736d --- /dev/null +++ b/guides/User interface/hud-and-nametags.md @@ -0,0 +1,116 @@ +--- +title: HUD, prompts and nametags +description: Hide parts of the game's HUD, float an interaction prompt over the world, and set the names, colours and lines over players' heads. +sidebar: + order: 61 +--- + +Some of what players see on screen is the game's own HUD, which client scripts can hide in parts and add a prompt to. The names over players' heads are set from both sides: the server decides what everyone sees, and each client can add lines and hide nametags for its own player. + +## Hiding the HUD + +The game sorts its HUD into named groups. `Hud.groups()` lists them, read from the game itself, and `Hud.hideGroup(group)` hides one until `Hud.showGroup(group)` lets it go: + +```js +// client/main.js +function startCutscene() { + for (const group of Hud.groups()) Hud.hideGroup(group); +} + +function endCutscene() { + for (const group of Hud.groups()) Hud.showGroup(group); +} +``` + +The game also hides groups of its own, and a group stays hidden while either side hides it. The player's own HUD settings are left alone, so there is nothing to put back afterwards. + +A hidden group is held by the mod, not by your resource: `showGroup` from any resource releases it, and stopping your resource does not, so show what you hid yourself. Every group comes back when the player disconnects. + +`Hud.setVisible(false)` hides the whole HUD with one switch for the whole client. Any resource can turn it back on, and it is not restored on disconnect, so prefer groups. `Settings.setHud` changes the player's own HUD settings and belongs only behind a settings panel they opened. + +## Interaction prompts + +`Hud.showPrompt(key, label, x, y, z)` floats a `[key] label` prompt at a world position, in centimetres. It follows that point on screen and hides while the point is off screen. There is one prompt per client, so showing another replaces it, and `Hud.hidePrompt()` takes it away. + +The prompt only shows text. Pair it with a [key binding](/guides/user-interface/keys-and-menus/#binding-keys), and use `Key.getLabel` so the key it names matches the player's keyboard layout: + +```js +// client/main.js +const counter = { x: 12000, y: -4500, z: 300 }; +let atCounter = false; + +setInterval(() => { + const pos = LocalPlayer.getPosition(); + const near = !!pos && Math.hypot(pos.x - counter.x, pos.y - counter.y, pos.z - counter.z) <= 250; + if (near && !atCounter) Hud.showPrompt(Key.getLabel("f"), "Browse wands", counter.x, counter.y, counter.z + 100); + if (!near && atCounter) Hud.hidePrompt(); + atCounter = near; +}, 200); + +Key.bind("f", () => { + if (atCounter) Events.emitServer("shop:open", { shop: "wands" }); +}); +``` + +The server receives `shop:open` as a request from the client, so check there that the player really stands at the counter before opening anything. [Authority](/guides/core-concepts/authority/) covers handling what a client sends. + +## Nametags + +Every player has a nametag: their name over their head, with a health bar under it. The server sets what everyone sees on a player's nametag: + +| Call | Does | +| --- | --- | +| `player.setNametagText(text)` | Shows `text` instead of the player's name, cut to 64 bytes. Empty or no text restores the name. | +| `player.setNametagColor(color)` | Tints the text, as a packed `0xAARRGGBB` number. | +| `player.setNametagVisible(visible)` | Shows or hides the player's nametag for every other player. | +| `player.setNametagHealthVisible(visible)` | Shows or hides only the health bar under it. | + +`getNametagText()`, `getNametagColor()`, `isNametagVisible()` and `isNametagHealthVisible()` read them back. + +```js +// server/main.js +const STAFF_GOLD = 0xffd4af37; // alpha first, then red, green, blue + +function markStaff(player) { + player.setNametagText(`[Staff] ${player.nickname}`); + player.setNametagColor(STAFF_GOLD); +} +``` + +The nametag colour puts alpha first, while chat colours put it last as `0xRRGGBBAA`. + +### On the client + +Each client can change what its own player sees, without affecting anyone else: + +- `Nametags.setVisible(false)` hides every nametag. A nametag the server hid stays hidden either way. +- `Nametags.setHealthVisible(false)` hides the health bars and keeps the names. +- `Nametags.setSelfVisible(true)` draws the player's own nametag, as others see it. It is off by default. + +`Nametags.setLabel(entityId, text, durationMs, color)` hangs a passing line above a player's name, for speech, an emote or a status. It follows them and fades and hides with the name. It lasts 6 seconds by default, a duration of 0 or less holds it until cleared, and `\n` starts a new line. `color` is a packed `0xAARRGGBB` number; leave it out for the nametag's own colour. Labels are local: the server sends the event, and each client draws it: + +```js +// server/main.js +Events.on("chatCommand", (player, message, command, args) => { + if (command.toLowerCase() !== "me" || args.length === 0) return; + Events.emitAllClients("emote", { playerId: player.id, text: `*${args.join(" ")}*` }); +}); +``` + +```js +// client/main.js +Events.on("emote", (payload) => { + Nametags.setLabel(payload.playerId, payload.text, 5000); +}); +``` + +`Nametags.clearLabel(entityId)` removes a label early, and `Nametags.clearLabels()` removes all of them. + +## Notices + +`Game.notify(text)` shows a line in the local chat, seen only by this player. From the server, [`player.sendChat`](/guides/players/chat-and-commands/#sending-chat) does the same for one player. + +## Where to go next + +- [Keys and menus](/guides/user-interface/keys-and-menus/) for key bindings and the game's own menus. +- [Web views](/guides/user-interface/web-views/) for drawing interfaces of your own in HTML. diff --git a/guides/User interface/keys-and-menus.md b/guides/User interface/keys-and-menus.md new file mode 100644 index 0000000..60cea48 --- /dev/null +++ b/guides/User interface/keys-and-menus.md @@ -0,0 +1,74 @@ +--- +title: Keys and menus +description: Bind keyboard and mouse keys, lock the game's controls while players use your interface, and keep the game's own menus closed so your resource can replace them. +sidebar: + order: 63 +--- + +Client scripts can listen for keys of their own, take the game's controls away while players use an interface, and keep the game's own menus from opening so the keys behind them can open yours. + +## Binding keys + +`Key.bind(key, handler)` runs `handler` when a key is pressed. Pass `"up"` or `"both"` before the handler to run it when the key is released, or on both: + +```js +// client/main.js +Key.bind("f2", () => Events.emitServer("menu:open")); + +Key.bind("lshift", "both", (key, state) => { + Events.emitServer("sprint", { held: state === "down" }); +}); +``` + +The handler receives the key's name and `"down"` or `"up"`. A key name stands for a key's position, named as on a US keyboard, so `y` is the same physical key on every layout. Names ignore case: + +| Keys | Names | +| --- | --- | +| Letters and digits | `a` to `z`, `0` to `9` | +| Function keys | `f1` to `f12` | +| Number pad | `numpad0` to `numpad9`, or `num0` to `num9` | +| Modifiers | `shift`, `lshift`, `rshift`, `ctrl` or `control`, `lctrl`, `rctrl`, `alt`, `lalt`, `ralt` | +| Arrows | `up`, `down`, `left`, `right` | +| Others | `space`, `enter` or `return`, `escape` or `esc`, `tab`, `backspace`, `capslock`, `insert`, `delete`, `home`, `end`, `pageup`, `pagedown` | +| Mouse buttons | `mouse1` to `mouse5` | + +An unknown name throws. A key pressed while Alt is held does not fire, so Alt+Tab never reaches your handlers. Bindings fire only while the player is in the game and nothing else has their input: not while the game is in the background or loading, while the chat is open, while a [web view](/guides/user-interface/web-views/) has focus, or while the controls are [locked](#locking-the-controls). A binding does not take the key from the game, which still acts on it, so pick keys the game leaves free. + +A binding belongs to the resource that made it and goes when that resource stops. `Key.unbind(key, state, handler)` removes your bindings for a key, all of them when you leave out the state and handler. + +`Key.isDown(key)` reads whether a key is held, behind the same checks. `Key.getLabel(key)` returns what the player's own layout prints on that key, such as `Z` for `y` on a German keyboard, so a [prompt](/guides/user-interface/hud-and-nametags/#interaction-prompts) names the key the player sees. + +## Locking the controls + +`Game.lockControls(true)` shows the cursor and stops the player's character from responding to input, and `Game.lockControls(false)` gives control back. Each resource keeps a count: pair every `true` with one `false`, and the controls stay locked while any resource holds one. Stopping a resource releases what it held. `Game.areControlsLocked()` tells you whether anything holds a lock. + +A focused web view locks the controls and shows the cursor by itself while it has focus, so there is no need to lock them around `Web.focusView`. + +## The game's own menus + +Each of these switches keeps one of the game's menus from opening, so a resource can put its own interface on the keys behind it: + +| Call | Keeps shut | +| --- | --- | +| `Game.setPauseMenuEnabled(false)` | The Field Guide, the game's pause menu, with every page hotkey and the escape key | +| `Game.setMenuPageEnabled(page, false)` | One page of the Field Guide; the rest keeps working | +| `Game.setActionScreenEnabled(false)` | The action screen, where players edit their spell sets | +| `Game.setToolWheelEnabled(false)` | The tool wheel, along with its quick-item slot on the HUD | + +The pages are `"inventory"`, `"gear"`, `"talents"`, `"quests"`, `"map"`, `"mail"`, `"collections"`, `"challenges"` and `"settings"`. A page kept shut is greyed out, skipped when paging and ignored by its hotkey. + +```js +// client/main.js +// This resource keeps inventories itself, so the game's inventory page stays out of reach. +Game.setMenuPageEnabled("inventory", false); +Game.setToolWheelEnabled(false); +``` + +Each resource holds a switch once, however often it calls it, and a menu stays shut while any resource holds it. Passing `true` gives back only your own hold. Holds go when the resource stops or reloads and when the player disconnects, so there is nothing to undo. A menu that is already open when you shut it stays open until the player closes it. + +`Game.isPauseMenuEnabled()` and its siblings return `false` while any resource keeps that menu shut. `Game.pauseMenuState()` and its siblings also name the resources holding it, for finding out which one does. + +## Where to go next + +- [Web views](/guides/user-interface/web-views/) for drawing the menus that replace the game's. +- [HUD, prompts and nametags](/guides/user-interface/hud-and-nametags/) for prompts that tell players which key to press. diff --git a/guides/User interface/map-markers.md b/guides/User interface/map-markers.md new file mode 100644 index 0000000..5f18ba8 --- /dev/null +++ b/guides/User interface/map-markers.md @@ -0,0 +1,119 @@ +--- +title: Map markers +description: Put markers, search circles and gold routes on the map and minimap, choose which players get a blip and in what colour, and read what the minimap shows. +sidebar: + order: 62 +--- + +The game's map and minimap draw a blip for every other player, and client scripts can add markers and circles of their own and draw the game's gold route to one. All of this lives on each client, in the `Blips` global, so the server decides what each player should see and tells their client. + +## Markers and circles + +`Blips.setMarker(key, x, y, z, icon, label)` puts a marker at a world position, in centimetres. `Blips.setCircle(key, x, y, z, radius)` draws a search circle instead, with its radius in **metres**, 50 by default. + +The key is a name of your choosing. Setting the same key again moves or restyles that blip rather than adding another, so a moving target is a repeated `setMarker`. `Blips.remove(key)` takes one away. Leave `icon` out for the game's default marker icon. + +```js +// client/main.js +Blips.setMarker("hunt:clue", 15200, -3400, 250, undefined, "Next clue"); +Blips.setCircle("hunt:area", 15200, -3400, 250, 40); +Blips.pulseCircle("hunt:area", true); +``` + +`Blips.pulseCircle(key, true)` makes a circle pulse, and `false` fades it in instead. `Blips.inArea(key, x, y, z)` tells you whether a point is inside a circle. + +### Who owns a blip + +Blips belong to the client, not to the resource that set them. Every resource shares one set of keys, so prefix yours with something your own, and `Blips.clear()` removes every script blip, whichever resource set it. Stopping your resource leaves its blips on the map; only disconnecting clears them. + +Keep the list on the server and send it to clients, including players who join after a blip went up: + +```js +// server/main.js +const markers = new Map(); // key -> { key, x, y, z, label } + +function showMarker(marker) { + markers.set(marker.key, marker); + Events.emitAllClients("marker:set", marker); +} + +function hideMarker(key) { + markers.delete(key); + Events.emitAllClients("marker:remove", { key }); +} + +// A client asks for the whole set once its script is running. +Events.onClient("marker:ready", (player) => { + player.emit("marker:all", JSON.stringify([...markers.values()])); +}); +``` + +```js +// client/main.js +Events.on("marker:set", (m) => Blips.setMarker(m.key, m.x, m.y, m.z, undefined, m.label)); +Events.on("marker:remove", (m) => Blips.remove(m.key)); +Events.on("marker:all", (list) => { + for (const m of list) Blips.setMarker(m.key, m.x, m.y, m.z, undefined, m.label); +}); + +Events.emitServer("marker:ready"); +``` + +A marker the server sends to everyone is on everyone's map. When a position should stay secret, such as the circle hunters are searching, send it only to the players meant to see it. + +## Routes + +`Blips.setRoute(target, label)` draws the game's gold mission route on the map and minimap, to a marker or circle by its key, or to another player's blip by their network id while they have one. It returns `false` when there is no such blip or the game did not take the route. Each client follows one route, so setting another replaces it: + +```js +// client/main.js +Blips.setRoute("hunt:area", "Search for the clue"); +``` + +- A route to a circle stops at its ring, so the player is led there and then searches inside it. +- It survives its blip being set again, so moving the circle to the next clue keeps the route on it. +- While the player has placed a waypoint of their own, their purple line takes over the minimap. The big map keeps drawing your route beside it. + +`Blips.clearRoute()` ends the route, and removing its blip ends it too. `Blips.route()` returns the key or network id the route leads to, or `null`. + +## Player blips + +Every other player has a blip by default. To show only some of them, turn that off and name the players who keep theirs: + +```js +// client/main.js +Events.on("party:members", (payload) => { + Blips.trackAllPlayers(false); + Blips.setTrackedPlayers(payload.memberIds); +}); +``` + +`Blips.trackPlayer(networkId, enabled)` adds or removes one player, and works before that player has spawned. `Blips.trackedPlayers()` lists the network ids that have a blip. + +`Blips.setPlayerColor(networkId, r, g, b, a)` colours one player's blip, with components from 0 to 1. Leave out the colour to go back to their house colour. `Blips.setAllPlayerColors(r, g, b, a)` colours every blip, and `Blips.setBlipScale(scale)` sizes them. + +Hiding a blip hides an icon, not a player: their position still reaches the client. To keep players apart, use [virtual worlds](/guides/core-concepts/virtual-worlds/), and to hide one from everyone, `player.setVisible(false)` on the server, which takes their blip with them. + +## The minimap + +`Minimap.state()` reports what the game's own minimap is drawing: + +| Field | Meaning | +| --- | --- | +| `map` | The region on screen: `"overland"`, `"hogwarts"`, `"hogsmeade"`, `"dungeon"`, `"tutorial"`, `"sanctuary"`, `"tents"`, or `"none"` behind a load screen | +| `floor` | In Hogwarts, the floor the game picked for the player, counting from 0; `null` elsewhere | +| `texture` | In Hogwarts, the floor's map tile; `null` elsewhere | + +The `minimapChanged` event fires with the new state and the previous one whenever any of them changes. `Minimap.floorAt(z)` returns the Hogwarts floor the game would pick at a height, for placing other things on floors the way the game places the player. + +```js +// client/main.js +Events.on("minimapChanged", (state, previous) => { + if (state.map === "hogwarts" && previous.map !== "hogwarts") Game.notify("Welcome back to the castle."); +}); +``` + +## Where to go next + +- [HUD, prompts and nametags](/guides/user-interface/hud-and-nametags/) for the rest of the game's HUD. +- [Virtual worlds](/guides/core-concepts/virtual-worlds/) for keeping groups of players apart. diff --git a/guides/User interface/web-views.md b/guides/User interface/web-views.md new file mode 100644 index 0000000..131e603 --- /dev/null +++ b/guides/User interface/web-views.md @@ -0,0 +1,139 @@ +--- +title: Web views +description: Draw your own interfaces in HTML over the game, ship their pages with your resource, and pass messages between a page and your client script. +sidebar: + order: 60 +--- + +A web view is a browser page drawn over the game. Client scripts create and control views with the `Web` global, and a page talks back to its script through a `callEvent` function the view gives it. Menus, shops and scoreboards are usually web views. + +## Shipping a page + +A page is a set of files in your resource. List them under `files` in the manifest so they reach players along with your client scripts: + +```json +{ + "name": "wand-shop", + "mafiahub": { + "clientScripts": ["client/main.js"], + "serverScripts": ["server/main.js"], + "files": ["ui/**"] + } +} +``` + +A shipped file's address is `fw://resources//`, so this resource's `ui/index.html` is `fw://resources/wand-shop/ui/index.html`. Pass that full address: `Web.createView` does not resolve a path such as `ui/index.html` against your resource. Inside the page, load your styles, scripts and images by relative paths such as `style.css`. A path starting with `/` points at another resource. + +A view can also load a site over `https://`, or a development server such as `http://localhost:5173` while you work on a page. + +## Creating a view + +`Web.createView(url, options)` creates a view and returns its id, which the other `Web` calls take: + +```js +// client/main.js +const scoreboard = Web.createView("fw://resources/wand-shop/ui/scoreboard.html", { width: 400, height: 300, x: 40, y: 40 }); +``` + +| Option | Default | Meaning | +| --- | --- | --- | +| `width`, `height` | 0 | Size in pixels. Leaving both at 0 fills the screen and follows the window's size. | +| `x`, `y` | 0 | Position of the top-left corner in pixels. | +| `zIndex` | 0 | Stacking order: higher views are drawn on top. | +| `visible` | `true` | Whether the view starts on screen. | +| `focus` | `false` | Whether the view starts with the player's input. | + +`createView` throws if the view cannot be created. A page's background is transparent unless the page paints one, so only what it draws covers the game. Pages render without the GPU, so leave heavy animation and WebGL out. + +`Web.showView` and `Web.hideView` put a view on and off screen, `Web.resizeView` and `Web.setViewPosition` move it, and `Web.getScreenSize()` returns the screen's size in pixels. Resizing a full-screen view yourself stops it following the window. + +A view belongs to the resource that created it. It is destroyed when that resource stops and when the player disconnects, and `Web.destroyView(id)` destroys it earlier along with its handlers. During a loading screen, views stay off screen and come back once it is gone. + +## Messages between page and script + +The page calls `callEvent(name, payload)` to send your script an event. It takes exactly two arguments: the event name, and JSON text or `null`. Your script listens with `Web.on(view, name, handler)`, and the handler receives the payload already parsed. + +Your script calls `Web.emit(view, name, payload)` to send the page an event. The page receives it as a `CustomEvent` on `window`, with the payload in `detail`. The payload is serialized for you, so pass objects as they are. + +An event sent before the page listens for it is lost, so have the page say when it is ready. In this shop, `ui/index.html` holds an empty list with the id `wands` and a Close button with the id `close`, and loads `ui/shop.js`, which shows the wands it is sent and reports which one the player picks: + +```js +// ui/shop.js, running in the page +window.addEventListener("shop:stock", (event) => { + const items = event.detail.map((wand) => { + const item = document.createElement("li"); + item.textContent = `${wand.name}: ${wand.price} Galleons`; + item.addEventListener("click", () => callEvent("shop:buy", JSON.stringify({ wandId: wand.id }))); + return item; + }); + document.getElementById("wands").replaceChildren(...items); +}); + +document.getElementById("close").addEventListener("click", () => callEvent("shop:close", null)); +window.addEventListener("keydown", (event) => { + if (event.key === "Escape") callEvent("shop:close", null); +}); + +callEvent("shop:ready", null); +``` + +The client script opens the page when the server says so, and passes the player's choice on: + +```js +// client/main.js +const SHOP_URL = "fw://resources/wand-shop/ui/index.html"; +let shopView = null; + +Events.on("shop:open", (payload) => { + if (shopView !== null) return; + const view = Web.createView(SHOP_URL, { focus: true }); + shopView = view; + + Web.on(view, "shop:ready", () => Web.emit(view, "shop:stock", payload.wands)); + Web.on(view, "shop:buy", (choice) => Events.emitServer("shop:buy", { wandId: choice.wandId })); + Web.on(view, "shop:close", () => { + Web.destroyView(view); + shopView = null; + }); +}); +``` + +Any text that parses as JSON arrives parsed, so `"42"` arrives as the number 42. Send plain text through `JSON.stringify` so it arrives as a string. `Web.off(view, name, handler)` removes a handler. + +What a page sends is a request from the player's machine, like any client event. The server checks that the player can afford the wand and is at the shop before handing it over; [Authority](/guides/core-concepts/authority/) covers handling what a client sends. + +## Focus and input + +A focused view receives all of the player's keyboard and mouse input, Escape included, and the game receives none. While a view has focus, the controls stay locked and the cursor shows, so there is no need to call [`Game.lockControls`](/guides/user-interface/keys-and-menus/#locking-the-controls) around it, and [key bindings](/guides/user-interface/keys-and-menus/#binding-keys) do not fire. + +`Web.focusView(view)` gives a view focus, and `Web.focusView(view, false)` takes it away. Hiding or destroying a view also takes its focus away, and so does stopping its resource. + +- A page cannot give up focus by itself. Always give it a way to ask your script, as the Close button and Escape key do above. +- A focused view takes every click anywhere on screen, transparent areas included, so focus a view only while the player is using it. +- Do not focus a hidden view: it blocks the game's input with nothing on screen to click. +- The Enter key reaches a page as a key press but types no character. Handle it in a `keydown` listener rather than relying on a form to submit itself. + +## Where a view may go + +Each view is locked to the origin of the address it was created with: its scheme, host and port. The view refuses to navigate to another origin, including through a redirect, and raises `browserResourceBlocked` with the reason `"cross-origin"`. It ignores `callEvent` from a page of another origin. To move a view to another origin on purpose, `Web.loadURL(view, url)` navigates it and locks it to the new one. Your `Web.on` handlers stay, but the page starts again, so wait for it to be ready again. + +Pages cannot open popups or new windows; `browserPopup` reports the attempt with the address. + +Every resource's pages share the one origin `fw://resources`, so they can read each other's files and browser storage. Keep nothing in a page that another resource must not see. + +## Lifecycle events + +The resource that owns a view receives events about it, each with the view's id as `viewId`. Among them are `browserDocumentReady` when its page has loaded, `browserLoadingFailed` when a load fails, and `browserConsoleMessage` for what the page logs. [Events](/guides/core-concepts/events/#web-view-events) lists all of them, and [Debugging](/guides/getting-started/debugging/#web-views) covers reading a page's console. + +## Drawing on objects + +`Web.setViewOffscreen(view, true)` keeps a hidden view painting, so something else can sample it, such as a render target that draws the page onto an object in the world. A view that is merely hidden stops painting, and anything sampling it freezes. + +## From the server + +`player.showUI`, `player.hideUI` and `player.sendUI` ask a client resource named `ui` to open, close and message a view, and do nothing without one. Neither the release nor HMP Foundations includes that resource, so send your own events with `player.emit`, as above, and keep views in your client scripts. + +## Where to go next + +- [Keys and menus](/guides/user-interface/keys-and-menus/) for opening your interface from a key and keeping the game's own menus shut. +- [HUD, prompts and nametags](/guides/user-interface/hud-and-nametags/) for what the game draws itself. diff --git a/guides/overview.md b/guides/overview.md index 489ae6d..e7cbf93 100644 --- a/guides/overview.md +++ b/guides/overview.md @@ -45,6 +45,13 @@ Use the navigation to browse the globals available to the selected environment. - [Doors](/guides/world/doors/) — holding the game's doors locked or open for everyone, and deciding who gets in. - [Loot](/guides/world/loot/) — server-owned chests, moonstones and foragables, per-player contents, and switching off the game's own loot. +**User interface:** + +- [Web views](/guides/user-interface/web-views/) — HTML interfaces in the game, and messages between the page and your scripts. +- [HUD, prompts and nametags](/guides/user-interface/hud-and-nametags/) — hiding the game's HUD, interaction prompts, and what players' nametags show. +- [Map markers](/guides/user-interface/map-markers/) — markers, search circles and routes on the map, player blips, and the minimap. +- [Keys and menus](/guides/user-interface/keys-and-menus/) — key bindings, locking the controls, and keeping the game's own menus shut. + **Running a server:** - [Hosting a server](/guides/hosting-a-server/run-a-server/) — installing on Windows or Linux, Foundations, ports, the masterlist, and updates. diff --git a/scripts/check_samples.mjs b/scripts/check_samples.mjs index 91f18ee..12ed0aa 100644 --- a/scripts/check_samples.mjs +++ b/scripts/check_samples.mjs @@ -32,7 +32,7 @@ const ignored = (diagnostic, extension) => { if (diagnostic.code === 2307) return /^\.\.?\//.test(name); if (extension === ".js" && (diagnostic.code === 18046 || diagnostic.code === 2571)) return true; if (extension === ".js" && diagnostic.code === 2339) return message.endsWith("on type '{}'."); - if (![2304, 2552, 18004].includes(diagnostic.code)) return false; + if (![2304, 2552, 2584, 18004].includes(diagnostic.code)) return false; return /^[a-z_$]/.test(name); };