Death re-entry: auto-respawn at a resurrection point (no GM needed) #112

Closed
opened 2026-07-20 18:37:39 +02:00 by marco · 3 comments
Owner

Part of the Alpha epic (pillar 3). Today resurrection is GM-only (/resurrect targeting a ghost). In a PvP arena that means the loop dies without an arbiter present: die -> ghost -> stuck. Add automatic re-entry so the arena self-sustains.

Model it as a resurrection-point system, not a hardcoded arena coordinate — a real MMORPG has many spawn/res points (healers, shrines, bind points). For alpha the arena defines one or more res points (from #89/#B) and death routes the player to the nearest.

Scope

  • After a player becomes a ghost, auto-resurrect them after a configurable delay (GameOptions, single default) at a resurrection point, at full vitals.
  • Resurrection point(s) come from the arena content (the entrance/res point defined in the arena issue), not a literal in code.
  • Keep the GM /resurrect command working (manual override still allowed).
  • Server-authoritative: the respawn location and timing are decided on the sim thread; the client only reacts to PlayerResurrected.
  • Any new user-facing copy via the string catalog.

Out of scope

  • Corpse looting / item loss on death (separate concern).
  • Multiple bind points chosen by the player (future).

Definition of Done

Base DoD applies on top.

  • A player killed in the arena auto-resurrects after the configured delay at a resurrection point with full vitals, with no GM action (demonstrable: die to another player, wait, respawn at the entrance).
  • The resurrection point is read from arena content, not hardcoded.
  • GM /resurrect still works as a manual override.
Part of the Alpha epic (pillar 3). Today resurrection is GM-only (`/resurrect` targeting a ghost). In a PvP arena that means the loop dies without an arbiter present: die -> ghost -> stuck. Add **automatic re-entry** so the arena self-sustains. > Model it as a **resurrection-point system**, not a hardcoded arena coordinate — a real MMORPG has many spawn/res points (healers, shrines, bind points). For alpha the arena defines one or more res points (from #89/#B) and death routes the player to the nearest. ## Scope - After a player becomes a ghost, auto-resurrect them after a configurable delay (`GameOptions`, single default) at a resurrection point, at full vitals. - Resurrection point(s) come from the arena content (the entrance/res point defined in the arena issue), not a literal in code. - Keep the GM `/resurrect` command working (manual override still allowed). - Server-authoritative: the respawn location and timing are decided on the sim thread; the client only reacts to `PlayerResurrected`. - Any new user-facing copy via the string catalog. ## Out of scope - Corpse looting / item loss on death (separate concern). - Multiple bind points chosen by the player (future). ## Definition of Done _Base DoD applies on top._ - [ ] A player killed in the arena auto-resurrects after the configured delay at a resurrection point with full vitals, with no GM action (demonstrable: die to another player, wait, respawn at the entrance). - [ ] The resurrection point is read from arena content, not hardcoded. - [ ] GM `/resurrect` still works as a manual override.
marco added this to the Alpha milestone 2026-07-20 18:37:39 +02:00
Author
Owner

Design update — resurrection via Ankh (supersedes the timer model above)

The original scope (timed auto-respawn at a res point) is replaced: resurrection is now a diegetic, player-driven action — a ghost double-clicks an Ankh item and is resurrected on the spot. Reviewed via the critical-design-review skill; agreed with the owner.

Shape

  • Ankh = item with behavior. New capability interface IUsable on the auto-discovered BaseItem layer (mirrors IEquippable); Ankh : BaseItem, IUsable, resolved via ItemCatalog.Usable(kind). Item stays sealed (not subclassed).
  • New intent Use(Guid ItemId) (double-click), routed by GameSessionHandler through one Simulation.InvokeAsync → thin World.UseItemItemRegistry (find placed item → range check → Ankh.OnUse(user)). Validation server-side: item exists + is a Placed ankh + user.IsGhost + Chebyshev ≤ UseRange.
  • Resurrection is intrinsic on the entity: PlayerMobile.Resurrect() (IsGhost=false; Hits=MaxHits, no position change — res where you stand) and PlayerMobile.Die() (IsGhost=true; Hits=0).
  • Timer machinery deleted: the _resurrections scheduling dict, ProcessResurrections, ForceResurrect indirection, ResurrectDelayTicks, and the old spawn-forcing Resurrect. GM /resurrect stays (acts directly on the instance). Broadcast rides a transient per-tick event queue (_pendingResurrections, symmetric to _pendingDeaths) → PlayerResurrected.
  • Ankh is a world-placed persistent item (a category that didn't exist): new ItemPlace.Placed + a persisted section in the world save (mirrors CreatureSerializer), restored single-threaded at startup, visible via AoI (ItemSpawned). Placed by GM /place ankh (mirrors /spawn).
  • IsGhost is now persisted on PlayerMobile (version bump) so relog isn't a free resurrection.
  • FX: reuses the existing PlayerResurrected message; the client plays a resurrect sparkle off it (no new server→client message). Art via the pack (item_ankh) with PlaceholderArt fallback.
  • Feedback ("you are not dead" / "out of range") via SystemMessageId + catalog.

Owner decisions (confirmed)

Placement = GM /place; UseRange = 2; resurrect HP = full; persist ghost across relog = yes; light per-use rate-limit; ship as one PR.

Invariants Check (per CLAUDE.md → ## Design checklist)

  • Scope ✓ (epic-#107 pillar-3 item) · Server-authoritative ✓ (Use intent only; server validates ghost+range+ankh; /place tile validated vs TileMap) · GM authorization ✓ (/place,/res admin-gated; Use is a normal intent) · Identity N/A · Protocol versioned ✓ (new Use + SystemMessageId → bump ProtocolVersion.Current 5→6) · String catalog ✓ · Single-threaded sim ✓ (one InvokeAsync; startup restore single-threaded) · World.cs HARD GATE ✓ (World.UseItem delegates to ItemRegistry) · Screen HARD GATE ✓ (input→GameInputController, FX→WorldRenderer, msg→ServerMessageDispatcher) · Client engine-independence ✓ (DoubleClickDetector in Client.Core) · Gameplay/Networking separation ✓ · Act on the instance ✓ (PlayerMobile.Resurrect/Die; scheduling dict deleted; only a transient event queue remains) · Extend by type, not switch ✓ (Ankh : BaseItem, IUsable, ItemCatalog.Usable) · Server-paced actions N/A (instantaneous) · Persistence (GameServer) ✓ (ItemPlace.Placed + serializer section, own version; IsGhost persisted; GM-placed) · Persistence (Auth) N/A · Process separation N/A · Typed options ✓ (UseRange on GameOptions) · Broadcasts absolute + AoI ✓ (ankh reconciled as entity; res is AoI-scoped) · Multi-platform ✓ · Asset fallback ✓ (item_ankh → placeholder) · ModernUO ref ✓ (follow OnDoubleClick/ankh; reject res-gump/stat-loss/serial-linker) · Docs & DoD ✓ (update gm-commands.md; this DoD).

Definition of Done (revised — replaces the timer DoD above)

Base DoD applies on top.

  • A ghost who double-clicks an ankh within range returns to life at full HP, on the spot, with a resurrection FX visible to nearby players.
  • Using the ankh while alive, or from out of range, does nothing but a catalogued feedback message; a non-ankh item does nothing.
  • The timed auto-respawn is gone: a ghost that never reaches an ankh stays a ghost (GM /resurrect still works); relog does not resurrect a ghost.
  • A GM /place ankh places an ankh that survives a server restart and is visible to players within AoI.
  • A modified client cannot resurrect a live player, from beyond range, someone else, or via a non-ankh item (server-validated).
  • docs/gm-commands.md gains the /place row; ProtocolVersion.Current bumped.
## Design update — resurrection via **Ankh** (supersedes the timer model above) The original scope (timed auto-respawn at a res point) is **replaced**: resurrection is now a diegetic, player-driven action — a ghost double-clicks an **Ankh** item and is resurrected on the spot. Reviewed via the `critical-design-review` skill; agreed with the owner. ### Shape - **Ankh = item with behavior.** New capability interface `IUsable` on the auto-discovered `BaseItem` layer (mirrors `IEquippable`); `Ankh : BaseItem, IUsable`, resolved via `ItemCatalog.Usable(kind)`. `Item` stays `sealed` (not subclassed). - **New intent** `Use(Guid ItemId)` (double-click), routed by `GameSessionHandler` through **one** `Simulation.InvokeAsync` → thin `World.UseItem` → `ItemRegistry` (find placed item → range check → `Ankh.OnUse(user)`). Validation server-side: item exists + is a `Placed` ankh + `user.IsGhost` + Chebyshev ≤ `UseRange`. - **Resurrection is intrinsic on the entity:** `PlayerMobile.Resurrect()` (`IsGhost=false; Hits=MaxHits`, **no position change** — res where you stand) and `PlayerMobile.Die()` (`IsGhost=true; Hits=0`). - **Timer machinery deleted:** the `_resurrections` scheduling dict, `ProcessResurrections`, `ForceResurrect` indirection, `ResurrectDelayTicks`, and the old spawn-forcing `Resurrect`. GM `/resurrect` stays (acts directly on the instance). Broadcast rides a transient per-tick event queue (`_pendingResurrections`, symmetric to `_pendingDeaths`) → `PlayerResurrected`. - **Ankh is a world-placed persistent item** (a category that didn't exist): new `ItemPlace.Placed` + a persisted section in the world save (mirrors `CreatureSerializer`), restored single-threaded at startup, visible via AoI (`ItemSpawned`). Placed by GM **`/place ankh`** (mirrors `/spawn`). - **`IsGhost` is now persisted** on `PlayerMobile` (version bump) so relog isn't a free resurrection. - **FX:** reuses the existing `PlayerResurrected` message; the client plays a resurrect sparkle off it (no new server→client message). Art via the pack (`item_ankh`) with `PlaceholderArt` fallback. - **Feedback** ("you are not dead" / "out of range") via `SystemMessageId` + catalog. ### Owner decisions (confirmed) Placement = GM `/place`; UseRange = 2; resurrect HP = full; **persist ghost across relog = yes**; light per-use rate-limit; ship as one PR. ### Invariants Check (per `CLAUDE.md → ## Design checklist`) - Scope ✓ (epic-#107 pillar-3 item) · Server-authoritative ✓ (Use intent only; server validates ghost+range+ankh; `/place` tile validated vs TileMap) · GM authorization ✓ (`/place`,`/res` admin-gated; `Use` is a normal intent) · Identity N/A · Protocol versioned ✓ (new `Use` + SystemMessageId → bump `ProtocolVersion.Current` 5→6) · String catalog ✓ · Single-threaded sim ✓ (one InvokeAsync; startup restore single-threaded) · World.cs HARD GATE ✓ (`World.UseItem` delegates to `ItemRegistry`) · Screen HARD GATE ✓ (input→`GameInputController`, FX→`WorldRenderer`, msg→`ServerMessageDispatcher`) · Client engine-independence ✓ (`DoubleClickDetector` in Client.Core) · Gameplay/Networking separation ✓ · Act on the instance ✓ (`PlayerMobile.Resurrect/Die`; scheduling dict deleted; only a transient event queue remains) · Extend by type, not switch ✓ (`Ankh : BaseItem, IUsable`, `ItemCatalog.Usable`) · Server-paced actions N/A (instantaneous) · Persistence (GameServer) ✓ (`ItemPlace.Placed` + serializer section, own version; `IsGhost` persisted; GM-placed) · Persistence (Auth) N/A · Process separation N/A · Typed options ✓ (`UseRange` on `GameOptions`) · Broadcasts absolute + AoI ✓ (ankh reconciled as entity; res is AoI-scoped) · Multi-platform ✓ · Asset fallback ✓ (`item_ankh` → placeholder) · ModernUO ref ✓ (follow `OnDoubleClick`/ankh; reject res-gump/stat-loss/serial-linker) · Docs & DoD ✓ (update `gm-commands.md`; this DoD). ### Definition of Done (revised — replaces the timer DoD above) _Base DoD applies on top._ - [ ] A ghost who double-clicks an ankh within range returns to life at **full HP, on the spot**, with a resurrection FX visible to nearby players. - [ ] Using the ankh while alive, or from out of range, does nothing but a catalogued feedback message; a **non-ankh** item does nothing. - [ ] The timed auto-respawn is gone: a ghost that never reaches an ankh stays a ghost (GM `/resurrect` still works); **relog does not resurrect a ghost**. - [ ] A GM `/place ankh` places an ankh that **survives a server restart** and is visible to players within AoI. - [ ] A modified client cannot resurrect a live player, from beyond range, someone else, or via a non-ankh item (server-validated). - [ ] `docs/gm-commands.md` gains the `/place` row; `ProtocolVersion.Current` bumped.
Author
Owner

Mechanism update (agreed): ghost state is delivered as reconciled appearance state, not one-shot events. PlayerState gains PlayerAppearance(bool IsGhost); PlayerJoined/Welcome carry it (fixes the enter-AoI-sees-ghost-as-alive bug); a new PlayerAppearanceChanged(id, appearance) is emitted by the AoI reconcile when a known player's appearance changes this tick. Removed: PlayerDied, PlayerResurrected, _pendingDeaths, _pendingResurrections, _resurrections, ProcessResurrections. Death/resurrection = intrinsic PlayerMobile.Die()/Resurrect() which flip IsGhost + mark the entity AppearanceDirty; PlayerRegistry drains dirty players each tick → the reconcile broadcasts. This is the shared foundation for #18 (worn gear = more PlayerAppearance fields). Invariants Check re-run: 0 blocking; Broadcasts+AoI improves (entering AoI now shows correct state); Act-on-instance improves (two event queues collapse into an intrinsic dirty flag). Client derives ghost render, the #30 death screen, and the resurrect FX from the observed appearance transition. ProtocolVersion 5→6.

**Mechanism update (agreed):** ghost state is delivered as **reconciled appearance state**, not one-shot events. `PlayerState` gains `PlayerAppearance(bool IsGhost)`; `PlayerJoined`/`Welcome` carry it (fixes the enter-AoI-sees-ghost-as-alive bug); a new `PlayerAppearanceChanged(id, appearance)` is emitted by the AoI reconcile when a known player's appearance changes this tick. **Removed:** `PlayerDied`, `PlayerResurrected`, `_pendingDeaths`, `_pendingResurrections`, `_resurrections`, `ProcessResurrections`. Death/resurrection = intrinsic `PlayerMobile.Die()/Resurrect()` which flip `IsGhost` + mark the entity `AppearanceDirty`; `PlayerRegistry` drains dirty players each tick → the reconcile broadcasts. This is the shared foundation for #18 (worn gear = more `PlayerAppearance` fields). Invariants Check re-run: 0 blocking; **Broadcasts+AoI** improves (entering AoI now shows correct state); **Act-on-instance** improves (two event queues collapse into an intrinsic dirty flag). Client derives ghost render, the #30 death screen, and the resurrect FX from the observed appearance transition. ProtocolVersion 5→6.
Author
Owner

Superseded by #129 (ankh resurrection, merged) — closing.

The core goal here — a self-sustaining, no-GM re-entry loop — is met: a ghost double-clicks a placed Ankh (GameServer/Gameplay/Items/Ankh.cs) to return to life, no GM /resurrect required. Note the deliberate design pivot from this issue's original auto-after-delay respawn to a walk-to-ankh resurrection fixture — the more MMO-general resurrection-point model. GM /resurrect remains as a manual override. Ankh art polish is tracked in #130.

Superseded by #129 (ankh resurrection, merged) — closing. The core goal here — a **self-sustaining, no-GM** re-entry loop — is met: a ghost double-clicks a placed `Ankh` (`GameServer/Gameplay/Items/Ankh.cs`) to return to life, no GM `/resurrect` required. Note the deliberate **design pivot** from this issue's original *auto-after-delay* respawn to a *walk-to-ankh* resurrection **fixture** — the more MMO-general resurrection-point model. GM `/resurrect` remains as a manual override. Ankh art polish is tracked in #130.
marco closed this issue 2026-07-22 14:48:03 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
marco/IsoMmo#112
No description provided.