using ProjectM.Simulation; using Unity.Burst; using Unity.Collections; using Unity.Entities; using Unity.Mathematics; using Unity.NetCode; using Unity.Transforms; namespace ProjectM.Server { /// /// One-shot server restore of player-built structures for a "Continue" session. The menu (WorldLauncher) stages a /// / carrier in the fresh ServerWorld BEFORE the /// gameplay subscene streams; this system waits (RequireForUpdate) for the streamed /// + + a valid NetworkTime, then replays each saved structure CHARGE-FREE: Instantiate the /// catalog prefab at the saved cell (preserving the baked Scale), re-stamp the rebased tick fields /// (; LastProcessed = now so within-session catch-up resumes from now, /// never a wall-clock mint), re-tag RegionTag{Base} + RuntimePlacedTag, refill the in-flight conveyor item + the /// machine I/O buffers, then DESTROY the carrier so it never runs again. The ledger/goal restore separately + /// absolutely via CycleDirectorSpawnSystem's born-correct load (no double-spend, no Withdraw here). /// [BurstCompile] [WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)] public partial struct BaseRestoreSystem : ISystem { ComponentLookup m_TransformLookup; ComponentLookup m_ConveyorLookup; [BurstCompile] public void OnCreate(ref SystemState state) { m_TransformLookup = state.GetComponentLookup(isReadOnly: true); m_ConveyorLookup = state.GetComponentLookup(isReadOnly: true); state.RequireForUpdate(); state.RequireForUpdate(); state.RequireForUpdate(); state.RequireForUpdate(state.GetEntityQuery(ComponentType.ReadOnly())); } [BurstCompile] public void OnUpdate(ref SystemState state) { var serverTick = SystemAPI.GetSingleton().ServerTick; if (!serverTick.IsValid) return; uint now = serverTick.TickIndexForValidTick; m_TransformLookup.Update(ref state); m_ConveyorLookup.Update(ref state); var anchor = SystemAPI.GetSingleton(); var catalog = SystemAPI.GetBuffer(SystemAPI.GetSingletonEntity()); var ecb = new EntityCommandBuffer(Allocator.Temp); foreach (var (pending, ioBuf, carrier) in SystemAPI.Query, DynamicBuffer>().WithEntityAccess()) { for (int s = 0; s < pending.Length; s++) { var p = pending[s]; int entryIdx = -1; for (int i = 0; i < catalog.Length; i++) if (catalog[i].Type == p.Type) { entryIdx = i; break; } if (entryIdx < 0 || catalog[entryIdx].Prefab == Entity.Null) continue; // type not in the catalog (e.g. a save from a newer build) -> skip, don't crash var prefab = catalog[entryIdx].Prefab; var structure = ecb.Instantiate(prefab); int2 cell = new int2(p.CellX, p.CellZ); var xform = m_TransformLookup[prefab]; xform.Position = BaseGridMath.CellToWorld(anchor, cell); // preserve baked Scale (FromPosition would reset it) ecb.SetComponent(structure, xform); ecb.SetComponent(structure, new PlacedStructure { Type = p.Type, Cell = cell, NextTick = ProductionMath.RestoreNextTick(now, p.RemainingTicks), LastProcessedTick = TickUtil.NonZero(now), }); ecb.AddComponent(structure, new RegionTag { Region = RegionId.Base }); ecb.AddComponent(structure); if (p.Type == StructureType.Conveyor && m_ConveyorLookup.HasComponent(prefab)) { var conv = m_ConveyorLookup[prefab]; conv.Direction = p.Direction; ecb.SetComponent(structure, conv); ecb.SetComponent(structure, new ConveyorItem { ResourceId = p.ConveyorResId, Count = p.ConveyorCount }); ecb.SetComponentEnabled(structure, p.ConveyorCount > 0); } // Refill machine I/O buffers from the flat io table (only slots with saved rows -> the prefab has them). bool inInit = false, outInit = false; DynamicBuffer inBuf = default; DynamicBuffer outBuf = default; for (int r = 0; r < ioBuf.Length; r++) { if (ioBuf[r].StructureIndex != s) continue; if (ioBuf[r].Slot == 0) { if (!inInit) { inBuf = ecb.SetBuffer(structure); inInit = true; } inBuf.Add(new MachineInput { ResourceId = ioBuf[r].ResourceId, Count = ioBuf[r].Count }); } else { if (!outInit) { outBuf = ecb.SetBuffer(structure); outInit = true; } outBuf.Add(new MachineOutput { ResourceId = ioBuf[r].ResourceId, Count = ioBuf[r].Count }); } } } ecb.DestroyEntity(carrier); } ecb.Playback(state.EntityManager); } } }