Files
Project-M/Assets/_Project/Scripts/Simulation/Persistence/SaveData.cs
T
kronic 73cfe2943d EB-1: machines can die - structures get HP, Husks raze them, wounded base persists
Structures (Turret/Wall/Pylon) reuse the combat spine: authoring bakes Health(GhostField)+DamageEvent buffer+a Destructible tag (no HitRadius -> no friendly projectile fire; no EffectiveCharacterStats -> clamp-to-0). HealthApplyDamageSystem destroys a Destructible at 0 (occupancy auto-frees). EnemyAISystem fortress-targets the weighted-nearest of players+structures via the shared EnemyAIMath.PickWeightedNearest (StructureAggroWeight TuningConfig knob, <1 prefers structures, squared factor; snapshot above the early-return so an undefended base is razed). Persistence v3: per-structure HP threaded through 5 sites (SaveData/PendingStructure/scan-guarded/BaseRestore same-ECB born-correct/WorldLauncher via SaveApply.ToPending); SaveService floor-gate [2,3] loads old saves. Loss feedback: proximity-gated StructureFeedbackSystem; CombatFeedbackSystem suppressed for structures. Pre-code review caught the DamageEvent-buffer crash blocker + 8 majors; post-code review clean. See DR-032.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 23:53:34 -07:00

68 lines
2.8 KiB
C#

using System;
namespace ProjectM.Simulation
{
/// <summary>One serialized ledger row (item id + count). An array FIELD of <see cref="SaveData"/>.</summary>
[Serializable]
public struct LedgerRow
{
public int ItemId;
public int Count;
}
/// <summary>
/// One serialized player-built structure (M7). Flat scalars (JsonUtility has no int2). The production
/// cooldown is stored as REMAINING ticks (epoch-independent) so it survives the server-tick origin reset on a
/// fresh session; the in-flight conveyor item (if any) rides here, while variable-length machine I/O buffers
/// live in the flat <see cref="SaveData.StructureIo"/> table keyed by index.
/// </summary>
[Serializable]
public struct StructureSave
{
public byte Type;
public int CellX;
public int CellZ;
public byte Direction; // conveyor facing (0 for non-conveyors)
public uint RemainingTicks; // production/cooldown ticks left at save time
public byte ConveyorResId; // in-flight conveyor item resource (0 = none)
public int ConveyorCount;
public float HP; // EB-1: hit points at save time (0 from a pre-v3 save -> restored to baked Max)
}
/// <summary>
/// One serialized machine I/O buffer row, joined to <see cref="SaveData.Structures"/> by
/// <see cref="StructureIndex"/>. A flat top-level array (JsonUtility can't nest arrays-of-arrays); Slot 0 =
/// MachineInput, Slot 1 = MachineOutput.
/// </summary>
[Serializable]
public struct StructureIoRow
{
public int StructureIndex;
public byte Slot;
public byte ResourceId;
public int Count;
}
/// <summary>
/// Versioned, host-authoritative save slice (the FOUNDATION): the long-arc goal charge/target + the shared
/// resource ledger. JsonUtility-friendly — a class with flat fields and an array FIELD (never a root array).
/// The schema is intentionally ADDITIVE: future fields (placed structures, threat, storage) append without
/// breaking old saves, gated by <see cref="Version"/> migration.
/// </summary>
[Serializable]
public class SaveData
{
public const int CurrentVersion = 3; // EB-1: v3 adds StructureSave.HP
/// <summary>Oldest save schema the loader accepts (additive); a v2 save loads with structures at full HP.</summary>
public const int MinLoadableVersion = 2;
public int Version = CurrentVersion;
public int GoalCharge;
public int GoalTarget;
public LedgerRow[] Ledger = Array.Empty<LedgerRow>();
public StructureSave[] Structures = Array.Empty<StructureSave>();
public StructureIoRow[] StructureIo = Array.Empty<StructureIoRow>();
public long SavedAtMs;
}
}