Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 13 additions & 12 deletions BETA_TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@ created test transaction after its state has been inspected.
dismount volumes must not constrain the broom, including after streaming into a new area.
- Temporarily preserve boundary volumes through the `hmp-world` API or server admin command, reload the
area, and confirm the native restriction returns; reset the policy and confirm the JSON baseline wins.
- Confirm ambient students and native enemy encounters match the configured policy for every connected
client and after world travel.
- Confirm ambient students and the Overland's enemy camps and world events match the configured policy
(none under `"suppressed"`, the same NPCs for everyone under `"server"`) for every connected client and
after world travel.
- Report environment changes that affect only one client, disappear on world travel, or survive a
server restart despite differing from the JSON baseline.

Expand Down Expand Up @@ -214,24 +215,24 @@ created test transaction after its state has been inspected.

### PvP policy and duels

- Keep `hmp-pvp.defaultDecision` set to `deny`. Outside an agreed duel, cast at another player and
confirm the server vetoes the hit.
- Challenge with `/duel <playerId>`. Confirm the target receives the Arcanum-default consent dialog,
- With the default rules (`who: "none"`), cast at another player outside an agreed duel and confirm
the hit does no damage.
- Challenge with `/duel #<playerId>`. Confirm the target receives the Arcanum-default consent dialog,
can decline it, and that the challenge expires without leaving either player busy.
- While one challenge is pending, attempt another outgoing challenge and confirm exclusivity rejects
it. Attempt a cross-area challenge and confirm it is rejected.
- Accept a challenge and confirm neither player can damage the other during the authoritative
countdown. After “Duel!”, verify mutual lock-on/homing, opponent health meters, and normal spell hits.
- Deal a finishing burst and confirm the loser remains at or above the configured health floor,
enters the retail kneel when available, and both clients restore targeting, team, meter, recovery,
and damage-immunity state afterward.
- Deal a finishing burst and confirm the loser stays at or above the configured health floor, kneels,
and the duel ends as a knock-out. Afterward both players are peaceful again, the meter is gone and
health recovers normally.
- As configured staff, run `/pvp lethal on`. Confirm all connected players can mutually lock on and
ordinary relayed damage can enter the real death/respawn flow; a player joining afterward must inherit
mutual targeting after the configured delay.
ordinary relayed damage can enter the real death/respawn flow; a player joining afterward must be
hostile to everyone straight away.
- While lethal mode remains on, complete another duel and confirm its finishing hit is still floored and
kneel-finished. After the duel, both participants must return to open-world mutual hostility rather than
becoming untargetable. Run `/pvp lethal on` again and `/pvp sync` to exercise proxy re-fan recovery.
- Run `/pvp lethal off` and confirm third-party hits are denied and the global hostile presentation clears.
becoming untargetable.
- Run `/pvp lethal off` and confirm third-party hits are denied and players are peaceful again.
Do not report the currently documented missing victim-side green-bolt cosmetic as an arbitration failure.
- Exercise `/duel forfeit`, death, and disconnect. Confirm each closes the activity exactly once and
the remaining player can immediately issue or accept another challenge.
Expand Down
61 changes: 61 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,82 @@ pre-`1.0.0` policy documented in [COMPATIBILITY.md](COMPATIBILITY.md#version-pol
release with the pack, on its version, as a separate optional archive, `hmp-gamemodes-v<version>.zip`.
A gamemode may depend on the core pack; the core pack never depends on a gamemode, and the release
check enforces it. See `gamemodes/README.md`.
- `hmp-quidditch`, the first gamemode, brings HogwartsMP's Quidditch into Foundations. The pitch is
server WorldObjects: a physics Quaffle simulated by the nearest client, a Snitch Blueprint flown by
one client and steered by the server, race-ring hoops and server-moved bludgers. Sessions use
activities, character metadata, houses, native inventory, UI and input, interactions, audio, blips
and world policy. Match control is configurable, from an `hmp-core` group to anyone, and opens to
everyone while no qualifying player is online. The manager's tuning screens are live `hmp-ui`
sliders. A called match reaches eligible players as a small unfocused card they can ignore; a key
opens the picker.
- `hmp-gauntlet`, a second gamemode: cooperative wave survival against randomized, escalating waves of native
enemies. A run is an `hmp-activities` lobby, enemies come from the `hmp-npcs` catalog, ranked records
are kept per preset and party size in `hmp-worldstate`, and personal bests in `hmp-core` character
metadata. An optional `hmp-interact` altar opens the configuration screen. Every finished run raises
`hmp-gauntlet:complete` for other resources.
- The shared typings declare the Framework's server `Chat` builtin.
- `hmp-ui` input dialogs gain `range` fields: a slider with a live value readout, bounded by `min` and
`max`, with an optional `step` (now also honoured by `number` fields) and `unit`.
- `hmp-ui` `input()` takes an optional `{ onChange }` handler. While the dialog is open, `number`,
`range`, `checkbox` and `select` edits reach it as they happen, each validated against its field on
the server, so tuning screens can apply values live and restore them on cancel.
- `hmp-houses` ships a minimal client entry, like `hmp-mysql`'s, so client-bearing resources can
depend on it in the shared resource dependency graph.
- `hmp-admin` no-clip gains a slow modifier for precise positioning: holding `noclip.movement.slow`
(Alt by default) multiplies speed by `noclip.slow` (`0.25` by default). Slow wins over the Shift
boost when both are held.
- `hmp-admin` no-clip can hide the administrator's body from their own screen while flying, keeping
the falling animation out of view. Set `noclip.hideLocal` to `true`; it is off by default and
needs HogwartsMP `1.9` (`LocalPlayer.setVisibility`). Visibility is restored whenever no-clip ends.
- `hmp-admin` exports `noclip.isActive(player | playerId)`, so other resources can tell whether an
administrator is no-clipping without duplicating the state.
- `hmp-loot` `suppressGameLoot` gains `pouches`, switching off the game's coin pouches ("Search Pouch").
It needs a host with pouch suppression; an older one ignores it.

### Changed

- `hmp-pvp` lethal mode is now the host's PvP rules: on sets `who: "everyone"`, off sets `who: "none"`,
and the mode follows `pvpRulesChanged`. The host resolves relations for every client, so the client
targeting sync, `/pvp sync`, the `joinSyncDelayMs` setting and the client `mode.restore()` are gone.
Stopping `hmp-pvp` while lethal ends lethal mode.
- `hmp-pvp` `defaultDecision` now defaults to `"allow"`. The host drops hits between players who are not
hostile before any policy runs, so an undecided hit follows the host's own relations: a fresh server
stays consent-only under `who: "none"`, and a team, flag or pair set up elsewhere deals damage instead
of being vetoed. Set `"deny"` to keep registered rules as the only way a hit lands.
- `hmp-world`'s policy replaces `nativeEncounters` with `encounters`: `"suppressed"` (the default) or
`"server"`, where the server spawns the Overland's camps and world events as NPCs every player shares.
The server applies it through `World.setEncounterPolicy`, so clients are no longer sent it: the client
policy, `policy.current()` and `status()` carry only boundaries and population, and `status()` drops
`encounterVolumesChanged`. A config that leaves `encounters` out, or still has `nativeEncounters`,
runs suppressed.
- `hmp-quidditch` no longer switches encounters off for a match: with server camps that would despawn
every camp for everyone.

### Fixed

- `hmp-duels` works on current HogwartsMP hosts, which removed the client duel builtins it called
(`startDuelContext`, `setTargetable`, `setTeam`, `kneel`, `endDuelContext`) and drop hits between
players who are not hostile. An accepted duel is now a `Pvp.setPair` override, peaceful through the
countdown and then hostile with a kneel finish. Health recovery is held off for the fight, and the
duel ends on the host's `pvpDefeat` instead of a knock-out guessed from stale vitals. The countdown
timer is no longer unreferenced, which had left accepted duels counting down forever. The usage text
now shows that a player id takes a `#`.
- `hmp-emotes` keeps Enter in its menu: current hosts hand web views Enter's character, which also
pressed whichever button a click had left focused.
- `hmp-world`'s client no longer calls the removed client `World.setEncounterPolicy`, which failed every
policy update on current HogwartsMP hosts and left the client policy unset.
- `hmp-inventory` keeps a worn item's Equipment-menu look (`appearanceOverride`, the transmog source
GearID) when it saves and restores native rows, so gear no longer comes back in its own look after a
reconnect. It needs a HogwartsMP host that reports the field; an older host leaves it out, as before.
An override that is not a bare GearID is dropped on its own instead of failing the whole restore.

