Frontend menu + settings + saves foundation
Netcode frontend pattern: UITK main menu / pause / settings (MenuUi + controllers), on-demand world lifecycle (WorldLauncher/SessionRunner), GameBootstrap menu branch; Graphics/Audio settings (SettingsService/GameVolume); single-slot save foundation (SaveData/SaveService, born-correct load at director spawn, autosave on Siege->Calm + quit); RuntimePanelSettings + theme; BuildTool menu; 10 EditMode tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using ProjectM.Simulation;
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
|
||||
namespace ProjectM.Server
|
||||
{
|
||||
/// <summary>
|
||||
/// Host-only autosave writer. A managed <see cref="SystemBase"/> (file IO => NO Burst) that reacts to the
|
||||
/// <see cref="SaveRequest"/> flag the Bursted <c>CyclePhaseSystem</c> raises on the Siege->Calm checkpoint:
|
||||
/// reads the authoritative <see cref="GoalProgress"/> + shared resource ledger off the CycleDirector ghost,
|
||||
/// writes the JSON save (<see cref="SaveService"/>), then clears the flag. ServerSimulation-only, so a pure
|
||||
/// (Join) client never writes. Deliberately carries NO <c>[UpdateAfter(CyclePhaseSystem)]</c> (that would risk
|
||||
/// a sort-cycle); a one-tick-late autosave is irrelevant.
|
||||
/// </summary>
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
public partial class SaveWriteSystem : SystemBase
|
||||
{
|
||||
protected override void OnCreate()
|
||||
{
|
||||
RequireForUpdate<SaveRequest>();
|
||||
RequireForUpdate<NetworkTime>();
|
||||
}
|
||||
|
||||
protected override void OnUpdate()
|
||||
{
|
||||
var dir = SystemAPI.GetSingletonEntity<SaveRequest>();
|
||||
var req = SystemAPI.GetComponent<SaveRequest>(dir);
|
||||
if (req.Pending == 0)
|
||||
return;
|
||||
|
||||
req.Pending = 0;
|
||||
SystemAPI.SetComponent(dir, req);
|
||||
|
||||
var goal = SystemAPI.HasComponent<GoalProgress>(dir)
|
||||
? SystemAPI.GetComponent<GoalProgress>(dir)
|
||||
: default;
|
||||
|
||||
// The shared ledger lives on this same CycleDirector ghost (ResourceLedger-tagged StorageEntry buffer).
|
||||
var buffer = SystemAPI.GetBuffer<StorageEntry>(dir);
|
||||
var rows = new LedgerRow[buffer.Length];
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
rows[i] = new LedgerRow { ItemId = buffer[i].ItemId, Count = buffer[i].Count };
|
||||
|
||||
// M7: also persist player-built structures + their production tick-state / inventory (single shared scan).
|
||||
uint nowTick = SystemAPI.GetSingleton<NetworkTime>().ServerTick.TickIndexForValidTick;
|
||||
SaveStructureScan.Collect(EntityManager, nowTick, out var structures, out var structureIo);
|
||||
|
||||
SaveService.Save(new SaveData
|
||||
{
|
||||
GoalCharge = goal.Charge,
|
||||
GoalTarget = goal.Target,
|
||||
Ledger = rows,
|
||||
Structures = structures,
|
||||
StructureIo = structureIo,
|
||||
SavedAtMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user