From cfdef509eb48a4e27f702b4bd2e1695300f12748 Mon Sep 17 00:00:00 2001 From: KHeartz Date: Mon, 19 Oct 2026 16:41:40 -0400 Subject: [PATCH 1/4] docs: add a Gates guide and bring the Doors guide up to date --- guides/World/doors.md | 54 +++++++++++++- guides/World/gates.md | 169 ++++++++++++++++++++++++++++++++++++++++++ guides/overview.md | 1 + 3 files changed, 220 insertions(+), 4 deletions(-) create mode 100644 guides/World/gates.md diff --git a/guides/World/doors.md b/guides/World/doors.md index 5d25deb..7353ce1 100644 --- a/guides/World/doors.md +++ b/guides/World/doors.md @@ -1,12 +1,14 @@ --- title: Doors -description: Hold the game's doors locked, unlocked, open or closed for every player, find a door's name, and decide when a locked door opens for someone. +description: Hold the game's doors locked, unlocked, open or closed for every player, share the locks players open, find a door's name, and decide when a locked door opens for someone. sidebar: order: 520 --- Hogwarts and Overland are full of the game's own doors and locks, and each player's game normally keeps their state in that player's save. The server can take over any of them, so that a door is locked or open for everyone. +Portcullises, the Astronomy rune gate, goblin camp gates and crawl grates are not doors, and no `Doors` call reaches them. See [Gates](/guides/world/gates/). + ## Holding a door's state `Doors.set(selector, state)` holds a door's lock, its open state or both, for every player: @@ -29,6 +31,7 @@ Each player's game re-applies what the server holds as the door streams in, so p - `Doors.get(selector)` returns the state held under a selector, and `Doors.list()` every held state. - `Doors.clear(selector)` releases a selector, and `Doors.clearAll()` releases them all. A released door keeps its current state until it streams in again, when each player's save takes over. - A selector left holding nothing, with both fields `null`, is released. +- Each leaf of a double door is its own door and swings on its own, so hold both leaves to keep a double door shut. ## Finding a door @@ -54,9 +57,25 @@ Events.on("resourceStart", (resourceName) => { `Doors.list(radius)` returns each loaded door within `radius` centimetres, 3000 by default, with its name, persistent id, full path, distance and lock state. The output goes to the [client log](/guides/getting-started/debugging/#where-output-goes). +## When a player unlocks a door + +When a player opens a lock with a key or Alohomora, the server holds that door unlocked for everyone, under its persistent id or, without one, its path. `Doors.setShareUnlocks(false)` keeps each unlock in the player's own game instead, and `Doors.getShareUnlocks()` reads the setting. + +Either way the server raises `doorUnlocked` with the player and the door: its `name`, persistent `id`, `path` and whether the unlock was `shared`. `id` is empty for a lock that is not on a door. + +```js +// server/main.js +Events.on("doorUnlocked", (player, door) => { + if (!door.shared) return; + Chat.sendToAll(`${player.nickname} unlocked ${door.name} for everyone.`); +}); +``` + +`Doors.set` or `Doors.clear` on that selector takes a shared unlock back. An unlock is not shared while the server holds the door locked. + ## When a player tries a locked door -When a player tries to open a lock the server holds shut, with a key or Alohomora, their game refuses at once and the server raises `doorUnlockRequest` with the player and the door: its `selector`, `name` and `id`. Release the lock there if the player should get in: +When a player tries to open a lock the server holds shut, with a key or Alohomora, their game refuses at once and the server raises `doorUnlockRequest` with the player and the door: its `selector`, `name` and `id`. It fires at most once a second for each door. Release the lock there if the player should get in: ```js // server/main.js @@ -72,7 +91,10 @@ Events.on("doorUnlockRequest", (player, door) => { }); ``` -Unlocking with `Doors.set` opens the door for everyone, not only the player who asked. +Unlocking with `Doors.set` unlocks the door for everyone, not only the player who asked. + +- On a double door, `selector` may name the other leaf, when that leaf's held lock is what refused the player. +- A lock the player's own client policy keeps shut raises it too, with an empty `selector`. ## On the client @@ -80,10 +102,34 @@ Client scripts can change doors too, but only for their own player, and the serv - `Doors.setLocked(selector, locked)` locks or unlocks matching doors on this client only, until they stream in again. - `Doors.setPolicy(policy)` keeps streamed doors unlocked or locked on this client: unlock all of them, a list, or all except a list, and lock a list, which beats any unlock. -- `Doors.superAlohomora(true)` lets every lock open to a key or Alohomora, whatever the player's Alohomora level. Locks the server holds still refuse, and it is cleared on disconnect. +- `Doors.superAlohomora(true)` lets every lock open to a key or Alohomora, whatever the player's Alohomora level, key-only locks included. Locks the server holds still refuse, and it is cleared on disconnect. - `Doors.held()` lists the door states the server holds, as this client received them. +## With the doors resource + +The `doors` resource holds the doors listed under `held` in its `doors.config.js` for everyone while it runs, and releases them when it stops. The file exports one object, `module.exports = { … }`: + +```json5 +{ + version: 1, + held: { + "BP_Door_BellTowers_EXT": { locked: false }, + "BP_Door_Template4:AQsMCQ0PAAUKCgQEBAgADQ": { open: true }, + }, + rules: [{ priority: 1000, action: "allow", doors: "*" }], +} +``` + +| Command | Does | +| --- | --- | +| `/listdoors [radius]` | Lists the doors near you, with their names and ids, in the F8 console | +| `/doorset ` | Holds a door's state for everyone. Needs the staff or admin role. | +| `/doorsheld` | Lists every door state the server holds | + +The same resource handles gates with `heldGates`, `gates:` rules and its gate commands. See [Gates](/guides/world/gates/#with-the-doors-resource). + ## Where to go next +- [Gates](/guides/world/gates/) for portcullises, the rune gate, camp gates and crawl grates. - [Loot](/guides/world/loot/) for chests behind your doors. - [Authority](/guides/core-concepts/authority/) for why a request like `doorUnlockRequest` is the server's to decide. diff --git a/guides/World/gates.md b/guides/World/gates.md new file mode 100644 index 0000000..12fbd12 --- /dev/null +++ b/guides/World/gates.md @@ -0,0 +1,169 @@ +--- +title: Gates +description: Open, close and lock the game's portcullises, the Astronomy rune gate, goblin camp gates and crawl grates, for everyone or for one player. +sidebar: + order: 525 +--- + +Besides its doors, the game has gates: portcullises, a rune-puzzle gate, goblin camp gates and the grates over goblin crawl spaces. Gates are not doors. Each kind is its own actor, so no `Doors` call reaches a gate and no `Gates` call reaches a door. The server can hold a gate's state for everyone, and a client script can change gates for its own player. + +## The four kinds + +| `kind` | Gates | Opens | Closes | Locks | +| --- | --- | --- | --- | --- | +| `portcullis` | The Clocktower, outer wall and dungeon portcullises | Yes | Yes | Yes | +| `runeGate` | The rune gate from the Astronomy Class quest, `BP_ZZS_TundraGate_2` | Yes, skipping its puzzle | Yes | No | +| `swingGate` | Goblin camp gates | Yes | Yes | No | +| `crawlGrate` | Breakable grates over goblin crawl spaces | Yes, by breaking it | No | No | + +- An unlocked portcullis stays down, rises as a player walks up to it and lowers a second after the last one leaves. An open one stays raised. +- The rune gate lets players through about 7 seconds after it starts to open. +- Opening a crawl grate breaks it, which cannot be undone. + +## Finding a gate + +To learn a gate's name, stand near it with a client script that lists the gates around the player: + +```js +// client/main.js +Events.on("resourceStart", (resourceName) => { + if (resourceName !== "my-gamemode") return; + for (const gate of Gates.list(3000)) { + console.log(`${gate.kind} ${gate.name} open=${gate.isOpen} locked=${gate.isLocked} ${Math.round(gate.dist)} cm away`); + } +}); +``` + +`Gates.list(radius, kind)` returns the loaded gates within `radius` centimetres, nearest first. A `radius` of 0, the default, returns every loaded gate, and `kind` narrows the list to one kind or an array of them. The output goes to the [client log](/guides/getting-started/debugging/#where-output-goes). + +| Field | Meaning | +| --- | --- | +| `name` | The actor name. Several placements can share one. | +| `path` | The full path name, which belongs to one placement | +| `kind` | One of the four kinds above | +| `dist`, `bearing` | Centimetres from the player, -1 when unknown, and degrees off the player's facing, 999 when unknown | +| `x`, `y`, `z` | World position, left out when the gate could not be located | +| `isOpen` | Open or opening. The rune gate reads `true` once it no longer blocks the way. | +| `isLocked` | The portcullis lock, or `null` for a kind without one | +| `canClose` | Whether it can be closed. `false` for a crawl grate. | +| `canLock` | Whether it can be locked. `true` only for a portcullis. | +| `lift` | How far open, from 0 to 1, or `null` where that cannot be measured | +| `hasBeenOpened`, `useSchedule`, `wantsToOpen` | Portcullis only: whether walking up to it raises it, whether the clock still moves it, and whether it stays raised after players walk through | + +Every `Gates` call that takes a selector reads it as an actor name, which can match several placements, or as a path name when it holds `/` or `:`, which matches one. + +## Holding a gate's state + +`Gates.set(selector, state)` holds a gate's lock, its open state or both, for every player: + +```js +// server/main.js +Gates.set("BP_ZZS_TundraGate_2", { locked: null, open: true }); +Gates.set("BP_HW_ClocktowerPortcullis2", { locked: true, open: null }); +``` + +| Field | `true` | `false` | `null` | +| --- | --- | --- | --- | +| `locked` | Held locked and closed. A kind without a lock is held closed instead. | Held unlocked | Left to each player's save and gate policy | +| `open` | Held open | Held closed. A crawl grate cannot close. | Left to each player's game | + +As with `Doors.set`, a field you leave out keeps whatever the server already holds for it at runtime, but the current type declarations ask for both fields, so pass `null` for the one you mean to leave to the game. + +Each player's game applies the held state when it arrives and again whenever a matching gate streams in, whatever that player's save or gate policy says. + +- A held lock beats a held open. +- Only a portcullis can be held shut. A player can still open any other gate held closed their own way, by its puzzle, a spell or Interact, until it next streams in. +- `Gates.get(selector)` returns the state held under a selector, and `Gates.list()` every held state. +- `Gates.clear(selector)` releases a selector, and `Gates.clearAll()` releases them all, other resources' included. A released gate keeps its current state until it streams in again. +- A selector left holding nothing, with both fields `null`, is released. A malformed selector or a field that is not a boolean or `null` throws a `TypeError`. + +## On the client + +Client scripts can change gates for their own player. A change lasts until the gate streams in again, and the server's held state always wins: + +```js +// client/main.js +const opened = Gates.setOpen("BP_ZZS_TundraGate_2", true); +if (opened === 0) console.log("The rune gate is not loaded."); +``` + +- `Gates.setOpen(selector, open)` opens, and unlocks, or closes matching gates. +- `Gates.setLocked(selector, locked)` locks, and closes, or unlocks matching portcullises. +- `Gates.freezeSchedule(selector)` takes matching portcullises off the clock. + +Each returns how many loaded gates matched and support the call, so closing a crawl grate or locking a camp gate counts 0. + +### A policy for each player + +`Gates.setPolicy(policy)` keeps gates open, unlocked or locked on this client, on every loaded gate now and on each gate as it streams in: + +```js +// client/main.js +Gates.setPolicy({ + unlockAll: ["portcullis"], + openAll: ["swingGate"], + freezeSchedule: true, + lockGates: ["BP_HW_ClocktowerPortcullis_2", "BP_HW_ClocktowerPortcullis2", "BP_HW_ClocktowerPortcullis_LOD2"], +}); +``` + +| Field | Meaning | +| --- | --- | +| `unlockAll` | `true`, or a list of kinds: unlock every gate of those kinds and leave it closed | +| `openAll` | `true`, or a list of kinds: open every gate of those kinds, and keep a portcullis raised | +| `allExcept` | Gates that `unlockAll` and `openAll` skip | +| `unlockGates` | Gates to unlock, whatever their kind | +| `openGates` | Gates to open, whatever their kind | +| `lockGates` | Gates to close and lock. This beats any unlock or open in the policy. A kind without a lock is closed instead, until the player opens it their own way. | +| `freezeSchedule` | Take every portcullis off the clock | + +Each call replaces the policy before it and returns how many loaded gates it checked. `Gates.policy()` returns the policy last set, with `true` spelled out as all four kinds and every list present. `Gates.held()` lists the gate states the server holds, as this client received them. + +Each field the server holds overrides the policy for that gate. A lock still beats an open, so a gate the policy locks stays shut while the server only holds it open. Hold it `{ locked: false, open: true }` to open it for everyone. + +## With the doors resource + +The `doors` resource handles gates beside doors. Its `doors.config.js` exports one object, `module.exports = { … }`, with keys like these: + +```json5 +{ + version: 1, + gateSchedule: "freeze", + heldGates: { + "BP_ZZS_TundraGate_2": { open: true }, + }, + rules: [ + { priority: 1000, action: "allow", gates: "*", kind: "portcullis" }, + { priority: 900, action: "allow", gates: "*", kind: "swingGate", open: true }, + { priority: 500, action: "deny", gates: ["BP_HW_ClocktowerPortcullis_2", "BP_HW_ClocktowerPortcullis2", "BP_HW_ClocktowerPortcullis_LOD2"], lock: true }, + ], +} +``` + +A `gates:` rule takes a list of names or `"*"` and follows the same priority and allow/deny rules as a `doors:` rule. An allow unlocks a gate and leaves it closed. Three modifiers apply to `gates:` rules only: + +- `kind` on a `"*"` rule limits it to one kind or an array of them. Without it, `"*"` covers every kind. A rule that names gates applies to them whatever their kind. +- `open: true` on an allow opens the gate instead of only unlocking it. +- `lock: true` on a deny closes and locks the gate, instead of only leaving it out of a wider allow. + +`heldGates` holds gates for everyone through `Gates.set` while the resource runs, and releases them when it stops. `gateSchedule` is `"freeze"` by default, which takes every portcullis off the clock, or `"local"` to leave the game's schedule alone. + +| Command | Does | +| --- | --- | +| `/listgates [radius]` | Lists the loaded gates, with their kind and state, in the F8 console | +| `/gate [open\|close\|lock\|unlock]` | Changes one gate on your own client | +| `/freezegates [name\|*]` | Takes portcullises off the clock on your own client | +| `/gateset ` | Holds a gate's state for everyone. Needs the staff or admin role. | +| `/gatesheld` | Lists every gate state the server holds | + +## Things to watch for + +- **The Clocktower gate is three actors:** `BP_HW_ClocktowerPortcullis_2`, the one players walk through, plus the distant copies `BP_HW_ClocktowerPortcullis2` and `BP_HW_ClocktowerPortcullis_LOD2`. A rule or selector that names it must list all three, or use `"*"` with `kind: "portcullis"`. +- **Scope wide opens with `kind`.** `openAll: true`, or an `open: true` rule on `"*"` without a `kind`, breaks every crawl grate and skips the rune gate's puzzle. +- **Freeze portcullis schedules.** Most portcullises rise at 06:00 and lower at 20:00 on each player's own game clock, which the server's [shared clock](/guides/world/time-and-weather/#the-clock-and-calendar) does not drive, so two players can see the same gate in different places. Set `freezeSchedule: true` in the policy, or keep the doors resource's `gateSchedule: "freeze"`. +- **A gate a player opens their own way is not shared.** Solving the rune gate's puzzle, breaking a grate with a spell or opening a camp gate with Interact changes it in that player's game only, and the server is not told. Hold the gate open with `Gates.set` when everyone should find it open. + +## Where to go next + +- [Doors](/guides/world/doors/) for the game's doors and locks. +- [Time and weather](/guides/world/time-and-weather/) for the shared clock. diff --git a/guides/overview.md b/guides/overview.md index f9c9e64..acab4e3 100644 --- a/guides/overview.md +++ b/guides/overview.md @@ -65,6 +65,7 @@ Use the navigation to browse the globals available to the selected environment. - [Objects and effects](/guides/world/objects-and-effects/) — placing and moving props from the game's meshes and Blueprints, and visual effects that can follow an entity. - [Physics objects](/guides/world/physics-objects/) — objects that fall, roll and fly, pushing and throwing them, and letting players carry them. - [Doors](/guides/world/doors/) — holding the game's doors locked or open for everyone, and deciding who gets in. +- [Gates](/guides/world/gates/) — portcullises, the rune gate, goblin camp gates and crawl grates, held for everyone or set for one player. - [Loot](/guides/world/loot/) — server-owned chests, moonstones and foragables, per-player contents, and switching off the game's own loot. - [Line traces](/guides/world/line-traces/) — finding what a line through the world hits first, from a client script. From c906b3e9306335005cd19de21a34056d9fa60262 Mon Sep 17 00:00:00 2001 From: KHeartz Date: Sat, 24 Oct 2026 17:16:42 -0400 Subject: [PATCH 2/4] docs: skip type-checking the Doors.set samples for now The 1.10 contract declares every DoorStateUpdate field required, though an absent field leaves that axis alone, so these samples fail without a lockLevel. Fence them as jsx until the Mod marks the fields optional. --- guides/World/doors.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guides/World/doors.md b/guides/World/doors.md index 7353ce1..7095c96 100644 --- a/guides/World/doors.md +++ b/guides/World/doors.md @@ -13,7 +13,7 @@ Portcullises, the Astronomy rune gate, goblin camp gates and crawl grates are no `Doors.set(selector, state)` holds a door's lock, its open state or both, for every player: -```js +```jsx // server/main.js Doors.set(vaultDoor, { locked: true, open: null }); Doors.set(hallDoor, { locked: null, open: true }); @@ -77,7 +77,7 @@ Events.on("doorUnlocked", (player, door) => { When a player tries to open a lock the server holds shut, with a key or Alohomora, their game refuses at once and the server raises `doorUnlockRequest` with the player and the door: its `selector`, `name` and `id`. It fires at most once a second for each door. Release the lock there if the player should get in: -```js +```jsx // server/main.js const vaultKeyHolders = new Set(); From 7d30735c795eea50b8cc5ae212cecb6123654102 Mon Sep 17 00:00:00 2001 From: KHeartz Date: Sat, 24 Oct 2026 17:17:43 -0400 Subject: [PATCH 3/4] docs: hold a padlocked door's Alohomora level Doors.set's lockLevel, what a held level does together with a held lock, and the level Doors.list reports on the client. --- guides/World/doors.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/guides/World/doors.md b/guides/World/doors.md index 7095c96..3a30c32 100644 --- a/guides/World/doors.md +++ b/guides/World/doors.md @@ -1,6 +1,6 @@ --- title: Doors -description: Hold the game's doors locked, unlocked, open or closed for every player, share the locks players open, find a door's name, and decide when a locked door opens for someone. +description: Hold the game's doors locked, unlocked, open or closed for every player, set the Alohomora level a padlock asks for, share the locks players open, find a door's name, and decide when a locked door opens for someone. sidebar: order: 520 --- @@ -24,15 +24,26 @@ Doors.set(hallDoor, { locked: null, open: true }); | `locked` | Held locked. Only a door that has a lock can be locked. | Held unlocked | Left to each player's save | | `open` | Held open | Held closed; it still swings for anyone who walks into it unless it is locked | Left to swing on its own | -At runtime a field you leave out keeps whatever the server already holds for it, but the current type declarations ask for both fields, so pass `null` for the one you mean to leave to the game. +At runtime a field you leave out keeps whatever the server already holds for it, but the current type declarations ask for every field, `lockLevel` included, so pass `null` for the ones you mean to leave to the game. Each player's game re-applies what the server holds as the door streams in, so players who arrive later see it too. - `Doors.get(selector)` returns the state held under a selector, and `Doors.list()` every held state. - `Doors.clear(selector)` releases a selector, and `Doors.clearAll()` releases them all. A released door keeps its current state until it streams in again, when each player's save takes over. -- A selector left holding nothing, with both fields `null`, is released. +- A selector left holding nothing, with every field `null`, is released. - Each leaf of a double door is its own door and swings on its own, so hold both leaves to keep a double door shut. +### Lock levels + +A padlocked door asks for a level of Alohomora, from 1 to 3, and its callout shows that level. `lockLevel` holds the level for every player, so a player needs that level of Alohomora to cast it on the door. `null` leaves the door at the level it shipped with. Only padlocked doors carry a level. + +```js +// server/main.js +Doors.set(towerDoor, { locked: null, open: null, lockLevel: 3 }); +``` + +Players gain the levels through the Alohomora locks in [Progression](/guides/players/progression/#spells). Held together with `locked: true`, the lock refuses even a player whose Alohomora is high enough, and the server raises [`doorUnlockRequest`](#when-a-player-tries-a-locked-door) so your script can decide. + ## Finding a door A selector names one door, or several, in one of three ways: @@ -55,7 +66,7 @@ Events.on("resourceStart", (resourceName) => { }); ``` -`Doors.list(radius)` returns each loaded door within `radius` centimetres, 3000 by default, with its name, persistent id, full path, distance and lock state. The output goes to the [client log](/guides/getting-started/debugging/#where-output-goes). +`Doors.list(radius)` returns each loaded door within `radius` centimetres, 3000 by default, with its name, persistent id, full path, distance and lock state. A padlocked door also has its `lockLevel`, or `"key"` when only a key opens it. The output goes to the [client log](/guides/getting-started/debugging/#where-output-goes). ## When a player unlocks a door From 6365f60fa6630f6a86eb573bb5a1c0f40f26977d Mon Sep 17 00:00:00 2001 From: KHeartz Date: Sat, 24 Oct 2026 17:32:12 -0400 Subject: [PATCH 4/4] docs: point the doors and gates guides at hmp-doors Their resource sections now describe Foundations' hmp-doors: its data/hmp-doors.json rules, lock levels, shared unlocks, held gates, gate schedule and /doors commands. --- guides/World/doors.md | 25 +++++++++--------------- guides/World/gates.md | 44 ++++++++++++++++--------------------------- 2 files changed, 25 insertions(+), 44 deletions(-) diff --git a/guides/World/doors.md b/guides/World/doors.md index 3a30c32..a643c1f 100644 --- a/guides/World/doors.md +++ b/guides/World/doors.md @@ -116,28 +116,21 @@ Client scripts can change doors too, but only for their own player, and the serv - `Doors.superAlohomora(true)` lets every lock open to a key or Alohomora, whatever the player's Alohomora level, key-only locks included. Locks the server holds still refuse, and it is cleared on disconnect. - `Doors.held()` lists the door states the server holds, as this client received them. -## With the doors resource +## With Foundations -The `doors` resource holds the doors listed under `held` in its `doors.config.js` for everyone while it runs, and releases them when it stops. The file exports one object, `module.exports = { … }`: +HMP Foundations' [`hmp-doors`](https://github.com/hogwarts-mp/foundations/tree/main/resources/hmp-doors) decides which doors, gates and locks each player may pass, from ranked rules in its `data/hmp-doors.json`. A rule allows, denies or locks doors by name or path, can apply only to players in certain `hmp-core` groups, and can hold a padlock's `lockLevel` for everyone. Lower priorities are stronger. `shareUnlocks` turns shared unlocks on, and is `false` by default. -```json5 +```json { - version: 1, - held: { - "BP_Door_BellTowers_EXT": { locked: false }, - "BP_Door_Template4:AQsMCQ0PAAUKCgQEBAgADQ": { open: true }, - }, - rules: [{ priority: 1000, action: "allow", doors: "*" }], + "shareUnlocks": false, + "rules": [ + { "priority": 1000, "action": "allow", "doors": "*" }, + { "priority": 100, "action": "lock", "doors": ["BP_Door_Headmaster"], "lockLevel": 3 } + ] } ``` -| Command | Does | -| --- | --- | -| `/listdoors [radius]` | Lists the doors near you, with their names and ids, in the F8 console | -| `/doorset ` | Holds a door's state for everyone. Needs the staff or admin role. | -| `/doorsheld` | Lists every door state the server holds | - -The same resource handles gates with `heldGates`, `gates:` rules and its gate commands. See [Gates](/guides/world/gates/#with-the-doors-resource). +That is a fragment of the file. Staff in its `adminGroups` manage it in game with `/doors`: `/doors list [radius]` lists the doors near you with their names and paths. Its README covers every rule, grant and command, and its gate rules are in [Gates](/guides/world/gates/#with-foundations). ## Where to go next diff --git a/guides/World/gates.md b/guides/World/gates.md index 12fbd12..e19c8c1 100644 --- a/guides/World/gates.md +++ b/guides/World/gates.md @@ -121,46 +121,34 @@ Each call replaces the policy before it and returns how many loaded gates it che Each field the server holds overrides the policy for that gate. A lock still beats an open, so a gate the policy locks stays shut while the server only holds it open. Hold it `{ locked: false, open: true }` to open it for everyone. -## With the doors resource +## With Foundations -The `doors` resource handles gates beside doors. Its `doors.config.js` exports one object, `module.exports = { … }`, with keys like these: +HMP Foundations' [`hmp-doors`](https://github.com/hogwarts-mp/foundations/tree/main/resources/hmp-doors) handles gates beside [doors](/guides/world/doors/#with-foundations). In its `data/hmp-doors.json`, a rule with `gates` in place of `doors` takes gate names or `"*"`, and ranks and matches groups like a door rule: -```json5 +```json { - version: 1, - gateSchedule: "freeze", - heldGates: { - "BP_ZZS_TundraGate_2": { open: true }, - }, - rules: [ - { priority: 1000, action: "allow", gates: "*", kind: "portcullis" }, - { priority: 900, action: "allow", gates: "*", kind: "swingGate", open: true }, - { priority: 500, action: "deny", gates: ["BP_HW_ClocktowerPortcullis_2", "BP_HW_ClocktowerPortcullis2", "BP_HW_ClocktowerPortcullis_LOD2"], lock: true }, - ], + "gateSchedule": "freeze", + "heldGates": { "BP_ZZS_TundraGate_2": { "open": true } }, + "rules": [ + { "priority": 1000, "action": "allow", "gates": "*", "kind": "portcullis" }, + { "priority": 500, "action": "lock", "gates": ["BP_HW_ClocktowerPortcullis_2", "BP_HW_ClocktowerPortcullis2", "BP_HW_ClocktowerPortcullis_LOD2"] } + ] } ``` -A `gates:` rule takes a list of names or `"*"` and follows the same priority and allow/deny rules as a `doors:` rule. An allow unlocks a gate and leaves it closed. Three modifiers apply to `gates:` rules only: +- `allow` unlocks a gate and leaves it closed, and with `"open": true` opens it instead. `deny` withholds both. +- `lock` closes and locks the gates it names. A kind without a lock is closed instead, until a player opens it their own way. +- `kind` narrows a `"*"` rule to one kind or a list of them. Without it, `"*"` covers every kind. +- `heldGates` holds gates for everyone through `Gates.set` while the resource runs, and releases what it held when it stops. +- `gateSchedule` is `"local"` by default, which leaves the game's schedule running, or `"freeze"` to take every portcullis off the clock. -- `kind` on a `"*"` rule limits it to one kind or an array of them. Without it, `"*"` covers every kind. A rule that names gates applies to them whatever their kind. -- `open: true` on an allow opens the gate instead of only unlocking it. -- `lock: true` on a deny closes and locks the gate, instead of only leaving it out of a wider allow. - -`heldGates` holds gates for everyone through `Gates.set` while the resource runs, and releases them when it stops. `gateSchedule` is `"freeze"` by default, which takes every portcullis off the clock, or `"local"` to leave the game's schedule alone. - -| Command | Does | -| --- | --- | -| `/listgates [radius]` | Lists the loaded gates, with their kind and state, in the F8 console | -| `/gate [open\|close\|lock\|unlock]` | Changes one gate on your own client | -| `/freezegates [name\|*]` | Takes portcullises off the clock on your own client | -| `/gateset ` | Holds a gate's state for everyone. Needs the staff or admin role. | -| `/gatesheld` | Lists every gate state the server holds | +Staff list the gates near them with `/doors gates [radius]`, hold one for everyone with `/doors gateset `, and list what the server holds with `/doors gatesheld`. ## Things to watch for - **The Clocktower gate is three actors:** `BP_HW_ClocktowerPortcullis_2`, the one players walk through, plus the distant copies `BP_HW_ClocktowerPortcullis2` and `BP_HW_ClocktowerPortcullis_LOD2`. A rule or selector that names it must list all three, or use `"*"` with `kind: "portcullis"`. - **Scope wide opens with `kind`.** `openAll: true`, or an `open: true` rule on `"*"` without a `kind`, breaks every crawl grate and skips the rune gate's puzzle. -- **Freeze portcullis schedules.** Most portcullises rise at 06:00 and lower at 20:00 on each player's own game clock, which the server's [shared clock](/guides/world/time-and-weather/#the-clock-and-calendar) does not drive, so two players can see the same gate in different places. Set `freezeSchedule: true` in the policy, or keep the doors resource's `gateSchedule: "freeze"`. +- **Freeze portcullis schedules.** Most portcullises rise at 06:00 and lower at 20:00 on each player's own game clock, which the server's [shared clock](/guides/world/time-and-weather/#the-clock-and-calendar) does not drive, so two players can see the same gate in different places. Set `freezeSchedule: true` in the policy, or `hmp-doors`' `gateSchedule` to `"freeze"`. - **A gate a player opens their own way is not shared.** Solving the rune gate's puzzle, breaking a grate with a spell or opening a camp gate with Interact changes it in that player's game only, and the server is not told. Hold the gate open with `Gates.set` when everyone should find it open. ## Where to go next