### Known issues

- NPC kill experience does not persist. Current hosts pay kill rewards on the killer's client without
telling the server the amount, so `hmp-progression` never records it and its next synchronization sets
the experience back. Field Guide and kill-challenge progress are unaffected. See the `hmp-progression`
README for a server-side workaround.

## [0.8.2] - 2026-10-12

### Changed
Expand Down
12 changes: 6 additions & 6 deletions COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ preserve them:
native robe/crest character rebuild;
- client `Doors` logical-lock, Alohomora, physical-door diagnostic, and streaming-aware policy builtins;
- client `Breakables` repairable-object watch, listing, and streaming-aware state apply builtins with the native `breakableState` event (mod commit `59b3cfa9` or newer);
- server `World.setEncounterPolicy` (`"suppressed"` or `"server"`), the server-owned enemy encounter policy used by `hmp-world` (mod commit `c8c3fc74` or newer);
- synchronized `LocalPlayer.playClip`, ability playback/cancellation, emote preview/placement, and
prop/sound animation-notify replay;
- client key binding, controls, position, browser UI, and resource HTTP assets.
Expand All @@ -53,18 +54,17 @@ preserve them:
read/write, production slot casting, and native `spellCast` events.
- client `Progression` absolute point reconciliation and level-bound lookup, plus native `Talents`
availability, point, purchase, grant, level, removal, refund, and reset operations.
- server `Pvp` hit policy, live-health `minHp` floors and vitals view; client mutual targetability,
team relationships, duel context, opponent meter, damage immunity and near-death kneel controls.
- server-owned `Pvp` relations: rules (`configure`, `getRules`, `pvpRulesChanged`), pair overrides
(`setPair`) and the `pvpDefeat` event; the hit policy with live-health `minHp` floors and the vitals
view; `Player.setHealthRecovery`; and the client opponent meter (mod commit `55acb616` or newer).
- server `NPC.create`, NPC mutation/destruction, `NPCManager`, and `npcDied` events using the verified
enemy identifiers documented by `hmp-npcs`.
- server `Lootables` placement, contents, per-player views and the `lootableTaken` event, and
`World.suppressLoot`, used by `hmp-loot`; a build without them leaves
`hmp-loot` degraded with nothing placed.

