# Save Game System

Save/Load game state using UE5 SaveGame framework. C++ patterns for runtime logic, `execute_python` for Blueprint asset creation.

## Create a SaveGame Blueprint (via Python)

To create a SaveGame Blueprint via Python:
1. Discover the Blueprint factory class and asset tools via `query_ue_api`
2. Create factory, set parent_class to SaveGame
3. Create asset, compile, save
4. Use `dir()` to explore factory properties at runtime

## C++ SaveGame Class Pattern

Subclass `USaveGame` and add `UPROPERTY(SaveGame)` fields for any data you need to persist. The struct/property design depends entirely on what your game needs to save.

```cpp
// MySaveGame.h
#pragma once
#include "GameFramework/SaveGame.h"
#include "MySaveGame.generated.h"

UCLASS()
class UMySaveGame : public USaveGame
{
    GENERATED_BODY()

public:
    // Add UPROPERTY(SaveGame) for each field you want to persist.
    // Supported types: primitives, FString, FName, FVector, FRotator,
    // FTransform, FDateTime, TArray, TMap, TSet, USTRUCT.
    // NOT supported: raw UObject* pointers (use FSoftObjectPath or FName references instead).

    UPROPERTY(SaveGame) FString SaveSlotName = TEXT("Slot1");
    UPROPERTY(SaveGame) FDateTime SaveTimestamp;

    // Use USTRUCT sub-structs to organize complex save data.
    // Each struct's fields also need UPROPERTY(SaveGame).
};
```

## Save and Load (synchronous)

```cpp
// SAVE:
UMySaveGame* SaveObj = Cast<UMySaveGame>(
    UGameplayStatics::CreateSaveGameObject(UMySaveGame::StaticClass()));

// Populate save data from your game state here
SaveObj->SaveTimestamp = FDateTime::Now();

bool bSaved = UGameplayStatics::SaveGameToSlot(SaveObj, TEXT("Slot1"), 0);

// LOAD:
if (UGameplayStatics::DoesSaveGameExist(TEXT("Slot1"), 0))
{
    UMySaveGame* LoadedSave = Cast<UMySaveGame>(
        UGameplayStatics::LoadGameFromSlot(TEXT("Slot1"), 0));
    if (LoadedSave)
    {
        // Apply loaded data to your game state here
    }
}
```

## Async Save and Load (for large saves)

```cpp
// ASYNC SAVE:
UGameplayStatics::AsyncSaveGameToSlot(SaveObj, TEXT("Slot1"), 0,
    FAsyncSaveGameToSlotDelegate::CreateLambda([](const FString& SlotName, int32 UserIndex, bool bSuccess)
    {
        UE_LOG(LogTemp, Log, TEXT("Async save to %s: %s"), *SlotName, bSuccess ? TEXT("OK") : TEXT("FAIL"));
    }));

// ASYNC LOAD:
UGameplayStatics::AsyncLoadGameFromSlot(TEXT("Slot1"), 0,
    FAsyncLoadGameFromSlotDelegate::CreateLambda([this](const FString& SlotName, int32 UserIndex, USaveGame* LoadedSave)
    {
        if (UMySaveGame* MySave = Cast<UMySaveGame>(LoadedSave))
        {
            // Apply loaded data
        }
    }));
```

## Save slot management

```cpp
// Delete a save:
UGameplayStatics::DeleteGameInSlot(TEXT("Slot1"), 0);

// Check if save exists:
bool bExists = UGameplayStatics::DoesSaveGameExist(TEXT("Slot1"), 0);

// No built-in list of saves — manage slot names manually via config or directory scan
```

## ISaveable Interface Pattern (for actors)

Use a C++ interface to standardize save/load across any actor type:

```cpp
UINTERFACE(MinimalAPI)
class USaveable : public UInterface { GENERATED_BODY() };

class ISaveable
{
    GENERATED_BODY()
public:
    virtual void SaveState(UMySaveGame* Save) = 0;
    virtual void LoadState(UMySaveGame* Save) = 0;
};

// Iterate all saveable actors in the world:
for (TActorIterator<AActor> It(GetWorld()); It; ++It)
{
    if (ISaveable* Saveable = Cast<ISaveable>(*It))
    {
        Saveable->SaveState(SaveObj);
    }
}
```

## Architecture Approaches

- **Slot-based**: Multiple save slots (Slot1, Slot2, QuickSave, AutoSave)
- **Checkpoint**: Auto-save at specific events or locations
- **Persistent world**: Save/restore per-actor state (transforms, destroyed actors)
- **Profile save**: Separate from game saves — settings, unlocks (saved in a "Profile" slot)
- **Cloud saves**: Use platform-specific APIs (Steam, Epic, console) alongside local saves

## Critical Rules

- Always mark UPROPERTY with `SaveGame` specifier for serialized fields
- Use async save/load for large save files to avoid frame hitches
- Always check `DoesSaveGameExist()` before loading
- Cast result of `LoadGameFromSlot()` — it returns USaveGame base pointer
- SaveGame does NOT support raw UObject pointers — use soft references or FName paths instead
- Use `query_ue_api` to discover SaveGame-related methods on GameplayStatics
- Use `dir()` on unreal classes at runtime to discover available properties and methods
