Core Game Loop Additions
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Burst;
|
||||
using Unity.Entities;
|
||||
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.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
[UpdateInGroup(typeof(SimulationSystemGroup))]
|
||||
[UpdateBefore(typeof(WaveSystem))]
|
||||
public partial struct CyclePhaseSystem : ISystem
|
||||
{
|
||||
EntityQuery m_AliveHusks;
|
||||
|
||||
[BurstCompile]
|
||||
public void OnCreate(ref SystemState state)
|
||||
{
|
||||
state.RequireForUpdate<NetworkTime>();
|
||||
state.RequireForUpdate<CycleState>();
|
||||
m_AliveHusks = state.GetEntityQuery(ComponentType.ReadOnly<EnemyTag>());
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
var serverTick = SystemAPI.GetSingleton<NetworkTime>().ServerTick;
|
||||
if (!serverTick.IsValid)
|
||||
return;
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
||||
case CyclePhase.Defend:
|
||||
if (DefendCleared(ref state, runtime.DefendStartWave))
|
||||
{
|
||||
cycle.Phase = CyclePhase.Build;
|
||||
cycle.PhaseEndTick = TickUtil.NonZero(now + CyclePhase.BuildTicks);
|
||||
}
|
||||
break;
|
||||
|
||||
case CyclePhase.Build:
|
||||
if (timedPhaseDue)
|
||||
{
|
||||
cycle.Phase = CyclePhase.Expedition;
|
||||
cycle.CycleNumber += 1;
|
||||
cycle.PhaseEndTick = TickUtil.NonZero(now + CyclePhase.ExpeditionTicks);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
SystemAPI.SetComponent(cycleEntity, cycle);
|
||||
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.
|
||||
bool DefendCleared(ref SystemState state, int defendStartWave)
|
||||
{
|
||||
if (!SystemAPI.TryGetSingleton<WaveState>(out var wave))
|
||||
return false;
|
||||
return wave.WaveNumber > defendStartWave
|
||||
&& wave.RemainingToSpawn == 0
|
||||
&& m_AliveHusks.CalculateEntityCount() == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user