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:
Lefteris Notas
2026-05-20 15:04:17 +03:00
parent fee12b115f
commit f6c4f44827
24 changed files with 5160 additions and 0 deletions

View 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;
}