using ProjectM.Simulation;
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.NetCode;
namespace ProjectM.Server
{
///
/// END-2 — arms the FINAL siege when the long-arc goal meter fills. Server-only, plain
/// , [UpdateAfter(CyclePhaseSystem)] so it reads
/// AFTER the survived-siege increment that may have just reached Target.
/// On the Charge >= Target rising edge — guarded by +
/// so it fires EXACTLY once — it:
///
/// - arms a bigger siege through the existing single entry point :
/// the would-be-next normal siege size (SizeBase + ScheduleSizePerWave*wave) times the live
/// (floored at 1 so the final siege is never smaller), telegraphed
/// via (wrap-safe );
/// - flips to .
///
/// It NEVER writes .Phase / WaveState (CyclePhaseSystem stays the sole writer) nor
/// .Charge (CyclePhaseSystem clamps it at the increment site) — it only READS the edge.
/// CyclePhaseSystem then consumes the next tick exactly like any other
/// armed siege; ThreatDirectorSystem stops arming once leaves Normal, so no normal
/// siege can stomp the final one. Plain server group => one run per tick, no rollback/predicted exposure.
/// Bytes, never enums (Burst-safe).
///
[BurstCompile]
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
[UpdateInGroup(typeof(SimulationSystemGroup))]
[UpdateAfter(typeof(CyclePhaseSystem))]
public partial struct GoalReachedSystem : ISystem
{
[BurstCompile]
public void OnCreate(ref SystemState state)
{
state.RequireForUpdate();
state.RequireForUpdate();
state.RequireForUpdate();
}
[BurstCompile]
public void OnUpdate(ref SystemState state)
{
var serverTick = SystemAPI.GetSingleton().ServerTick;
if (!serverTick.IsValid)
return;
uint now = serverTick.TickIndexForValidTick;
var cycleEntity = SystemAPI.GetSingletonEntity();
// Exactly-once guards: a decided run, or one already in the final siege, arms nothing.
if (SystemAPI.HasComponent(cycleEntity)
&& SystemAPI.GetComponent(cycleEntity).Value != RunOutcomeId.InProgress)
return;
var runPhase = SystemAPI.GetComponent(cycleEntity);
if (runPhase.Value != RunPhaseId.Normal)
return;
// Goal cap reached? (Charge is clamped to Target at the CyclePhaseSystem increment site.)
if (!SystemAPI.HasComponent(cycleEntity))
return;
var goal = SystemAPI.GetComponent(cycleEntity);
if (goal.Target <= 0 || goal.Charge < goal.Target)
return;
if (!SystemAPI.HasComponent(cycleEntity) || !SystemAPI.HasComponent(cycleEntity))
return;
var threat = SystemAPI.GetComponent(cycleEntity);
var config = SystemAPI.GetComponent(cycleEntity);
int wave = SystemAPI.TryGetSingleton(out var ws) ? ws.WaveNumber : 0;
float mult = math.max(1f, SystemAPI.TryGetSingleton(out var tcfg)
? tcfg.FinalSiegeMultiplier
: TuningConfig.Defaults().FinalSiegeMultiplier);
int normalSize = config.SizeBase + config.ScheduleSizePerWave * wave;
int finalSize = math.max(1, (int)(normalSize * mult));
// Arm the final siege (overwrites any pending normal siege — the final supersedes; at the goal-reach tick
// PendingSiegeSize is 0 anyway, the just-cleared siege having consumed it). CyclePhaseSystem consumes it.
threat.PendingSiegeSize = finalSize;
threat.ArmTick = TickUtil.NonZero(now + config.PostExpeditionDelayTicks);
SystemAPI.SetComponent(cycleEntity, threat);
runPhase.Value = RunPhaseId.FinalDefense;
SystemAPI.SetComponent(cycleEntity, runPhase);
}
}
}