Files

220 lines
9.9 KiB
Markdown

# SS_SaveManager — GameInstance Subsystem
**File:** [`Content/Framework/Save/SS_SaveManager`](Content/Framework/Save/SS_SaveManager.uasset)
> **⚡ C++ Status: Full Implementation** — `Source/PG_Framework/Public/Save/SS_SaveManager.h` provides the complete save/load subsystem: slot manifest, save/load/delete, quick-save/load, checkpoint management, backup, FArchive binary serialization. **Auto-created** by UE's subsystem system when `GI_GameFramework` initializes — no BP child, no spawning needed. Access from any BP: `Get Game Instance → Get Subsystem(SS_SaveManager)`. See `docs/developer/cpp-integration-guide.md`.
>
> ---
**Purpose:** The single authority for all serialisation and deserialisation of game state to disk. Supports multiple save slots, checkpoint saves, hard saves, auto-saves, and world object persistence.
**Depends On:** [`GI_GameFramework`](../01-core/04_GI_GameFramework.md), [`I_Persistable`](30_I_Persistable.md)
**Used By:** [`BPC_CheckpointSystem`](31_BPC_CheckpointSystem.md), [`WBP_SaveLoadMenu`](../06-ui/), [`BPC_PlayerRespawnSystem`](31_BPC_CheckpointSystem.md)
---
## Enums
```cpp
// E_SaveType — Categorizes the trigger that initiated a save
E_SaveType
{
Checkpoint, // Auto-triggered by crossing a checkpoint volume
HardSave, // Player-initiated manual save
AutoSave, // Periodic or event-triggered auto save
DeathStateSave // Snapshot created when the player dies (for death loop continuity)
}
```
---
## Structs
```cpp
// S_SaveSlotInfo — Metadata entry for one save slot in the manifest
S_SaveSlotInfo
{
SlotIndex: Integer // 0-based slot number
DisplayName: FText // Player-chosen or auto-generated name
Timestamp: FDateTime // UTC timestamp of last save
ChapterTag: GameplayTag // Which chapter/level was active
ThumbnailBytes: Byte Array // Compressed screenshot for UI preview
PlaytimeSeconds: Float // Total playtime from GI_GameFramework
}
// S_WorldObjectState — Delta record for one persistent world actor
S_WorldObjectState
{
ObjectTag: GameplayTag // Unique identifier matching the actor's tag
bDestroyed: Bool // Was the actor destroyed?
CustomData: Map (Name String) // Generic key-value store for any component state
}
// S_PlayerSnapshot — Complete capture of player state at save time
S_PlayerSnapshot
{
// Core vitals
Health: Float // Current HP
Stress: Float // Current stress level
Stamina: Float // Current stamina pool
// Transform
Position: Vector // World location
Rotation: Rotator // World rotation
// Inventory
InventoryData: Array of S_InventorySlot // From BPC_InventorySystem
QuickSlotData: Array of S_QuickSlotEntry // From BPC_InventoryQuickSlot
EquipmentData: Map (GameplayTag S_EquipmentSlot) // From BPC_EquipmentSystem
// Narrative
NarrativeFlags: Map (GameplayTag Bool) // From BPC_NarrativeStateSystem
NarrativeValues: Map (GameplayTag Float)
// Metrics
DeathCount: Integer // From BPC_PlayerMetricsTracker
ItemsCollected: Integer
DistanceTravelled: Float
// Additional
ActiveObjectiveTags: Array of GameplayTag // From BPC_ObjectiveSystem
HidingState: Bool // Was player hiding?
CurrentHidingSpotTag: GameplayTag
AltDeathSpaceEnterCount: Integer
}
```
---
## Variables
| Name | Type | Description |
|------|------|-------------|
| `SlotManifest` | Array of `S_SaveSlotInfo` | Cached metadata for all available save slots |
| `ActiveSaveObject` | SaveGame Object Reference | The currently loaded save data object in memory |
| `bIsSaving` | Bool | Prevents concurrent save operations |
| `bIsLoading` | Bool | Prevents concurrent load operations |
| `SaveVersion` | Integer | Current schema version for migration detection |
| `WorldStateDelta` | Map (Name → `S_WorldObjectState`) | Live delta of changed world objects since last save |
| `PersistentActors` | Array of Actor Reference | All registered `I_Persistable` actors in the current level |
---
## Functions / Events
| Name | Inputs | Outputs | Description |
|------|--------|---------|-------------|
| `SaveToSlot` | SlotIndex: Int, SaveType: E_SaveType | Bool Success | Orchestrates full save: collects all state, writes SaveGame to disk, updates manifest |
| `LoadFromSlot` | SlotIndex: Int | Bool Success | Reads SaveGame from disk, migrates if needed, distributes state to all systems |
| `DeleteSlot` | SlotIndex: Int | — | Removes save file and manifest entry |
| `GetSlotManifest` | — | Array of `S_SaveSlotInfo` | Returns all slot metadata for UI display |
| `GetSaveFileSize` | SlotIndex: Int | Int64 Bytes | Returns the on-disk file size for the given slot |
| `RegisterPersistableActor` | Actor: Actor Reference | — | Adds an `I_Persistable` actor to the tracked list |
| `UnregisterPersistableActor` | Actor: Actor Reference | — | Removes an `I_Persistable` actor from the tracked list |
| `CollectWorldState` | — | — | Iterates all registered `I_Persistable` actors and calls `CollectState()` |
| `DistributeWorldState` | — | — | Iterates all registered `I_Persistable` actors and calls `RestoreState()` |
| `CollectPlayerSnapshot` | — | `S_PlayerSnapshot` | Gathers player state from all relevant components |
| `DistributePlayerSnapshot` | Snapshot: S_PlayerSnapshot | — | Restores player state to all relevant components |
| `MigrateSaveVersion` | OldVersion: Int, Data: SaveGame Object | SaveGame Object | Upgrades old save schema to current version |
| `SaveCheckpoint` | CheckpointTag: GameplayTag | — | Saves checkpoint data to the active slot without prompting the player |
| `SaveDeathState` | — | — | Saves a special death-state snapshot for death loop continuity |
| `LoadDeathState` | — | Bool Success | Loads the death-state snapshot |
| `HasSaveData` | SlotIndex: Int | Bool | Returns whether a save file exists for the slot |
| `GetActiveSaveSizeInfo` | — | FText | Formatted string with playtime, chapter, timestamp for HUD |
---
## Event Dispatchers
| Name | Parameters | Fired When |
|------|-----------|------------|
| `OnSaveComplete` | SlotIndex: Int, SaveType: E_SaveType | Save operation finishes successfully |
| `OnLoadComplete` | SlotIndex: Int | Load operation finishes successfully |
| `OnSaveFailed` | SlotIndex: Int, ErrorCode: Int | Save fails (disk full, write error, etc.) |
| `OnLoadFailed` | SlotIndex: Int, ErrorCode: Int | Load fails (corrupt file, version mismatch, etc.) |
| `OnWorldStateCollected` | ActorCount: Int | After all `I_Persistable` actors collected state |
| `OnWorldStateDistributed` | ActorCount: Int | After all `I_Persistable` actors restored state |
| `OnSaveVersionMigration` | OldVersion: Int, NewVersion: Int | When a save file is migrated to a newer schema |
---
## Blueprint Flow Diagram
```mermaid
flowchart TD
A[SaveToSlot called] --> B{bIsSaving?}
B -->|Yes| C[Return False]
B -->|No| D[Set bIsSaving = true]
D --> E[CollectPlayerSnapshot]
E --> F[CollectWorldState]
F --> G[Write SaveGame to disk]
G --> H[Update SlotManifest]
H --> I[Set bIsSaving = false]
I --> J[Broadcast OnSaveComplete]
J --> K[Return True]
L[LoadFromSlot called] --> M{bIsLoading?}
M -->|Yes| N[Return False]
M -->|No| O[Set bIsLoading = true]
O --> P[Read SaveGame from disk]
P --> Q{File valid?}
Q -->|No| R[Broadcast OnLoadFailed]
R --> S[Set bIsLoading = false]
S --> T[Return False]
Q -->|Yes| U[Check SaveVersion]
U --> V{Needs migration?}
V -->|Yes| W[MigrateSaveVersion]
W --> X[DistributePlayerSnapshot]
V -->|No| X
X --> Y[DistributeWorldState]
Y --> Z[Set bIsLoading = false]
Z --> AA[Broadcast OnLoadComplete]
AA --> AB[Return True]
```
---
## Save File Structure
```
SaveGame Object (USaveGame-derived)
├── Header
│ ├── SaveVersion: Integer
│ └── SlotInfo: S_SaveSlotInfo
├── Player Snapshot
│ └── S_PlayerSnapshot
├── World State
│ └── WorldStateDelta: Map (Name → S_WorldObjectState)
└── Death State
└── DeathStateData: S_PlayerSnapshot (optional, only in DeathStateSave)
```
---
## Communication Matrix
| Target System | Method | Why |
|---------------|--------|-----|
| All `I_Persistable` actors | Interface call (`CollectState`, `RestoreState`) | World object persistence |
| `BPC_HealthSystem` / Stress / Stamina | Via `S_PlayerSnapshot` | Restore player vitals |
| `BPC_InventorySystem` | Via `S_PlayerSnapshot` | Restore inventory state |
| `BPC_NarrativeStateSystem` | Via `S_PlayerSnapshot` | Restore narrative flags |
| `BPC_PlayerMetricsTracker` | Via `S_PlayerSnapshot` | Restore session metrics |
| `BPC_ObjectiveSystem` | Via `S_PlayerSnapshot` | Restore objective state |
| `BPC_EquipmentSystem` | Via `S_PlayerSnapshot` | Restore equipment state |
| `BPC_InventoryQuickSlot` | Via `S_PlayerSnapshot` | Restore quick slot layout |
| `BPC_CheckpointSystem` | Dispatcher `OnLoadComplete` | Notify checkpoint system to reset |
| `WBP_SaveLoadMenu` | Dispatcher `OnSaveComplete`/`OnLoadComplete` | UI refresh after operation |
| `GI_GameFramework` | Direct (owned by) | Access active slot index, update phase |
| `BPC_DeathHandlingSystem` | Direct call `SaveDeathState`/`LoadDeathState` | Death loop continuity |
---
## Reuse Notes
- Add new fields to `S_PlayerSnapshot` per project without breaking existing saves (migration handles missing fields)
- The `WorldStateDelta` map handles any `I_Persistable` actor generically — no per-actor serialisation code needed
- Save file naming convention: `SaveSlot_{SlotIndex}.sav`
- All disk I/O uses UE5's `UGameplayStatics::SaveGameToSlot` / `LoadGameFromSlot`
- Register/unregister `I_Persistable` actors in `BeginPlay` / `EndPlay` of the actor
- For multiplayer, the `GM_CoreGameMode` should be the Server-authorised caller of `SaveToSlot` / `LoadFromSlot`