60e1e21dd3
Adds CoreIntegrity{[GhostField] Current,Max,OverrunTick} on the GLOBAL
CycleDirector ghost (no new ghost/relevancy). CoreDamageSystem (server,
after EnemyAISystem): a Husk within ~3u of PlotCenter drains + is consumed;
CoreRestoreSystem regenerates only in Calm. The SOFT-loss edge lives inside
CyclePhaseSystem (sole Phase writer): Current<=0 in Siege flips to Calm with
NO goal reward, StorageMath.DrainFraction drains the shared ledger, all Husks
despawn, and OverrunTick is stamped (a transient HUD-flash pulse, not a
latching outcome - the Victory latch is END-2's). EnemyAISystem treats the
Core as a FALLBACK target so an undefended base is overrun instead of idling.
SaveData -> v4 persists CoreCurrent (0 -> born full, the EB-1 HP sentinel);
3 live TuningConfig knobs + a red HUD Core bar. Soft-loss + targeting +
breach-resolution forks operator-locked.
See DR-034.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
69 lines
2.8 KiB
C#
69 lines
2.8 KiB
C#
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;
|
|
|
|
// END-1: persist the Engine Core integrity (a wounded base stays wounded across save/quit).
|
|
var core = SystemAPI.HasComponent<CoreIntegrity>(dir)
|
|
? SystemAPI.GetComponent<CoreIntegrity>(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,
|
|
CoreCurrent = core.Current,
|
|
|
|
Ledger = rows,
|
|
Structures = structures,
|
|
StructureIo = structureIo,
|
|
SavedAtMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
|
|
});
|
|
}
|
|
}
|
|
}
|