The current PvP host has two presentation caveats: relayed Killing Curse hits do not reproduce the
victim-side green-bolt cosmetic, and target/team writes require the remote proxy to be streamed. Foundations
re-fans complete snapshots for late joiners and exposes `/pvp sync` for recovery; neither caveat weakens
server arbitration or the duel health floor.
The current PvP host has one presentation caveat: relayed Killing Curse hits do not reproduce the
victim-side green-bolt cosmetic. It does not weaken server arbitration or the duel health floor.

Asynchronous resource lifecycle is not a requirement for Foundations `0.8.2`. The current pack still
uses the existing synchronous lifecycle and will adopt the asynchronous contract after upstream
Expand Down
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ COPY resources/hmp-webhooks/package.json resources/hmp-webhooks/package.json
COPY resources/hmp-world/package.json resources/hmp-world/package.json
COPY resources/hmp-worldstate/package.json resources/hmp-worldstate/package.json
COPY resources/hmp-loot/package.json resources/hmp-loot/package.json
COPY gamemodes/resources/hmp-gauntlet/package.json gamemodes/resources/hmp-gauntlet/package.json
COPY gamemodes/resources/hmp-quidditch/package.json gamemodes/resources/hmp-quidditch/package.json
RUN npm ci

COPY . .
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ The dedicated server may run on Windows x64 or Linux x86-64; Hogwarts Legacy cli

Complete games built on Foundations ship separately, as optional resources a server adds on top of the
core pack. They live in [`gamemodes/`](gamemodes/README.md), release with the pack on its version, and
are published as their own archive.
are published as their own archive. The first is [`hmp-quidditch`](gamemodes/resources/hmp-quidditch).

## Development

