304ee8c2a7
Portal-gated rooms: new RunLifecycle.RoomExplore loot window; room+nodes persist past the last kill; PortalInteractRequest -> server-only PortalCommand advances (client derives the portal pos). RunDirectorSystem moves teardown off the InRoom edge, relocates the boss-branch/route-gate into the RoomExplore exit, and advances an empty expedition immediately (incl. a boss clear). Class-at-base via a shared ClassSwapUtil (meta-band strip + per-class MetaTierState replay, so the base RPC and the dev SetClass can't drift); ClassSelectRequest Staging-gated. Per-run PREP buffs (PrepPurchaseRequest, PrepCatalog): once-per-run == the row's prep StatModifier band is present, TotalOf-before-Withdraw atomic, stripped on Returning beside the boon band. Health.Max promoted to [GhostField] (the one ghost-hash re-bake) so the boss + floating enemy HP bars read it directly. Feel: melee cone connect-thunk (C3), hit-stop throttle so a horde wipe can't stutter (C4), Spitter cornered-hold. 456/456 EditMode; two adversarial reviews (pre-code 13-confirmed folded; post-impl clean bar the RoomExplore boss-clear dead-time, fixed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
86 lines
5.3 KiB
C#
86 lines
5.3 KiB
C#
using Unity.Entities;
|
|
using Unity.NetCode;
|
|
|
|
namespace ProjectM.Simulation
|
|
{
|
|
/// <summary>
|
|
/// Lifecycle states for a co-op expedition run (<see cref="RunInfo.Lifecycle"/>). A <c>byte</c>, never an enum
|
|
/// (Burst/serialization safe), APPEND-ONLY. <see cref="RouteSelect"/> is appended after the original four so no
|
|
/// value is re-meaned. The party is at the base hub in <see cref="Staging"/>; a discrete run spans
|
|
/// <see cref="Launching"/>→<see cref="InRoom"/>→<see cref="RoomReward"/>→<see cref="RouteSelect"/> (loop) →
|
|
/// <see cref="Returning"/>→<see cref="Staging"/>.
|
|
/// </summary>
|
|
public static class RunLifecycle
|
|
{
|
|
/// <summary>Party in the base hub; ready-check active; no expedition ghosts exist.</summary>
|
|
public const byte Staging = 0;
|
|
/// <summary>All-ready launch transient: seed chosen, party teleporting into room 0.</summary>
|
|
public const byte Launching = 1;
|
|
/// <summary>Active room populated; party fighting/looting.</summary>
|
|
public const byte InRoom = 2;
|
|
/// <summary>Room cleared; per-player boon offers pending; room torn down.</summary>
|
|
public const byte RoomReward = 3;
|
|
/// <summary>Run ended (boss cleared / party wiped / all left): teleport home, bank, → Staging.</summary>
|
|
public const byte Returning = 4;
|
|
/// <summary>Boons picked; party choosing the next branch (no room materialized — the teardown gap).</summary>
|
|
public const byte RouteSelect = 5;
|
|
/// <summary>DR-046: room cleared + boon picked, but the room + resource NODES persist and a portal is up.
|
|
/// Party loots; interacting the portal (or a soft-timeout) tears the room down + advances (RouteSelect, or
|
|
/// Returning if the boss fell). Append-only byte value — no ghost re-mean.</summary>
|
|
public const byte RoomExplore = 6;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// The REPLICATED run-lifecycle summary the whole party observes — a server-decided, client-observed FSM on the
|
|
/// GLOBAL untagged CycleDirector ghost (so it is relevant cross-region for free, like <see cref="CycleState"/>/
|
|
/// <see cref="GoalProgress"/>/<see cref="RunOutcome"/>). SOLE writer: <c>RunDirectorSystem</c>. Distinct from
|
|
/// <see cref="CycleState.Phase"/> (that stays the BASE Calm↔Siege posture for retaliation/final sieges).
|
|
///
|
|
/// Fields split three ways: the lifecycle/room readout (HUD "Room i/N", biome cross-fade), the branching-map
|
|
/// wire (<see cref="RunSeed"/> so the client regenerates the map for DISPLAY, + <see cref="CurrentCol"/> and the
|
|
/// authoritative reachable <c>RouteOpt*</c> the clickable options bind to), and a two-field mirror of the
|
|
/// persisted meta counters for the HUD. All integers/bytes → replicate exact (no quantization). Adding this
|
|
/// <c>[GhostField]</c> component re-hashes the runtime-spawned director ghost (server + client bake the same
|
|
/// prefab → hash matches), exactly like <see cref="CoreIntegrity"/>/<see cref="RunOutcome"/>.
|
|
/// </summary>
|
|
public struct RunInfo : IComponentData
|
|
{
|
|
// ---- lifecycle + room readout ----
|
|
/// <summary><see cref="RunLifecycle"/>.</summary>
|
|
[GhostField] public byte Lifecycle;
|
|
/// <summary>0-based depth of the active room (HUD "Room CurrentRoom+1 / RoomCount").</summary>
|
|
[GhostField] public int CurrentRoom;
|
|
/// <summary>Total rooms this run (== map layer count, seed-varied in [6,10]).</summary>
|
|
[GhostField] public int RoomCount;
|
|
/// <summary><see cref="RoomTypeId"/> of the active room.</summary>
|
|
[GhostField] public byte CurrentRoomType;
|
|
/// <summary><see cref="RoomBiomeId"/> of the active room (client atmosphere cross-fade).</summary>
|
|
[GhostField] public byte CurrentBiome;
|
|
/// <summary>Server tick the launch countdown elapses (0 = none). Via <see cref="TickUtil.NonZero"/>; compared with IsNewerThan.</summary>
|
|
[GhostField] public uint LaunchTick;
|
|
|
|
// ---- branching map wire ----
|
|
/// <summary>The run seed — clients regenerate the map layout for DISPLAY via <see cref="RunMapMath.Generate"/> (no gameplay authority).</summary>
|
|
[GhostField] public uint RunSeed;
|
|
/// <summary>The party's current column in the active layer.</summary>
|
|
[GhostField] public byte CurrentCol;
|
|
/// <summary>Number of reachable next-room options (0 unless <see cref="RunLifecycle.RouteSelect"/>).</summary>
|
|
[GhostField] public byte RouteOptionCount;
|
|
/// <summary>Reachable next-layer column for option 0 (authoritative — the clickable button binds to this, not the regen).</summary>
|
|
[GhostField] public byte RouteOpt0Col;
|
|
[GhostField] public byte RouteOpt1Col;
|
|
[GhostField] public byte RouteOpt2Col;
|
|
/// <summary><see cref="RoomTypeId"/> of option 0 (so the HUD labels the choice).</summary>
|
|
[GhostField] public byte RouteOpt0Type;
|
|
[GhostField] public byte RouteOpt1Type;
|
|
[GhostField] public byte RouteOpt2Type;
|
|
|
|
// ---- persisted-meta HUD mirror ----
|
|
/// <summary>Runs completed (boss-cleared), mirrored for the HUD from the persisted meta counters.</summary>
|
|
[GhostField] public int RunsCompleted;
|
|
/// <summary>Deepest room reached across runs, mirrored for the HUD.</summary>
|
|
[GhostField] public int MaxDepthReached;
|
|
}
|
|
}
|