feat: Add Enhanced Input Manager for context management and key rebinding
- Implemented USS_EnhancedInputManager to manage input contexts with priority. - Added methods for pushing, popping, and querying input contexts. - Integrated input mode switching and key rebinding functionality. feat: Introduce Inventory System Component for item management - Created UBPC_InventorySystem to handle inventory operations such as adding, removing, and sorting items. - Implemented weight management and slot organization features. - Added event dispatchers for inventory changes. feat: Develop Item Data Asset for item definitions - Established UDA_ItemData as a base class for all items, encapsulating properties like type, weight, and stack limits. - Included conditional sub-data structures for equipment, consumables, and inspect data. feat: Create State Manager Component for player state management - Developed UBPC_StateManager to manage player action states and overlays. - Implemented gating logic for action requests and vital sign tracking. feat: Implement Save Manager for game state persistence - Introduced USS_SaveManager for handling save/load operations and slot management. - Utilized FArchive for efficient binary serialization. feat: Implement Damage Reception System for combat mechanics - Created UBPC_DamageReceptionSystem to process incoming damage and apply resistance calculations. - Added event dispatchers for damage reception and hit reactions.
This commit is contained in:
371
Source/Framework/Private/Save/SS_SaveManager.cpp
Normal file
371
Source/Framework/Private/Save/SS_SaveManager.cpp
Normal file
@@ -0,0 +1,371 @@
|
||||
// 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.
|
||||
Writer << const_cast<FSaveSlotInfo&>(Meta);
|
||||
|
||||
// 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);
|
||||
Reader << OutMeta;
|
||||
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user