Expand Down
3 changes: 2 additions & 1 deletion examples/config/data/hmp-loot.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"suppressGameLoot": {
"chests": true,
"moonstones": true,
"foragables": true
"foragables": true,
"pouches": true
},
"lootables": [
{
Expand Down
3 changes: 1 addition & 2 deletions examples/config/data/hmp-pvp.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
{
"defaultDecision": "deny",
"defaultDecision": "allow",
"lethalMode": false,
"enableCommands": true,
"command": "pvp",
"joinSyncDelayMs": 3000,
"adminGroups": [
{ "key": "admin", "minimumGrade": 1 }
]
Expand Down
2 changes: 1 addition & 1 deletion examples/config/data/hmp-world.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@
"policy": {
"removeBoundaryVolumes": true,
"ambientPopulation": false,
"nativeEncounters": false
"encounters": "suppressed"
}
}
4 changes: 3 additions & 1 deletion gamemodes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
Gamemodes are complete games built on HMP Foundations. They are optional: the core pack is the
infrastructure every Foundations server installs, and a server adds only the gamemodes it wants on top.

No gamemodes ship yet. Each one goes in `resources/` here and gets a row below.
Each one lives in `resources/` here and has a row below.

| Gamemode | Description |
|---|---|
| [`hmp-gauntlet`](resources/hmp-gauntlet) | Cooperative wave survival: a party fights randomized, escalating waves of native enemies for ranked leaderboards. Built on activities, NPCs, interactions and worldstate. |
| [`hmp-quidditch`](resources/hmp-quidditch) | Quidditch on the Hogwarts pitch: called matches with house lineups, practice and Snitch hunts. Built on activities, houses, UI, input, inventory, audio, blips and world policy. |

## Versioning

Expand Down
3 changes: 3 additions & 0 deletions gamemodes/resources/hmp-gauntlet/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This resource is licensed under the same terms as HMP Foundations.

See the repository-level LICENSE file for the complete license text.
97 changes: 97 additions & 0 deletions gamemodes/resources/hmp-gauntlet/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# hmp-gauntlet

`hmp-gauntlet` is a cooperative wave-survival gamemode. Players form a Foundations activity, then
fight randomized, escalating waves of real Hogwarts Legacy enemies. The gamemode owns encounter
rules, scoring, leaderboards, its configuration screen, and its HUD. Foundations owns the reusable
session, character, input, NPC, and interaction behavior.

## Flow

1. The host opens the configuration screen with `/gauntlet menu` or the optional altar.
2. Starting creates a public `hmp-gauntlet:survival` activity lobby at the host's position.
3. Other players use `/gauntlet join`.
4. The host uses `/gauntlet begin` to lock membership and start the waves.
5. Clearing the configured waves completes the activity. A party wipe completes it as a loss, and
`/gauntlet stop` cancels it.

## Commands

```text
/gauntlet menu
/gauntlet start
/gauntlet join
/gauntlet begin
/gauntlet stop
/gauntlet status
/gauntlet top [preset]
```

`start` creates a lobby with the default preset without opening the configuration screen. `top` is
public; the remaining commands operate on the current activity.

## Foundations integration

- `hmp-activities` owns lobby membership and lifecycle transitions.
- `hmp-interact` owns the optional altar, prompt, range check, and world object.
- `hmp-lib` owns command routing, configuration loading, logging, and the client control lease.
- `hmp-core` owns active-character leaderboard metadata.
- `hmp-worldstate` persists the server-wide leaderboards.
- `hmp-npcs` supplies the Framework-verified enemy catalog and owns resource-scoped NPC spawning and cleanup.
- `hmp-gauntlet` bundles its configuration screen and HUD, so it has no `hmp-ui` dependency.

It types against each core resource's own `types.d.ts`, so a Foundations API change fails its
typecheck instead of the running server.

## Configuration

The defaults live in `server/config.ts`. A server can override them with `data/hmp-gauntlet.json`, or
the file `HMP_GAUNTLET_CONFIG` names; overrides are normalized before use. Supported sections are
`presets`, `defaultPreset`, `bosses`, `hidden`, `groups`, `arena`, `players`, and `interaction`.

The enemy roster comes from the runtime `hmp-npcs` catalog, which is verified against the Framework's
native definitions. Bosses missing from the catalog are skipped. The altar is off by default; enable
`interaction.enabled` and author its `position`, `areaId`, and optional `regionId` for your map:

```json
{
"interaction": {
"enabled": true,
"position": { "x": 0, "y": 0, "z": 0 },
"areaId": "Overland",
"label": "Enter the Gauntlet"
}
}
```

## Scaling and persistence

Each wave scales enemy count, health, size, and boss cadence. Enemy ownership is round-robined across
connected activity participants so the pack distributes aggro. Ownership stays fixed for an enemy's
lifetime because reassignment re-embodies and heals the pawn.

Ranked runs require an unchanged preset and the full enemy roster. Scores are separated by preset and
party-size bucket. Server records live in the `gauntlet-board` system of `hmp-worldstate` (MySQL,
server-only); per-character bests use `hmp-core` metadata under `hmp-gauntlet:bests`.

## Events

Every run that ends raises the server event `hmp-gauntlet:complete` once, with an
`HmpGauntletCompletion` payload (see `types.d.ts`): the result, wave, score, preset, bucket, whether
it was ranked, and the participants still connected.

## Development

From the repository root:

```text
npm run typecheck -w hmp-gauntlet
npm test -w hmp-gauntlet
npm run build -w hmp-gauntlet
npm run smoke -w hmp-gauntlet
```

`server/main.ts`, `server/config.ts`, and `client/main.ts` are the authored sources; the build bundles
them into `dist/server.js` and `dist/client.js` and copies the two static pages beside them.

Current gameplay limitations are one global run at a time, fixed enemy ownership, owner-only enemy
damage, and client-local HUD and configuration rendering.
Loading
Loading