// 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(UGameplayStatics::CreateSaveGameObject(UAgrarianSaveGame::StaticClass())); } UAgrarianSaveGame* UAgrarianPersistenceSubsystem::LoadOrCreateSave() const { if (UGameplayStatics::DoesSaveGameExist(DefaultSlotName, UserIndex)) { if (UAgrarianSaveGame* Loaded = Cast(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 ActorClass) { if (ActorTypeId != NAME_None && ActorClass) { WorldActorClassRegistry.Add(ActorTypeId, ActorClass); } } int32 UAgrarianPersistenceSubsystem::CaptureWorldActors(UAgrarianSaveGame* SaveGame) const { if (!SaveGame) { return 0; } TArray 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 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* ActorClass = WorldActorClassRegistry.Find(SavedActor.ActorTypeId); if (!ActorClass || !(*ActorClass)) { continue; } FActorSpawnParameters SpawnParams; SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; AActor* SpawnedActor = World->SpawnActor(*ActorClass, SavedActor.Transform, SpawnParams); if (!SpawnedActor) { continue; } if (UAgrarianPersistentActorComponent* Component = SpawnedActor->FindComponentByClass()) { Component->ApplySaveState(SavedActor); } RestoredCount++; } return RestoredCount; } bool UAgrarianPersistenceSubsystem::CaptureWorldState(UAgrarianSaveGame* SaveGame) const { UWorld* World = GetWorld(); AAgrarianGameState* GameState = World ? World->GetGameState() : 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() : 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 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 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 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 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& OutComponents) const { OutComponents.Reset(); UWorld* World = GetWorld(); if (!World) { return; } for (TActorIterator ActorIt(World); ActorIt; ++ActorIt) { AActor* Actor = *ActorIt; if (!Actor || Actor->IsPendingKillPending()) { continue; } if (UAgrarianPersistentActorComponent* Component = Actor->FindComponentByClass()) { OutComponents.Add(Component); } } } void UAgrarianPersistenceSubsystem::FindAgrarianPlayers(TArray& OutPlayers) const { OutPlayers.Reset(); UWorld* World = GetWorld(); if (!World) { return; } for (TActorIterator ActorIt(World); ActorIt; ++ActorIt) { AAgrarianGameCharacter* Character = *ActorIt; if (Character && !Character->IsPendingKillPending()) { OutPlayers.Add(Character); } } } void UAgrarianPersistenceSubsystem::FindResourceNodes(TArray& OutResourceNodes) const { OutResourceNodes.Reset(); UWorld* World = GetWorld(); if (!World) { return; } for (TActorIterator 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(); }