Game Scene Split up
This commit is contained in:
@@ -1,17 +1,22 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Burst;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Server-authoritative macro-loop director for "The Aether Cycle": Expedition (timed) -> Defend
|
||||
/// (wave-driven) -> Build (timed) -> next cycle. Maintains the <see cref="CycleState"/> singleton and gates
|
||||
/// <see cref="WaveSystem"/> so the base-defense wave only spawns during Defend. Runs in the plain server
|
||||
/// SimulationSystemGroup (NOT prediction) before <see cref="WaveSystem"/>. All timing is wrap-safe
|
||||
/// NetworkTick math (<see cref="TickUtil.NonZero"/> + <see cref="Unity.NetCode.NetworkTick.IsNewerThan"/>),
|
||||
/// never raw uint compares. The CycleState/CycleRuntime live on the runtime-spawned CycleDirector ghost.
|
||||
/// Server-authoritative macro-loop director for the PLAYER-DRIVEN loop. The base sits in <c>Calm</c>
|
||||
/// (persistent, unhurried — build/prep at your pace, no countdown) until the <see cref="ThreatState"/> arms a
|
||||
/// siege, then flips to <c>Siege</c> (the base-defense wave) and back to <c>Calm</c> when the wave is cleared.
|
||||
/// There is no global "Expedition" phase — being out on an expedition is per-player presence (server-only
|
||||
/// <see cref="RegionTag"/>), read client-side by the HUD, so one global byte never has to represent
|
||||
/// "player A out / player B home." Maintains the replicated <see cref="CycleState"/> singleton and gates
|
||||
/// <see cref="WaveSystem"/> (waves spawn only during Siege). Runs in the plain server SimulationSystemGroup
|
||||
/// before WaveSystem. All timing is wrap-safe NetworkTick math (<see cref="ProjectM.Simulation.TickUtil.NonZero"/>
|
||||
/// + <see cref="Unity.NetCode.NetworkTick.IsNewerThan"/>), never raw uint compares. Lives on the
|
||||
/// runtime-spawned CycleDirector ghost. Supersedes the forced timed Expedition→Defend→Build cycle.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
@@ -38,48 +43,64 @@ namespace ProjectM.Server
|
||||
uint now = serverTick.TickIndexForValidTick;
|
||||
|
||||
var cycleEntity = SystemAPI.GetSingletonEntity<CycleState>();
|
||||
|
||||
var cycle = SystemAPI.GetComponent<CycleState>(cycleEntity);
|
||||
var runtime = SystemAPI.GetComponent<CycleRuntime>(cycleEntity);
|
||||
|
||||
bool timedPhaseDue =
|
||||
cycle.PhaseEndTick != 0 && !new NetworkTick(cycle.PhaseEndTick).IsNewerThan(serverTick);
|
||||
|
||||
switch (cycle.Phase)
|
||||
if (cycle.Phase == CyclePhase.Calm)
|
||||
{
|
||||
case CyclePhase.Expedition:
|
||||
if (timedPhaseDue)
|
||||
{
|
||||
cycle.Phase = CyclePhase.Defend;
|
||||
cycle.PhaseEndTick = 0; // Defend is wave-driven, not timed.
|
||||
runtime.DefendStartWave =
|
||||
SystemAPI.TryGetSingleton<WaveState>(out var w) ? w.WaveNumber : 0;
|
||||
}
|
||||
break;
|
||||
// Default calm: no pending siege => no countdown.
|
||||
cycle.PhaseEndTick = 0;
|
||||
|
||||
case CyclePhase.Defend:
|
||||
if (DefendCleared(ref state, runtime.DefendStartWave))
|
||||
if (SystemAPI.HasComponent<ThreatState>(cycleEntity))
|
||||
{
|
||||
var threat = SystemAPI.GetComponent<ThreatState>(cycleEntity);
|
||||
if (threat.PendingSiegeSize > 0)
|
||||
{
|
||||
cycle.Phase = CyclePhase.Build;
|
||||
cycle.PhaseEndTick = TickUtil.NonZero(now + CyclePhase.BuildTicks);
|
||||
}
|
||||
break;
|
||||
// Telegraph: mirror the arm tick into the replicated PhaseEndTick so the HUD can show an
|
||||
// "incursion in Ns" countdown (reuses the existing HUD countdown path) while it arms.
|
||||
cycle.PhaseEndTick = threat.ArmTick;
|
||||
|
||||
case CyclePhase.Build:
|
||||
if (timedPhaseDue)
|
||||
{
|
||||
cycle.Phase = CyclePhase.Expedition;
|
||||
cycle.CycleNumber += 1;
|
||||
cycle.PhaseEndTick = TickUtil.NonZero(now + CyclePhase.ExpeditionTicks);
|
||||
// Long-arc goal: one charge per completed cycle (single writer).
|
||||
if (SystemAPI.HasComponent<GoalProgress>(cycleEntity))
|
||||
bool armed = threat.ArmTick == 0
|
||||
|| !new NetworkTick(threat.ArmTick).IsNewerThan(serverTick);
|
||||
|
||||
if (armed && SystemAPI.TryGetSingletonEntity<WaveState>(out var waveEntity))
|
||||
{
|
||||
var goal = SystemAPI.GetComponent<GoalProgress>(cycleEntity);
|
||||
goal.Charge += 1;
|
||||
SystemAPI.SetComponent(cycleEntity, goal);
|
||||
// ---- Calm -> Siege: seed WaveSystem's own Spawning entry atomically. Writing
|
||||
// Phase=Spawning bypasses its Lull escalation recompute (WaveSystem only recomputes
|
||||
// RemainingToSpawn while Phase==Lull), so the siege spawns EXACTLY the director-chosen
|
||||
// size and WaveSystem stays the sole WaveState writer thereafter. ----
|
||||
var w = SystemAPI.GetComponent<WaveState>(waveEntity);
|
||||
runtime.DefendStartWave = w.WaveNumber; // capture BEFORE the bump (DefendCleared tests > this)
|
||||
w.WaveNumber += 1;
|
||||
w.Phase = WavePhase.Spawning;
|
||||
w.RemainingToSpawn = math.max(1, threat.PendingSiegeSize);
|
||||
w.NextActionTick = TickUtil.NonZero(now); // spawn the first Husk this tick
|
||||
SystemAPI.SetComponent(waveEntity, w);
|
||||
|
||||
cycle.Phase = CyclePhase.Siege;
|
||||
cycle.PhaseEndTick = 0; // Siege is wave-driven, not timed.
|
||||
|
||||
threat.PendingSiegeSize = 0; // consume once
|
||||
threat.ArmTick = 0;
|
||||
SystemAPI.SetComponent(cycleEntity, threat);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (cycle.Phase == CyclePhase.Siege)
|
||||
{
|
||||
if (DefendCleared(ref state, runtime.DefendStartWave))
|
||||
{
|
||||
cycle.Phase = CyclePhase.Calm;
|
||||
cycle.PhaseEndTick = 0;
|
||||
// Long-arc goal: +1 per siege survived (single writer; was +1 per completed timed cycle).
|
||||
if (SystemAPI.HasComponent<GoalProgress>(cycleEntity))
|
||||
{
|
||||
var goal = SystemAPI.GetComponent<GoalProgress>(cycleEntity);
|
||||
goal.Charge += 1;
|
||||
SystemAPI.SetComponent(cycleEntity, goal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Surface the live wave number on the replicated CycleState for the HUD (single writer).
|
||||
@@ -90,8 +111,8 @@ namespace ProjectM.Server
|
||||
SystemAPI.SetComponent(cycleEntity, runtime);
|
||||
}
|
||||
|
||||
// The Defend wave has run for this phase (WaveNumber advanced past the captured start), is fully
|
||||
// spawned, and no Husks remain alive.
|
||||
// The Siege wave has run for this phase (WaveNumber advanced past the captured start), is fully spawned,
|
||||
// and no Husks remain alive.
|
||||
bool DefendCleared(ref SystemState state, int defendStartWave)
|
||||
{
|
||||
if (!SystemAPI.TryGetSingleton<WaveState>(out var wave))
|
||||
|
||||
Reference in New Issue
Block a user