diff --git a/Content/Buildables/Hub/Build_STHub.uasset b/Content/Buildables/Hub/Build_STHub.uasset index a5e102c..a4287db 100644 Binary files a/Content/Buildables/Hub/Build_STHub.uasset and b/Content/Buildables/Hub/Build_STHub.uasset differ diff --git a/Content/Buildables/ItemTeleporter/ItemTeleporter_Build.uasset b/Content/Buildables/ItemTeleporter/ItemTeleporter_Build.uasset index 2c10333..9211434 100644 Binary files a/Content/Buildables/ItemTeleporter/ItemTeleporter_Build.uasset and b/Content/Buildables/ItemTeleporter/ItemTeleporter_Build.uasset differ diff --git a/Content/Widgets/TFIT_ItemTeleporter.uasset b/Content/Widgets/TFIT_ItemTeleporter.uasset deleted file mode 100644 index 95cdffb..0000000 Binary files a/Content/Widgets/TFIT_ItemTeleporter.uasset and /dev/null differ diff --git a/Content/Widgets/TFIT_STHUB.uasset b/Content/Widgets/TFIT_STHUB.uasset deleted file mode 100644 index 4e2bff0..0000000 Binary files a/Content/Widgets/TFIT_STHUB.uasset and /dev/null differ diff --git a/Source/StorageTeleporter/Private/StorageTeleporter.cpp b/Source/StorageTeleporter/Private/StorageTeleporter.cpp index b6bfce3..f10c7be 100644 --- a/Source/StorageTeleporter/Private/StorageTeleporter.cpp +++ b/Source/StorageTeleporter/Private/StorageTeleporter.cpp @@ -1,20 +1,412 @@ -// Copyright Epic Games, Inc. All Rights Reserved. - #include "StorageTeleporter.h" -#define LOCTEXT_NAMESPACE "FStorageTeleporterModule" +#include "Containers/Ticker.h" +#include "Engine/Engine.h" +#include "Engine/GameInstance.h" +#include "Engine/World.h" +#include "EngineUtils.h" +#include "FGRemoteCallObject.h" +#include "Buildables/FGBuildableFactory.h" +#include "FGFactoryConnectionComponent.h" +#include "FGInventoryComponent.h" +#include "GameFramework/Actor.h" +#include "Registry/RemoteCallObjectRegistry.h" +#include "Subsystem/ModSubsystem.h" +#include "Subsystem/SubsystemActorManager.h" +#include "UObject/UObjectGlobals.h" +#include "UObject/StructOnScope.h" +#include "UObject/UnrealType.h" + +DEFINE_LOG_CATEGORY_STATIC(LogStorageTeleporterRepair, Log, All); + +namespace +{ + constexpr float RegistryRepairIntervalSeconds = 2.0f; + + bool IsClassNamed(const UObject* Object, const TCHAR* ClassName) + { + return IsValid(Object) && Object->GetClass()->GetName().Equals(ClassName); + } + + UObject* ReadTeleporterInfo(AActor* Teleporter) + { + if (FObjectPropertyBase* Property = FindFProperty(Teleporter->GetClass(), TEXT("TeleporterInfo"))) + { + if (UObject* Value = Property->GetObjectPropertyValue_InContainer(Teleporter)) + { + return Value; + } + } + + for (TFieldIterator It(Teleporter->GetClass()); It; ++It) + { + FObjectPropertyBase* Property = *It; + if (Property->GetName().Equals(TEXT("TeleporterInfo"), ESearchCase::IgnoreCase) || + (Property->PropertyClass && Property->PropertyClass->GetName().Equals(TEXT("TeleporterInfo_C")))) + { + if (UObject* Value = Property->GetObjectPropertyValue_InContainer(Teleporter)) + { + return Value; + } + } + } + return nullptr; + } + + UObject* ReadObjectProperty(UObject* Owner, const TCHAR* PropertyName) + { + if (!Owner) + { + return nullptr; + } + if (FObjectPropertyBase* Property = FindFProperty(Owner->GetClass(), PropertyName)) + { + return Property->GetObjectPropertyValue_InContainer(Owner); + } + return nullptr; + } + + UObject* CreateTeleporterInfo(UObject* Subsystem, AActor* Teleporter) + { + UClass* InfoClass = LoadClass(nullptr, TEXT("/StorageTeleporter/TeleporterInfo.TeleporterInfo_C")); + if (!InfoClass || !Subsystem || !Teleporter) + { + return nullptr; + } + + UObject* Info = NewObject(Subsystem, InfoClass); + UFunction* SetInfo = Info ? Info->FindFunction(TEXT("SetInfo")) : nullptr; + if (!SetInfo) + { + return nullptr; + } + + FStructOnScope Parameters(SetInfo); + uint8* ParameterMemory = Parameters.GetStructMemory(); + for (TFieldIterator It(SetInfo); It && It->HasAnyPropertyFlags(CPF_Parm); ++It) + { + FProperty* Property = *It; + if (Property->HasAnyPropertyFlags(CPF_ReturnParm | CPF_OutParm)) + { + continue; + } + + if (FStructProperty* StructProperty = CastField(Property)) + { + if (StructProperty->Struct == TBaseStructure::Get()) + { + *StructProperty->ContainerPtrToValuePtr(ParameterMemory) = Teleporter->GetActorLocation(); + } + } + else if (FObjectPropertyBase* ObjectProperty = CastField(Property)) + { + UObject* Value = nullptr; + if (Property->GetName().Contains(TEXT("Teleporter"), ESearchCase::IgnoreCase)) + { + Value = Teleporter; + } + else if (Property->GetName().Contains(TEXT("Input"), ESearchCase::IgnoreCase)) + { + Value = ReadObjectProperty(Teleporter, TEXT("InputConnection")); + } + else if (Property->GetName().Contains(TEXT("Output"), ESearchCase::IgnoreCase)) + { + Value = ReadObjectProperty(Teleporter, TEXT("OutputConnection")); + } + ObjectProperty->SetObjectPropertyValue_InContainer(ParameterMemory, Value); + } + } + + Info->ProcessEvent(SetInfo, ParameterMemory); + if (FObjectPropertyBase* InfoProperty = FindFProperty(Teleporter->GetClass(), TEXT("TeleporterInfo"))) + { + InfoProperty->SetObjectPropertyValue_InContainer(Teleporter, Info); + } + return Info; + } + + bool ReadBoolProperty(UObject* Owner, const TCHAR* PropertyName, bool DefaultValue = false) + { + if (Owner) + { + if (FBoolProperty* Property = FindFProperty(Owner->GetClass(), PropertyName)) + { + return Property->GetPropertyValue_InContainer(Owner); + } + } + return DefaultValue; + } + + bool RegistryContains(FMapProperty* MapProperty, UObject* Subsystem, const FString& StorageId, UObject* TeleporterInfo) + { + if (!MapProperty || !Subsystem || !TeleporterInfo) + { + return false; + } + + FStrProperty* KeyProperty = CastField(MapProperty->KeyProp); + FStructProperty* ValueProperty = CastField(MapProperty->ValueProp); + if (!KeyProperty || !ValueProperty) + { + return false; + } + + FArrayProperty* InfoArrayProperty = nullptr; + for (TFieldIterator It(ValueProperty->Struct); It; ++It) + { + if (CastField((*It)->Inner)) + { + InfoArrayProperty = *It; + break; + } + } + if (!InfoArrayProperty) + { + return false; + } + + FScriptMapHelper MapHelper(MapProperty, MapProperty->ContainerPtrToValuePtr(Subsystem)); + for (int32 Index = 0; Index < MapHelper.GetMaxIndex(); ++Index) + { + if (!MapHelper.IsValidIndex(Index) || KeyProperty->GetPropertyValue(MapHelper.GetKeyPtr(Index)) != StorageId) + { + continue; + } + + void* ArrayAddress = InfoArrayProperty->ContainerPtrToValuePtr(MapHelper.GetValuePtr(Index)); + FScriptArrayHelper ArrayHelper(InfoArrayProperty, ArrayAddress); + FObjectPropertyBase* InnerProperty = CastFieldChecked(InfoArrayProperty->Inner); + for (int32 ArrayIndex = 0; ArrayIndex < ArrayHelper.Num(); ++ArrayIndex) + { + if (InnerProperty->GetObjectPropertyValue(ArrayHelper.GetRawPtr(ArrayIndex)) == TeleporterInfo) + { + return true; + } + } + return false; + } + return false; + } + + bool InvokeAddTeleporterId(UObject* Subsystem, UObject* TeleporterInfo, const FString& StorageId) + { + UFunction* Function = Subsystem ? Subsystem->FindFunction(TEXT("AddTeleporterID")) : nullptr; + if (!Function || !TeleporterInfo) + { + return false; + } + + FStructOnScope Parameters(Function); + uint8* ParameterMemory = Parameters.GetStructMemory(); + bool bSetInfo = false; + bool bSetId = false; + for (TFieldIterator It(Function); It && It->HasAnyPropertyFlags(CPF_Parm); ++It) + { + FProperty* Property = *It; + if (Property->HasAnyPropertyFlags(CPF_ReturnParm | CPF_OutParm)) + { + continue; + } + if (FObjectPropertyBase* ObjectProperty = CastField(Property)) + { + ObjectProperty->SetObjectPropertyValue_InContainer(ParameterMemory, TeleporterInfo); + bSetInfo = true; + } + else if (FStrProperty* StringProperty = CastField(Property)) + { + StringProperty->SetPropertyValue_InContainer(ParameterMemory, StorageId); + bSetId = true; + } + } + if (!bSetInfo || !bSetId) + { + return false; + } + + Subsystem->ProcessEvent(Function, ParameterMemory); + return true; + } +} void FStorageTeleporterModule::StartupModule() { - // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module + UE_LOG(LogStorageTeleporterRepair, Warning, TEXT("Storage Teleporter registry repair module started.")); + RegistryRepairTickerHandle = FTSTicker::GetCoreTicker().AddTicker( + FTickerDelegate::CreateRaw(this, &FStorageTeleporterModule::RepairRegistries), + RegistryRepairIntervalSeconds); } void FStorageTeleporterModule::ShutdownModule() { - // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, - // we call this function before unloading the module. + if (RegistryRepairTickerHandle.IsValid()) + { + FTSTicker::GetCoreTicker().RemoveTicker(RegistryRepairTickerHandle); + RegistryRepairTickerHandle.Reset(); + } +} + +bool FStorageTeleporterModule::RepairRegistries(float DeltaTime) +{ + if (GEngine) + { + for (const FWorldContext& Context : GEngine->GetWorldContexts()) + { + UWorld* World = Context.World(); + if (World && (World->WorldType == EWorldType::Game || World->WorldType == EWorldType::PIE)) + { + RepairWorld(World); + } + } + } + return true; +} + +void FStorageTeleporterModule::RepairWorld(UWorld* World) +{ + static TSet> RegisteredRcoGameInstances; + if (UGameInstance* GameInstance = World->GetGameInstance()) + { + if (!RegisteredRcoGameInstances.Contains(GameInstance)) + { + UClass* RemoteCallObjectClass = LoadClass( + nullptr, TEXT("/StorageTeleporter/RCO_StorageTeleporter.RCO_StorageTeleporter_C")); + if (RemoteCallObjectClass) + { + if (URemoteCallObjectRegistry* Registry = GameInstance->GetSubsystem()) + { + Registry->RegisterRemoteCallObject(RemoteCallObjectClass); + RegisteredRcoGameInstances.Add(GameInstance); + UE_LOG(LogStorageTeleporterRepair, Warning, + TEXT("Registered RCO_StorageTeleporter with SML for game instance %s."), + *GameInstance->GetName()); + } + } + } + } + + UClass* DesiredSubsystemClass = LoadClass( + nullptr, TEXT("/StorageTeleporter/STSubsystem.STSubsystem_C")); + USubsystemActorManager* SubsystemManager = World->GetSubsystem(); + static TSet> RegistrationAttemptedWorlds; + if (DesiredSubsystemClass && SubsystemManager && !RegistrationAttemptedWorlds.Contains(World)) + { + RegistrationAttemptedWorlds.Add(World); + AModSubsystem* RegisteredSubsystem = SubsystemManager->K2_GetSubsystemActor(DesiredSubsystemClass); + if (!RegisteredSubsystem) + { + SubsystemManager->RegisterSubsystemActor(DesiredSubsystemClass); + RegisteredSubsystem = SubsystemManager->K2_GetSubsystemActor(DesiredSubsystemClass); + UE_LOG(LogStorageTeleporterRepair, Warning, + TEXT("Registered STSubsystem with SML in %s; immediately available=%s."), + *World->GetName(), RegisteredSubsystem ? TEXT("yes") : TEXT("no")); + } + } + + AActor* Subsystem = nullptr; + AActor* PrimaryHub = nullptr; + AActor* FallbackHub = nullptr; + TArray Teleporters; + for (TActorIterator It(World); It; ++It) + { + AActor* Actor = *It; + if ((DesiredSubsystemClass && Actor->IsA(DesiredSubsystemClass)) || + FindFProperty(Actor->GetClass(), TEXT("StorageDataMap"))) + { + Subsystem = Actor; + } + else if (IsClassNamed(Actor, TEXT("Build_STHub_C")) || + Actor->GetClass()->GetPathName().Contains(TEXT("/StorageTeleporter/Buildables/Hub/"))) + { + FallbackHub = FallbackHub ? FallbackHub : Actor; + if (ReadBoolProperty(Actor, TEXT("isMainHub"), true)) + { + PrimaryHub = Actor; + } + } + else if (IsClassNamed(Actor, TEXT("ItemTeleporter_Build_C")) || + (Actor->GetClass()->GetPathName().Contains(TEXT("/StorageTeleporter/")) && + FindFProperty(Actor->GetClass(), TEXT("StorageID")))) + { + Teleporters.Add(Actor); + } + } + if (!Subsystem || Teleporters.IsEmpty()) + { + return; + } + + AActor* Hub = PrimaryHub ? PrimaryHub : FallbackHub; + if (FObjectPropertyBase* HubProperty = FindFProperty(Subsystem->GetClass(), TEXT("HUB"))) + { + UObject* CurrentHub = HubProperty->GetObjectPropertyValue_InContainer(Subsystem); + if (Hub && CurrentHub != Hub) + { + HubProperty->SetObjectPropertyValue_InContainer(Subsystem, Hub); + UE_LOG(LogStorageTeleporterRepair, Warning, + TEXT("Restored STSubsystem HUB reference to '%s' in %s."), + *Hub->GetName(), *World->GetName()); + } + } + + static TSet> LoggedWorlds; + if (!LoggedWorlds.Contains(World)) + { + LoggedWorlds.Add(World); + const AFGBuildableFactory* HubFactory = Cast(Hub); + UE_LOG(LogStorageTeleporterRepair, Warning, + TEXT("Registry scan found subsystem '%s', hub '%s' (powered=%s), and %d item teleporter(s) in %s (net mode %d)."), + *Subsystem->GetName(), Hub ? *Hub->GetName() : TEXT("none"), + HubFactory && HubFactory->HasPower() ? TEXT("yes") : TEXT("no"), Teleporters.Num(), + *World->GetName(), static_cast(World->GetNetMode())); + + for (AActor* Teleporter : Teleporters) + { + const UFGFactoryConnectionComponent* InputConnection = + Cast(ReadObjectProperty(Teleporter, TEXT("In"))); + const UFGFactoryConnectionComponent* OutputConnection = + Cast(ReadObjectProperty(Teleporter, TEXT("Out"))); + const UFGInventoryComponent* InputInventory = + Cast(ReadObjectProperty(Teleporter, TEXT("InputInventory"))); + const UFGInventoryComponent* OutputInventory = + Cast(ReadObjectProperty(Teleporter, TEXT("OutputInventory"))); + UE_LOG(LogStorageTeleporterRepair, Log, + TEXT("Transport state '%s': enabled=%s, input-connected=%s, output-connected=%s, input=%d/%d, output=%d/%d."), + *Teleporter->GetName(), ReadBoolProperty(Teleporter, TEXT("isSwitchedOn")) ? TEXT("yes") : TEXT("no"), + InputConnection && InputConnection->IsConnected() ? TEXT("yes") : TEXT("no"), + OutputConnection && OutputConnection->IsConnected() ? TEXT("yes") : TEXT("no"), + InputInventory ? InputInventory->GetNumItems(nullptr) : -1, + InputInventory ? InputInventory->GetSizeLinear() : -1, + OutputInventory ? OutputInventory->GetNumItems(nullptr) : -1, + OutputInventory ? OutputInventory->GetSizeLinear() : -1); + } + } + + FMapProperty* MapProperty = FindFProperty(Subsystem->GetClass(), TEXT("StorageDataMap")); + for (AActor* Teleporter : Teleporters) + { + FStrProperty* IdProperty = FindFProperty(Teleporter->GetClass(), TEXT("StorageID")); + UObject* TeleporterInfo = ReadTeleporterInfo(Teleporter); + if (!TeleporterInfo) + { + TeleporterInfo = CreateTeleporterInfo(Subsystem, Teleporter); + } + if (!IdProperty || !TeleporterInfo) + { + UE_LOG(LogStorageTeleporterRepair, Warning, + TEXT("Could not repair '%s': StorageID property=%s, TeleporterInfo=%s."), + *Teleporter->GetName(), IdProperty ? TEXT("yes") : TEXT("no"), TeleporterInfo ? TEXT("yes") : TEXT("no")); + continue; + } + + const FString StorageId = IdProperty->GetPropertyValue_InContainer(Teleporter); + if (!RegistryContains(MapProperty, Subsystem, StorageId, TeleporterInfo) && + InvokeAddTeleporterId(Subsystem, TeleporterInfo, StorageId)) + { + UE_LOG(LogStorageTeleporterRepair, Log, + TEXT("Restored teleporter '%s' to connection '%s' in %s."), + *Teleporter->GetName(), *StorageId, *World->GetName()); + } + } } -#undef LOCTEXT_NAMESPACE - -IMPLEMENT_MODULE(FStorageTeleporterModule, StorageTeleporter) \ No newline at end of file +IMPLEMENT_MODULE(FStorageTeleporterModule, StorageTeleporter) diff --git a/Source/StorageTeleporter/Public/StorageTeleporter.h b/Source/StorageTeleporter/Public/StorageTeleporter.h index a9cc9a3..7112502 100644 --- a/Source/StorageTeleporter/Public/StorageTeleporter.h +++ b/Source/StorageTeleporter/Public/StorageTeleporter.h @@ -3,13 +3,18 @@ #pragma once #include "CoreMinimal.h" +#include "Containers/Ticker.h" #include "Modules/ModuleManager.h" class FStorageTeleporterModule : public IModuleInterface { public: - - /** IModuleInterface implementation */ virtual void StartupModule() override; virtual void ShutdownModule() override; + +private: + bool RepairRegistries(float DeltaTime); + void RepairWorld(class UWorld* World); + + FTSTicker::FDelegateHandle RegistryRepairTickerHandle; }; diff --git a/Source/StorageTeleporter/StorageTeleporter.Build.cs b/Source/StorageTeleporter/StorageTeleporter.Build.cs index 9d49e96..84e218b 100644 --- a/Source/StorageTeleporter/StorageTeleporter.Build.cs +++ b/Source/StorageTeleporter/StorageTeleporter.Build.cs @@ -36,6 +36,8 @@ public StorageTeleporter(ReadOnlyTargetRules Target) : base(Target) { "CoreUObject", "Engine", + "FactoryGame", + "SML", "Slate", "SlateCore", // ... add private dependencies that you statically link with here ... diff --git a/Source/StorageTeleporterEditor/Private/StorageTeleporterEditorModule.cpp b/Source/StorageTeleporterEditor/Private/StorageTeleporterEditorModule.cpp new file mode 100644 index 0000000..1c12882 --- /dev/null +++ b/Source/StorageTeleporterEditor/Private/StorageTeleporterEditorModule.cpp @@ -0,0 +1,3 @@ +#include "Modules/ModuleManager.h" + +IMPLEMENT_MODULE(FDefaultModuleImpl, StorageTeleporterEditor) diff --git a/Source/StorageTeleporterEditor/Private/StorageTeleporterEditorRepairLibrary.cpp b/Source/StorageTeleporterEditor/Private/StorageTeleporterEditorRepairLibrary.cpp new file mode 100644 index 0000000..3839b12 --- /dev/null +++ b/Source/StorageTeleporterEditor/Private/StorageTeleporterEditorRepairLibrary.cpp @@ -0,0 +1,38 @@ +#include "StorageTeleporterEditorRepairLibrary.h" + +#include "Engine/Blueprint.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "Kismet2/KismetEditorUtilities.h" + +bool UStorageTeleporterEditorRepairLibrary::SetBlueprintVariableReplicated( + UBlueprint* Blueprint, + FName VariableName, + FString& OutError) +{ + OutError.Reset(); + + if (!Blueprint) + { + OutError = TEXT("Blueprint is null."); + return false; + } + + uint64* PropertyFlags = FBlueprintEditorUtils::GetBlueprintVariablePropertyFlags(Blueprint, VariableName); + if (!PropertyFlags) + { + OutError = FString::Printf(TEXT("Variable '%s' was not found in '%s'."), *VariableName.ToString(), *Blueprint->GetPathName()); + return false; + } + + *PropertyFlags |= CPF_Net; + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); + FKismetEditorUtilities::CompileBlueprint(Blueprint); + + if (Blueprint->Status == BS_Error) + { + OutError = FString::Printf(TEXT("Blueprint '%s' failed to compile."), *Blueprint->GetPathName()); + return false; + } + + return true; +} diff --git a/Source/StorageTeleporterEditor/Public/StorageTeleporterEditorRepairLibrary.h b/Source/StorageTeleporterEditor/Public/StorageTeleporterEditorRepairLibrary.h new file mode 100644 index 0000000..66ab1ec --- /dev/null +++ b/Source/StorageTeleporterEditor/Public/StorageTeleporterEditorRepairLibrary.h @@ -0,0 +1,17 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "StorageTeleporterEditorRepairLibrary.generated.h" + +class UBlueprint; + +UCLASS() +class STORAGETELEPORTEREDITOR_API UStorageTeleporterEditorRepairLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, Category = "Storage Teleporter|Editor Repair") + static bool SetBlueprintVariableReplicated(UBlueprint* Blueprint, FName VariableName, FString& OutError); +}; diff --git a/Source/StorageTeleporterEditor/StorageTeleporterEditor.Build.cs b/Source/StorageTeleporterEditor/StorageTeleporterEditor.Build.cs new file mode 100644 index 0000000..793cdb5 --- /dev/null +++ b/Source/StorageTeleporterEditor/StorageTeleporterEditor.Build.cs @@ -0,0 +1,23 @@ +using UnrealBuildTool; + +public class StorageTeleporterEditor : ModuleRules +{ + public StorageTeleporterEditor(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange(new[] + { + "Core", + "CoreUObject", + "Engine" + }); + + PrivateDependencyModuleNames.AddRange(new[] + { + "BlueprintGraph", + "Kismet", + "UnrealEd" + }); + } +} diff --git a/StorageTeleporter.uplugin b/StorageTeleporter.uplugin index 3207950..d698ab3 100644 --- a/StorageTeleporter.uplugin +++ b/StorageTeleporter.uplugin @@ -12,6 +12,18 @@ "MarketplaceURL": "", "SupportURL": "https://discord.gg/ds696q4", "CanContainContent": true, + "Modules": [ + { + "Name": "StorageTeleporter", + "Type": "Runtime", + "LoadingPhase": "PostDefault" + }, + { + "Name": "StorageTeleporterEditor", + "Type": "Editor", + "LoadingPhase": "Default" + } + ], "IsBetaVersion": false, "IsExperimentalVersion": false, "Installed": false, @@ -26,4 +38,4 @@ "AcceptsAnyRemoteVersion": false, "GameVersion": ">=491125", "BuiltInInitialFeatureState": "Active" -} \ No newline at end of file +}