Add MVP reconnect snapshots

This commit is contained in:
2026-05-18 15:23:01 -07:00
parent 3b772da73c
commit 0b9a8b7b30
7 changed files with 208 additions and 49 deletions
@@ -1,11 +1,37 @@
// Copyright Epic Games, Inc. All Rights Reserved.
#include "AgrarianGameGameMode.h"
#include "AgrarianGameCharacter.h"
#include "AgrarianDebugHUD.h"
#include "AgrarianGameState.h"
#include "AgrarianPersistenceSubsystem.h"
AAgrarianGameGameMode::AAgrarianGameGameMode()
{
GameStateClass = AAgrarianGameState::StaticClass();
HUDClass = AAgrarianDebugHUD::StaticClass();
}
void AAgrarianGameGameMode::RestartPlayer(AController* NewPlayer)
{
Super::RestartPlayer(NewPlayer);
AAgrarianGameCharacter* AgrarianCharacter = NewPlayer ? Cast<AAgrarianGameCharacter>(NewPlayer->GetPawn()) : nullptr;
UAgrarianPersistenceSubsystem* Persistence = GetGameInstance() ? GetGameInstance()->GetSubsystem<UAgrarianPersistenceSubsystem>() : nullptr;
if (AgrarianCharacter && Persistence && Persistence->RestorePlayerSnapshot(AgrarianCharacter))
{
UE_LOG(LogTemp, Log, TEXT("Agrarian restored reconnect snapshot for %s."), *AgrarianCharacter->GetName());
}
}
void AAgrarianGameGameMode::Logout(AController* Exiting)
{
AAgrarianGameCharacter* AgrarianCharacter = Exiting ? Cast<AAgrarianGameCharacter>(Exiting->GetPawn()) : nullptr;
UAgrarianPersistenceSubsystem* Persistence = GetGameInstance() ? GetGameInstance()->GetSubsystem<UAgrarianPersistenceSubsystem>() : nullptr;
if (AgrarianCharacter && Persistence)
{
Persistence->SavePlayerSnapshot(AgrarianCharacter);
}
Super::Logout(Exiting);
}
@@ -18,6 +18,9 @@ public:
/** Constructor */
AAgrarianGameGameMode();
virtual void RestartPlayer(AController* NewPlayer) override;
virtual void Logout(AController* Exiting) override;
};
@@ -173,24 +173,7 @@ int32 UAgrarianPersistenceSubsystem::CapturePlayers(UAgrarianSaveGame* SaveGame)
SaveGame->Players.Reset();
for (const AAgrarianGameCharacter* Character : Players)
{
const UAgrarianSurvivalComponent* SurvivalComponent = Character ? Character->GetSurvivalComponent() : nullptr;
if (!Character || !SurvivalComponent)
{
continue;
}
FAgrarianSavedPlayer SavedPlayer;
SavedPlayer.PlayerId = GetPlayerPersistenceId(Character);
SavedPlayer.Transform = Character->GetActorTransform();
SavedPlayer.Survival = SurvivalComponent->Survival;
SavedPlayer.CareHistory = SurvivalComponent->CareHistory;
if (const UAgrarianInventoryComponent* InventoryComponent = Character->GetInventoryComponent())
{
SavedPlayer.Inventory = InventoryComponent->Items;
}
SaveGame->Players.Add(SavedPlayer);
CapturePlayerIntoSave(Character, SaveGame);
}
return SaveGame->Players.Num();
@@ -209,38 +192,32 @@ int32 UAgrarianPersistenceSubsystem::RestorePlayers(const UAgrarianSaveGame* Sav
int32 RestoredCount = 0;
for (AAgrarianGameCharacter* Character : Players)
{
UAgrarianSurvivalComponent* SurvivalComponent = Character ? Character->GetSurvivalComponent() : nullptr;
if (!Character || !SurvivalComponent)
if (RestorePlayerFromSave(Character, SaveGame))
{
continue;
RestoredCount++;
}
const FString PlayerId = GetPlayerPersistenceId(Character);
const FAgrarianSavedPlayer* SavedPlayer = SaveGame->Players.FindByPredicate(
[&PlayerId](const FAgrarianSavedPlayer& Candidate)
{
return Candidate.PlayerId == PlayerId;
});
if (!SavedPlayer)
{
continue;
}
Character->SetActorTransform(SavedPlayer->Transform, false, nullptr, ETeleportType::TeleportPhysics);
SurvivalComponent->ApplySavedState(SavedPlayer->Survival, SavedPlayer->CareHistory);
if (UAgrarianInventoryComponent* InventoryComponent = Character->GetInventoryComponent())
{
InventoryComponent->RestoreSavedItems(SavedPlayer->Inventory);
}
RestoredCount++;
}
return RestoredCount;
}
bool UAgrarianPersistenceSubsystem::SavePlayerSnapshot(const AAgrarianGameCharacter* Character) const
{
UAgrarianSaveGame* SaveGame = LoadOrCreateSave();
if (!SaveGame || !CapturePlayerIntoSave(Character, SaveGame))
{
return false;
}
return WriteSave(SaveGame);
}
bool UAgrarianPersistenceSubsystem::RestorePlayerSnapshot(AAgrarianGameCharacter* Character) const
{
const UAgrarianSaveGame* SaveGame = LoadOrCreateSave();
return SaveGame ? RestorePlayerFromSave(Character, SaveGame) : false;
}
int32 UAgrarianPersistenceSubsystem::CaptureResourceNodes(UAgrarianSaveGame* SaveGame) const
{
if (!SaveGame)
@@ -339,6 +316,64 @@ bool UAgrarianPersistenceSubsystem::LoadCurrentWorld(int32& RestoredPlayerCount,
return bRestoredWorldState;
}
bool UAgrarianPersistenceSubsystem::CapturePlayerIntoSave(const AAgrarianGameCharacter* Character, UAgrarianSaveGame* SaveGame) const
{
const UAgrarianSurvivalComponent* SurvivalComponent = Character ? Character->GetSurvivalComponent() : nullptr;
if (!Character || !SurvivalComponent || !SaveGame)
{
return false;
}
FAgrarianSavedPlayer SavedPlayer;
SavedPlayer.PlayerId = GetPlayerPersistenceId(Character);
SavedPlayer.Transform = Character->GetActorTransform();
SavedPlayer.Survival = SurvivalComponent->Survival;
SavedPlayer.CareHistory = SurvivalComponent->CareHistory;
if (const UAgrarianInventoryComponent* InventoryComponent = Character->GetInventoryComponent())
{
SavedPlayer.Inventory = InventoryComponent->Items;
}
SaveGame->Players.RemoveAll([&SavedPlayer](const FAgrarianSavedPlayer& Candidate)
{
return Candidate.PlayerId == SavedPlayer.PlayerId;
});
SaveGame->Players.Add(SavedPlayer);
return true;
}
bool UAgrarianPersistenceSubsystem::RestorePlayerFromSave(AAgrarianGameCharacter* Character, const UAgrarianSaveGame* SaveGame) const
{
UAgrarianSurvivalComponent* SurvivalComponent = Character ? Character->GetSurvivalComponent() : nullptr;
if (!Character || !SurvivalComponent || !SaveGame)
{
return false;
}
const FString PlayerId = GetPlayerPersistenceId(Character);
const FAgrarianSavedPlayer* SavedPlayer = SaveGame->Players.FindByPredicate(
[&PlayerId](const FAgrarianSavedPlayer& Candidate)
{
return Candidate.PlayerId == PlayerId;
});
if (!SavedPlayer)
{
return false;
}
Character->SetActorTransform(SavedPlayer->Transform, false, nullptr, ETeleportType::TeleportPhysics);
SurvivalComponent->ApplySavedState(SavedPlayer->Survival, SavedPlayer->CareHistory);
if (UAgrarianInventoryComponent* InventoryComponent = Character->GetInventoryComponent())
{
InventoryComponent->RestoreSavedItems(SavedPlayer->Inventory);
}
return true;
}
void UAgrarianPersistenceSubsystem::FindPersistentComponents(TArray<UAgrarianPersistentActorComponent*>& OutComponents) const
{
OutComponents.Reset();
@@ -59,6 +59,12 @@ public:
UFUNCTION(BlueprintCallable, Category = "Agrarian|Persistence")
int32 RestorePlayers(const UAgrarianSaveGame* SaveGame) const;
UFUNCTION(BlueprintCallable, Category = "Agrarian|Persistence")
bool SavePlayerSnapshot(const AAgrarianGameCharacter* Character) const;
UFUNCTION(BlueprintCallable, Category = "Agrarian|Persistence")
bool RestorePlayerSnapshot(AAgrarianGameCharacter* Character) const;
UFUNCTION(BlueprintCallable, Category = "Agrarian|Persistence")
int32 CaptureResourceNodes(UAgrarianSaveGame* SaveGame) const;
@@ -72,6 +78,8 @@ public:
bool LoadCurrentWorld(int32& RestoredPlayerCount, int32& RestoredWorldActorCount, bool bClearExistingActors = true) const;
protected:
bool CapturePlayerIntoSave(const AAgrarianGameCharacter* Character, UAgrarianSaveGame* SaveGame) const;
bool RestorePlayerFromSave(AAgrarianGameCharacter* Character, const UAgrarianSaveGame* SaveGame) const;
void FindPersistentComponents(TArray<UAgrarianPersistentActorComponent*>& OutComponents) const;
void FindAgrarianPlayers(TArray<AAgrarianGameCharacter*>& OutPlayers) const;
void FindResourceNodes(TArray<AAgrarianResourceNode*>& OutResourceNodes) const;