- Implemented DA_EquipmentConfig for managing equipment resistances, durability, and weight. - Created DA_ItemData to serve as a base item data asset with various item types and properties. - Introduced BPC_HealthSystem for managing player health and death events. - Added BPC_MovementStateSystem to handle player movement modes with event delegation. - Developed BPC_StaminaSystem to track player stamina and exhaustion states. - Established BPC_StateManager as a central authority for managing player action states and gating. - Created BPC_StressSystem to monitor and respond to player stress levels. - Implemented PC_CoreController and PS_CorePlayerState for player controller and state management. - Developed SS_SaveManager for save/load functionality with slot management and serialization. - Introduced DA_StateGatingTable for defining action gating rules based on gameplay tags. - Added BPC_DamageReceptionSystem to process incoming damage and apply resistance calculations. - Implemented BPC_HitReactionSystem for managing hit reactions based on damage received. - Created BPC_ShieldDefenseSystem to manage shield health and blocking mechanics. - Added PG_FrameworkEditor.Target.cs for editor build configuration.
373 lines
10 KiB
C++
373 lines
10 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
// UE5 Modular Game Framework — SS_SaveManager Implementation
|
|
|
|
#include "Save/SS_SaveManager.h"
|
|
#include "Core/GI_GameFramework.h"
|
|
#include "Misc/FileHelper.h"
|
|
#include "Misc/Paths.h"
|
|
#include "HAL/PlatformFileManager.h"
|
|
#include "Serialization/MemoryReader.h"
|
|
#include "Serialization/MemoryWriter.h"
|
|
#include "Serialization/ObjectAndNameAsStringProxyArchive.h"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogSave, Log, All);
|
|
|
|
USS_SaveManager::USS_SaveManager()
|
|
{
|
|
MaxSlots = 10;
|
|
SavePrefix = TEXT("FrameworkSave_");
|
|
}
|
|
|
|
// ============================================================================
|
|
// Lifecycle
|
|
// ============================================================================
|
|
|
|
void USS_SaveManager::Initialize(FSubsystemCollectionBase& Collection)
|
|
{
|
|
Super::Initialize(Collection);
|
|
|
|
UE_LOG(LogSave, Log, TEXT("SS_SaveManager::Initialize — Save directory: %s"), *GetSaveDirectory());
|
|
|
|
// Ensure save directory exists.
|
|
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
|
|
if (!PlatformFile.DirectoryExists(*GetSaveDirectory()))
|
|
{
|
|
PlatformFile.CreateDirectoryTree(*GetSaveDirectory());
|
|
}
|
|
|
|
// Broadcast initial manifest.
|
|
OnSaveManifestUpdated.Broadcast(GetSlotManifest());
|
|
}
|
|
|
|
void USS_SaveManager::Deinitialize()
|
|
{
|
|
UE_LOG(LogSave, Log, TEXT("SS_SaveManager::Deinitialize"));
|
|
Super::Deinitialize();
|
|
}
|
|
|
|
// ============================================================================
|
|
// Slot Manifest
|
|
// ============================================================================
|
|
|
|
TArray<FSaveSlotInfo> USS_SaveManager::GetSlotManifest() const
|
|
{
|
|
TArray<FSaveSlotInfo> Manifest;
|
|
|
|
for (int32 i = 0; i < MaxSlots; ++i)
|
|
{
|
|
FSaveSlotInfo Info = ReadSlotHeader(i);
|
|
Info.SlotIndex = i;
|
|
Manifest.Add(Info);
|
|
}
|
|
|
|
return Manifest;
|
|
}
|
|
|
|
bool USS_SaveManager::DoesSlotExist(int32 SlotIndex) const
|
|
{
|
|
if (SlotIndex < 0 || SlotIndex >= MaxSlots)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FString FilePath = GetSaveDirectory() / GetSlotName(SlotIndex) + TEXT(".sav");
|
|
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
|
|
return PlatformFile.FileExists(*FilePath);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Save / Load Operations
|
|
// ============================================================================
|
|
|
|
bool USS_SaveManager::SaveGame(int32 SlotIndex, const FString& Description)
|
|
{
|
|
if (SlotIndex < 0 || SlotIndex >= MaxSlots)
|
|
{
|
|
UE_LOG(LogSave, Warning, TEXT("SaveGame — Invalid slot index: %d"), SlotIndex);
|
|
return false;
|
|
}
|
|
|
|
UE_LOG(LogSave, Log, TEXT("SaveGame — Slot %d: '%s'"), SlotIndex, *Description);
|
|
|
|
// Build metadata.
|
|
FSaveSlotInfo Meta;
|
|
Meta.SlotIndex = SlotIndex;
|
|
Meta.SlotName = FString::Printf(TEXT("Slot %d"), SlotIndex);
|
|
Meta.ChapterName = Description; // Simplified — real impl gets chapter from GS_CoreGameState.
|
|
Meta.Timestamp = FDateTime::Now();
|
|
|
|
// Serialize game state to binary buffer.
|
|
// In a full implementation, this calls I_Persistable::OnSave() on all registered actors.
|
|
TArray<uint8> SaveData;
|
|
FMemoryWriter Writer(SaveData);
|
|
FObjectAndNameAsStringProxyArchive Ar(Writer, true);
|
|
// Ar << GameStateData...
|
|
|
|
bool bSuccess = SaveToFile(SlotIndex, SaveData, Meta);
|
|
|
|
OnSaveComplete.Broadcast(SlotIndex, bSuccess);
|
|
OnSaveManifestUpdated.Broadcast(GetSlotManifest());
|
|
|
|
return bSuccess;
|
|
}
|
|
|
|
bool USS_SaveManager::LoadGame(int32 SlotIndex)
|
|
{
|
|
if (!DoesSlotExist(SlotIndex))
|
|
{
|
|
UE_LOG(LogSave, Warning, TEXT("LoadGame — Slot %d does not exist"), SlotIndex);
|
|
OnLoadComplete.Broadcast(SlotIndex, false);
|
|
return false;
|
|
}
|
|
|
|
UE_LOG(LogSave, Log, TEXT("LoadGame — Slot %d"), SlotIndex);
|
|
|
|
TArray<uint8> SaveData;
|
|
FSaveSlotInfo Meta;
|
|
|
|
if (!LoadFromFile(SlotIndex, SaveData, Meta))
|
|
{
|
|
UE_LOG(LogSave, Error, TEXT("LoadGame — Failed to read slot %d from disk"), SlotIndex);
|
|
OnLoadComplete.Broadcast(SlotIndex, false);
|
|
return false;
|
|
}
|
|
|
|
// Deserialize game state.
|
|
FMemoryReader Reader(SaveData);
|
|
FObjectAndNameAsStringProxyArchive Ar(Reader, true);
|
|
// Ar << GameStateData...
|
|
|
|
// Set active slot in GameInstance.
|
|
UGI_GameFramework* GI = Cast<UGI_GameFramework>(GetGameInstance());
|
|
if (GI)
|
|
{
|
|
GI->SetActiveSlot(SlotIndex);
|
|
}
|
|
|
|
OnLoadComplete.Broadcast(SlotIndex, true);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool USS_SaveManager::DeleteSlot(int32 SlotIndex)
|
|
{
|
|
if (!DoesSlotExist(SlotIndex))
|
|
{
|
|
UE_LOG(LogSave, Warning, TEXT("DeleteSlot — Slot %d does not exist"), SlotIndex);
|
|
return false;
|
|
}
|
|
|
|
FString FilePath = GetSaveDirectory() / GetSlotName(SlotIndex) + TEXT(".sav");
|
|
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
|
|
|
|
bool bDeleted = PlatformFile.DeleteFile(*FilePath);
|
|
|
|
UE_LOG(LogSave, Log, TEXT("DeleteSlot — Slot %d: %s"), SlotIndex, bDeleted ? TEXT("Deleted") : TEXT("Failed"));
|
|
|
|
OnSaveManifestUpdated.Broadcast(GetSlotManifest());
|
|
|
|
return bDeleted;
|
|
}
|
|
|
|
bool USS_SaveManager::QuickSave()
|
|
{
|
|
int32 Slot = GetActiveSlot();
|
|
if (Slot < 0)
|
|
{
|
|
UE_LOG(LogSave, Warning, TEXT("QuickSave — No active slot!"));
|
|
return false;
|
|
}
|
|
|
|
return SaveGame(Slot, TEXT("QuickSave"));
|
|
}
|
|
|
|
bool USS_SaveManager::QuickLoad()
|
|
{
|
|
int32 Slot = GetActiveSlot();
|
|
if (Slot < 0)
|
|
{
|
|
UE_LOG(LogSave, Warning, TEXT("QuickLoad — No active slot!"));
|
|
return false;
|
|
}
|
|
|
|
return LoadGame(Slot);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Checkpoint Management
|
|
// ============================================================================
|
|
|
|
bool USS_SaveManager::LoadCheckpoint(int32 SlotIndex)
|
|
{
|
|
// Checkpoints are incremental saves within a slot.
|
|
// For now, loads the full slot (same as LoadGame).
|
|
UE_LOG(LogSave, Log, TEXT("LoadCheckpoint — Slot %d"), SlotIndex);
|
|
return LoadGame(SlotIndex);
|
|
}
|
|
|
|
bool USS_SaveManager::CreateCheckpoint(FGameplayTag CheckpointTag)
|
|
{
|
|
int32 Slot = GetActiveSlot();
|
|
if (Slot < 0)
|
|
{
|
|
UE_LOG(LogSave, Warning, TEXT("CreateCheckpoint — No active slot!"));
|
|
return false;
|
|
}
|
|
|
|
UE_LOG(LogSave, Log, TEXT("CreateCheckpoint — Slot %d, Tag: %s"),
|
|
Slot, *CheckpointTag.GetTagName().ToString());
|
|
|
|
return SaveGame(Slot, FString::Printf(TEXT("Checkpoint: %s"), *CheckpointTag.GetTagName().ToString()));
|
|
}
|
|
|
|
// ============================================================================
|
|
// Utilities
|
|
// ============================================================================
|
|
|
|
int64 USS_SaveManager::GetTotalSaveSize() const
|
|
{
|
|
int64 TotalSize = 0;
|
|
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
|
|
|
|
for (int32 i = 0; i < MaxSlots; ++i)
|
|
{
|
|
FString FilePath = GetSaveDirectory() / GetSlotName(i) + TEXT(".sav");
|
|
if (PlatformFile.FileExists(*FilePath))
|
|
{
|
|
TotalSize += PlatformFile.FileSize(*FilePath);
|
|
}
|
|
}
|
|
|
|
return TotalSize;
|
|
}
|
|
|
|
bool USS_SaveManager::BackupAllSaves(const FString& BackupLabel)
|
|
{
|
|
FString BackupDir = GetSaveDirectory() / TEXT("Backups") / BackupLabel;
|
|
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
|
|
|
|
if (!PlatformFile.DirectoryExists(*BackupDir))
|
|
{
|
|
PlatformFile.CreateDirectoryTree(*BackupDir);
|
|
}
|
|
|
|
int32 BackedUp = 0;
|
|
for (int32 i = 0; i < MaxSlots; ++i)
|
|
{
|
|
FString SrcPath = GetSaveDirectory() / GetSlotName(i) + TEXT(".sav");
|
|
FString DstPath = BackupDir / GetSlotName(i) + TEXT(".sav");
|
|
|
|
if (PlatformFile.FileExists(*SrcPath))
|
|
{
|
|
if (PlatformFile.CopyFile(*DstPath, *SrcPath))
|
|
{
|
|
++BackedUp;
|
|
}
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogSave, Log, TEXT("BackupAllSaves — '%s': %d slots backed up"), *BackupLabel, BackedUp);
|
|
return BackedUp > 0;
|
|
}
|
|
|
|
// ============================================================================
|
|
// Internal
|
|
// ============================================================================
|
|
|
|
FString USS_SaveManager::GetSlotName(int32 SlotIndex) const
|
|
{
|
|
return FString::Printf(TEXT("%s%d"), *SavePrefix, SlotIndex);
|
|
}
|
|
|
|
FString USS_SaveManager::GetSaveDirectory() const
|
|
{
|
|
return FPaths::ProjectSavedDir() / TEXT("SaveGames");
|
|
}
|
|
|
|
int32 USS_SaveManager::GetActiveSlot() const
|
|
{
|
|
UGI_GameFramework* GI = Cast<UGI_GameFramework>(GetGameInstance());
|
|
return GI ? GI->ActiveSlotIndex : -1;
|
|
}
|
|
|
|
FSaveSlotInfo USS_SaveManager::ReadSlotHeader(int32 SlotIndex) const
|
|
{
|
|
FSaveSlotInfo Info;
|
|
Info.SlotIndex = SlotIndex;
|
|
|
|
FString FilePath = GetSaveDirectory() / GetSlotName(SlotIndex) + TEXT(".sav");
|
|
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
|
|
|
|
if (!PlatformFile.FileExists(*FilePath))
|
|
{
|
|
Info.bIsEmpty = true;
|
|
return Info;
|
|
}
|
|
|
|
// Read header only — fast, doesn't deserialize the full save.
|
|
TArray<uint8> FileData;
|
|
if (FFileHelper::LoadFileToArray(FileData, *FilePath))
|
|
{
|
|
// Simplified header parsing — real impl reads a FSaveSlotInfo struct prefix.
|
|
Info.bIsEmpty = false;
|
|
Info.SlotName = FString::Printf(TEXT("Slot %d"), SlotIndex);
|
|
Info.Timestamp = PlatformFile.GetTimeStamp(*FilePath);
|
|
// In full impl: deserialize header from FileData.
|
|
}
|
|
|
|
return Info;
|
|
}
|
|
|
|
bool USS_SaveManager::SaveToFile(int32 SlotIndex, const TArray<uint8>& Data, const FSaveSlotInfo& Meta)
|
|
{
|
|
FString FilePath = GetSaveDirectory() / GetSlotName(SlotIndex) + TEXT(".sav");
|
|
|
|
// Serialize metadata header + save data.
|
|
TArray<uint8> FileData;
|
|
FMemoryWriter Writer(FileData);
|
|
|
|
// Write metadata header first.
|
|
FSaveSlotInfo& MetaRef = const_cast<FSaveSlotInfo&>(Meta);
|
|
FSaveSlotInfo::StaticStruct()->SerializeItem(Writer, &MetaRef, nullptr);
|
|
|
|
// Then write game state data.
|
|
Writer.Serialize(const_cast<uint8*>(Data.GetData()), Data.Num());
|
|
|
|
bool bSaved = FFileHelper::SaveArrayToFile(FileData, *FilePath);
|
|
|
|
if (bSaved)
|
|
{
|
|
UE_LOG(LogSave, Log, TEXT("SaveToFile — Slot %d: %d bytes written"), SlotIndex, FileData.Num());
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogSave, Error, TEXT("SaveToFile — Slot %d: FAILED to write!"), SlotIndex);
|
|
}
|
|
|
|
return bSaved;
|
|
}
|
|
|
|
bool USS_SaveManager::LoadFromFile(int32 SlotIndex, TArray<uint8>& OutData, FSaveSlotInfo& OutMeta)
|
|
{
|
|
FString FilePath = GetSaveDirectory() / GetSlotName(SlotIndex) + TEXT(".sav");
|
|
|
|
TArray<uint8> FileData;
|
|
if (!FFileHelper::LoadFileToArray(FileData, *FilePath))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Deserialize metadata header.
|
|
FMemoryReader Reader(FileData);
|
|
FSaveSlotInfo::StaticStruct()->SerializeItem(Reader, &OutMeta, nullptr);
|
|
|
|
// Remaining bytes are game state data.
|
|
int32 HeaderSize = Reader.Tell();
|
|
OutData.SetNum(FileData.Num() - HeaderSize);
|
|
FMemory::Memcpy(OutData.GetData(), FileData.GetData() + HeaderSize, OutData.Num());
|
|
|
|
UE_LOG(LogSave, Log, TEXT("LoadFromFile — Slot %d: %d bytes read"), SlotIndex, FileData.Num());
|
|
|
|
return true;
|
|
}
|