460 lines
11 KiB
C++
460 lines
11 KiB
C++
// Copyright Pacificao. All Rights Reserved.
|
|
|
|
#include "AgrarianPersistenceSubsystem.h"
|
|
#include "AgrarianGameCharacter.h"
|
|
#include "AgrarianGameState.h"
|
|
#include "AgrarianInventoryComponent.h"
|
|
#include "AgrarianPersistentActorComponent.h"
|
|
#include "AgrarianResourceNode.h"
|
|
#include "AgrarianSaveGame.h"
|
|
#include "AgrarianSurvivalComponent.h"
|
|
#include "EngineUtils.h"
|
|
#include "Engine/World.h"
|
|
#include "GameFramework/PlayerState.h"
|
|
#include "Kismet/GameplayStatics.h"
|
|
|
|
UAgrarianSaveGame* UAgrarianPersistenceSubsystem::CreateEmptySave() const
|
|
{
|
|
return Cast<UAgrarianSaveGame>(UGameplayStatics::CreateSaveGameObject(UAgrarianSaveGame::StaticClass()));
|
|
}
|
|
|
|
UAgrarianSaveGame* UAgrarianPersistenceSubsystem::LoadOrCreateSave() const
|
|
{
|
|
if (UGameplayStatics::DoesSaveGameExist(DefaultSlotName, UserIndex))
|
|
{
|
|
if (UAgrarianSaveGame* Loaded = Cast<UAgrarianSaveGame>(UGameplayStatics::LoadGameFromSlot(DefaultSlotName, UserIndex)))
|
|
{
|
|
return Loaded;
|
|
}
|
|
}
|
|
|
|
return CreateEmptySave();
|
|
}
|
|
|
|
bool UAgrarianPersistenceSubsystem::WriteSave(UAgrarianSaveGame* SaveGame) const
|
|
{
|
|
return SaveGame ? UGameplayStatics::SaveGameToSlot(SaveGame, DefaultSlotName, UserIndex) : false;
|
|
}
|
|
|
|
bool UAgrarianPersistenceSubsystem::DoesSaveExist() const
|
|
{
|
|
return UGameplayStatics::DoesSaveGameExist(DefaultSlotName, UserIndex);
|
|
}
|
|
|
|
void UAgrarianPersistenceSubsystem::RegisterWorldActorClass(FName ActorTypeId, TSubclassOf<AActor> ActorClass)
|
|
{
|
|
if (ActorTypeId != NAME_None && ActorClass)
|
|
{
|
|
WorldActorClassRegistry.Add(ActorTypeId, ActorClass);
|
|
}
|
|
}
|
|
|
|
int32 UAgrarianPersistenceSubsystem::CaptureWorldActors(UAgrarianSaveGame* SaveGame) const
|
|
{
|
|
if (!SaveGame)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
TArray<UAgrarianPersistentActorComponent*> PersistentComponents;
|
|
FindPersistentComponents(PersistentComponents);
|
|
|
|
SaveGame->WorldActors.Reset();
|
|
for (const UAgrarianPersistentActorComponent* Component : PersistentComponents)
|
|
{
|
|
if (Component && Component->IsSaveable())
|
|
{
|
|
SaveGame->WorldActors.Add(Component->CaptureSaveState());
|
|
}
|
|
}
|
|
|
|
return SaveGame->WorldActors.Num();
|
|
}
|
|
|
|
int32 UAgrarianPersistenceSubsystem::RestoreWorldActors(const UAgrarianSaveGame* SaveGame, bool bClearExistingActors) const
|
|
{
|
|
UWorld* World = GetWorld();
|
|
if (!World || !SaveGame)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (bClearExistingActors)
|
|
{
|
|
TArray<UAgrarianPersistentActorComponent*> ExistingComponents;
|
|
FindPersistentComponents(ExistingComponents);
|
|
|
|
for (UAgrarianPersistentActorComponent* Component : ExistingComponents)
|
|
{
|
|
if (AActor* Owner = Component ? Component->GetOwner() : nullptr)
|
|
{
|
|
Owner->Destroy();
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 RestoredCount = 0;
|
|
for (const FAgrarianSavedWorldActor& SavedActor : SaveGame->WorldActors)
|
|
{
|
|
const TSubclassOf<AActor>* ActorClass = WorldActorClassRegistry.Find(SavedActor.ActorTypeId);
|
|
if (!ActorClass || !(*ActorClass))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FActorSpawnParameters SpawnParams;
|
|
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
|
|
|
AActor* SpawnedActor = World->SpawnActor<AActor>(*ActorClass, SavedActor.Transform, SpawnParams);
|
|
if (!SpawnedActor)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (UAgrarianPersistentActorComponent* Component = SpawnedActor->FindComponentByClass<UAgrarianPersistentActorComponent>())
|
|
{
|
|
Component->ApplySaveState(SavedActor);
|
|
}
|
|
|
|
RestoredCount++;
|
|
}
|
|
|
|
return RestoredCount;
|
|
}
|
|
|
|
bool UAgrarianPersistenceSubsystem::CaptureWorldState(UAgrarianSaveGame* SaveGame) const
|
|
{
|
|
UWorld* World = GetWorld();
|
|
AAgrarianGameState* GameState = World ? World->GetGameState<AAgrarianGameState>() : nullptr;
|
|
if (!SaveGame || !GameState)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
SaveGame->WorldHours = GameState->WorldHours;
|
|
SaveGame->Weather = GameState->Weather;
|
|
SaveGame->WeatherInputs = GameState->ActiveWeatherInputs;
|
|
SaveGame->WeatherDebug = GameState->GetWeatherDebugSnapshot();
|
|
return true;
|
|
}
|
|
|
|
bool UAgrarianPersistenceSubsystem::RestoreWorldState(const UAgrarianSaveGame* SaveGame) const
|
|
{
|
|
UWorld* World = GetWorld();
|
|
AAgrarianGameState* GameState = World ? World->GetGameState<AAgrarianGameState>() : nullptr;
|
|
if (!SaveGame || !GameState || !GameState->HasAuthority())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
GameState->WorldHours = SaveGame->WorldHours;
|
|
if (SaveGame->WeatherInputs.bHasProviderData)
|
|
{
|
|
GameState->ApplyMappedWeatherInputs(SaveGame->WeatherInputs);
|
|
}
|
|
else
|
|
{
|
|
GameState->SetWeather(SaveGame->Weather);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int32 UAgrarianPersistenceSubsystem::CapturePlayers(UAgrarianSaveGame* SaveGame) const
|
|
{
|
|
if (!SaveGame)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
TArray<AAgrarianGameCharacter*> Players;
|
|
FindAgrarianPlayers(Players);
|
|
|
|
SaveGame->Players.Reset();
|
|
for (const AAgrarianGameCharacter* Character : Players)
|
|
{
|
|
CapturePlayerIntoSave(Character, SaveGame);
|
|
}
|
|
|
|
return SaveGame->Players.Num();
|
|
}
|
|
|
|
int32 UAgrarianPersistenceSubsystem::RestorePlayers(const UAgrarianSaveGame* SaveGame) const
|
|
{
|
|
if (!SaveGame)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
TArray<AAgrarianGameCharacter*> Players;
|
|
FindAgrarianPlayers(Players);
|
|
|
|
int32 RestoredCount = 0;
|
|
for (AAgrarianGameCharacter* Character : Players)
|
|
{
|
|
if (RestorePlayerFromSave(Character, SaveGame))
|
|
{
|
|
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)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
TArray<AAgrarianResourceNode*> ResourceNodes;
|
|
FindResourceNodes(ResourceNodes);
|
|
|
|
SaveGame->ResourceNodes.Reset();
|
|
for (const AAgrarianResourceNode* ResourceNode : ResourceNodes)
|
|
{
|
|
if (!ResourceNode)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FAgrarianSavedResourceNode SavedNode = ResourceNode->CaptureResourceSaveState();
|
|
if (SavedNode.ResourceNodeId != NAME_None)
|
|
{
|
|
SaveGame->ResourceNodes.Add(SavedNode);
|
|
}
|
|
}
|
|
|
|
return SaveGame->ResourceNodes.Num();
|
|
}
|
|
|
|
int32 UAgrarianPersistenceSubsystem::RestoreResourceNodes(const UAgrarianSaveGame* SaveGame) const
|
|
{
|
|
if (!SaveGame)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
TArray<AAgrarianResourceNode*> ResourceNodes;
|
|
FindResourceNodes(ResourceNodes);
|
|
|
|
int32 RestoredCount = 0;
|
|
for (AAgrarianResourceNode* ResourceNode : ResourceNodes)
|
|
{
|
|
if (!ResourceNode)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FName ResourceNodeId = ResourceNode->GetResourcePersistenceId();
|
|
const FAgrarianSavedResourceNode* SavedNode = SaveGame->ResourceNodes.FindByPredicate(
|
|
[ResourceNodeId](const FAgrarianSavedResourceNode& Candidate)
|
|
{
|
|
return Candidate.ResourceNodeId == ResourceNodeId;
|
|
});
|
|
|
|
if (!SavedNode)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ResourceNode->ApplyResourceSaveState(*SavedNode);
|
|
RestoredCount++;
|
|
}
|
|
|
|
return RestoredCount;
|
|
}
|
|
|
|
bool UAgrarianPersistenceSubsystem::SaveCurrentWorld() const
|
|
{
|
|
UAgrarianSaveGame* SaveGame = LoadOrCreateSave();
|
|
if (!SaveGame)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CaptureWorldState(SaveGame);
|
|
CapturePlayers(SaveGame);
|
|
CaptureWorldActors(SaveGame);
|
|
CaptureResourceNodes(SaveGame);
|
|
return WriteSave(SaveGame);
|
|
}
|
|
|
|
bool UAgrarianPersistenceSubsystem::LoadCurrentWorld(int32& RestoredPlayerCount, int32& RestoredWorldActorCount, bool bClearExistingActors) const
|
|
{
|
|
RestoredPlayerCount = 0;
|
|
RestoredWorldActorCount = 0;
|
|
|
|
const UAgrarianSaveGame* SaveGame = LoadOrCreateSave();
|
|
if (!SaveGame)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const bool bRestoredWorldState = RestoreWorldState(SaveGame);
|
|
RestoredPlayerCount = RestorePlayers(SaveGame);
|
|
RestoredWorldActorCount = RestoreWorldActors(SaveGame, bClearExistingActors);
|
|
RestoreResourceNodes(SaveGame);
|
|
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();
|
|
|
|
UWorld* World = GetWorld();
|
|
if (!World)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (TActorIterator<AActor> ActorIt(World); ActorIt; ++ActorIt)
|
|
{
|
|
AActor* Actor = *ActorIt;
|
|
if (!Actor || Actor->IsPendingKillPending())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (UAgrarianPersistentActorComponent* Component = Actor->FindComponentByClass<UAgrarianPersistentActorComponent>())
|
|
{
|
|
OutComponents.Add(Component);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UAgrarianPersistenceSubsystem::FindAgrarianPlayers(TArray<AAgrarianGameCharacter*>& OutPlayers) const
|
|
{
|
|
OutPlayers.Reset();
|
|
|
|
UWorld* World = GetWorld();
|
|
if (!World)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (TActorIterator<AAgrarianGameCharacter> ActorIt(World); ActorIt; ++ActorIt)
|
|
{
|
|
AAgrarianGameCharacter* Character = *ActorIt;
|
|
if (Character && !Character->IsPendingKillPending())
|
|
{
|
|
OutPlayers.Add(Character);
|
|
}
|
|
}
|
|
}
|
|
|
|
void UAgrarianPersistenceSubsystem::FindResourceNodes(TArray<AAgrarianResourceNode*>& OutResourceNodes) const
|
|
{
|
|
OutResourceNodes.Reset();
|
|
|
|
UWorld* World = GetWorld();
|
|
if (!World)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (TActorIterator<AAgrarianResourceNode> ActorIt(World); ActorIt; ++ActorIt)
|
|
{
|
|
AAgrarianResourceNode* ResourceNode = *ActorIt;
|
|
if (ResourceNode && !ResourceNode->IsPendingKillPending())
|
|
{
|
|
OutResourceNodes.Add(ResourceNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
FString UAgrarianPersistenceSubsystem::GetPlayerPersistenceId(const AAgrarianGameCharacter* Character) const
|
|
{
|
|
if (!Character)
|
|
{
|
|
return TEXT("UnknownPlayer");
|
|
}
|
|
|
|
if (const APlayerState* PlayerState = Character->GetPlayerState())
|
|
{
|
|
const FString PlayerName = PlayerState->GetPlayerName();
|
|
if (!PlayerName.IsEmpty())
|
|
{
|
|
return PlayerName;
|
|
}
|
|
}
|
|
|
|
return Character->GetName();
|
|
}
|