using ProjectM.Simulation; using Unity.Burst; using Unity.Collections; using Unity.Entities; using Unity.NetCode; using Unity.Transforms; namespace ProjectM.Server { /// /// Server-only, one-shot spawner for the GLOBAL cycle-director ghost (mirrors SharedStorageSpawnSystem, /// but MINUS the RegionTag — the director must stay global so GhostRelevancy keeps it relevant to every /// region). On its first update it reads the baked + NetworkTime, /// instantiates the ghost, initializes (Expedition, cycle 1, PhaseEndTick = /// now + ), adds the server-only , and /// places it at the base center (preserving the prefab's baked LocalTransform scale — FromPosition would /// reset the replicated Scale GhostField), then destroys the spawner so it idles. /// [BurstCompile] [WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)] public partial struct CycleDirectorSpawnSystem : ISystem { [BurstCompile] public void OnCreate(ref SystemState state) { state.RequireForUpdate(); state.RequireForUpdate(); } [BurstCompile] public void OnUpdate(ref SystemState state) { var serverTick = SystemAPI.GetSingleton().ServerTick; if (!serverTick.IsValid) return; var spawnerEntity = SystemAPI.GetSingletonEntity(); var spawner = SystemAPI.GetComponent(spawnerEntity); var ecb = new EntityCommandBuffer(Allocator.Temp); if (spawner.Prefab != Entity.Null) { var director = ecb.Instantiate(spawner.Prefab); // Place at the base center, preserving the prefab's baked scale/rotation. var xform = SystemAPI.GetComponent(spawner.Prefab); if (SystemAPI.TryGetSingleton(out var anchor)) xform.Position = BaseGridMath.PlotCenter(anchor); ecb.SetComponent(director, xform); // Boot the run-state in Calm (the persistent default) — no timer; ThreatDirector arms sieges. ecb.SetComponent(director, new CycleState { Phase = CyclePhase.Calm, CycleNumber = 1, PhaseEndTick = 0u, }); ecb.AddComponent(director, new CycleRuntime { DefendStartWave = 0 }); ecb.AddComponent(director, new ThreatState()); // Born-correct load: if the menu staged a save (Continue), apply it AT SPAWN so the director // ghost never serializes a default GoalProgress / empty ledger to clients (no replication flicker). if (SystemAPI.TryGetSingletonEntity(out var pendingEntity)) { var pending = SystemAPI.GetComponent(pendingEntity); if (pending.HasData != 0) { ecb.SetComponent(director, new GoalProgress { Charge = pending.GoalCharge, Target = pending.GoalTarget }); var srcLedger = SystemAPI.GetBuffer(pendingEntity); var destLedger = ecb.SetBuffer(director); SaveApply.WriteLedger(srcLedger, destLedger); } ecb.DestroyEntity(pendingEntity); } // Host-only autosave flag; SaveWriteSystem consumes it on the Siege->Calm checkpoint. ecb.AddComponent(director, new SaveRequest { Pending = 0 }); } // One-shot: remove the spawner so RequireForUpdate fails and the system idles. ecb.DestroyEntity(spawnerEntity); ecb.Playback(state.EntityManager); } } }