diff --git a/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_AddComponent.h b/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_AddComponent.h index 408b1402..4302ece7 100644 --- a/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_AddComponent.h +++ b/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_AddComponent.h @@ -73,29 +73,15 @@ public: USCS_Node* ParentSCSNode = nullptr; if (!Parent.IsEmpty()) { - for (USCS_Node* Node : Existing) - { - if (Node && Node->ComponentTemplate && - WingUtils::Identifies(Parent, Node->ComponentTemplate)) - { - ParentSCSNode = Node; - break; - } - } - - if (!ParentSCSNode) - { - UWingServer::Printf(TEXT("ERROR: Parent component '%s' not found in Blueprint '%s'\n"), - *Parent, *WingUtils::FormatName(BP)); - return; - } + ParentSCSNode = WingUtils::FindExactlyOneNamed(Parent, Existing); + if (!ParentSCSNode) return; } // Create the SCS node USCS_Node* NewNode = SCS->CreateNode(ComponentClassObj, FName(*Component)); if (!NewNode) { - UWingServer::Printf(TEXT("ERROR: Failed to create SCS node for component '%s' with class '%s'\n"), + UWingServer::Printf(TEXT("ERROR: Failed to create SCS node for component '%s' with class '%s'\n"), *Component, *WingUtils::FormatName(ComponentClassObj)); return; } diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Dump.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Dump.h index 72710bc8..00bd16b5 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Dump.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Dump.h @@ -9,10 +9,7 @@ #include "Engine/Blueprint.h" #include "Animation/AnimBlueprint.h" #include "Animation/Skeleton.h" -#include "Engine/SimpleConstructionScript.h" -#include "Engine/SCS_Node.h" -#include "Components/SceneComponent.h" -#include "GameFramework/Actor.h" +#include "WingActorComponent.h" #include "WingFunctionArgs.h" #include "Kismet2/BlueprintEditorUtils.h" #include "AnimationGraph.h" @@ -82,34 +79,21 @@ public: } // Components - bool bHasAny = false; - if (UClass* GenClass = BP->GeneratedClass) + TArray Components = FWingActorComponent::GetAll(BP); + if (!Components.IsEmpty()) { - // Native components first. - if (AActor* CDO = Cast(GenClass->GetDefaultObject())) + UWingServer::Print(TEXT("\nComponents:\n")); + for (const FWingActorComponent& Comp : Components) { - TArray NativeComponents; - CDO->GetComponents(NativeComponents); - for (UActorComponent* Comp : NativeComponents) - { - if (!bHasAny) { UWingServer::Print(TEXT("\nComponents:\n")); bHasAny = true; } - PrintComponent(Comp, TEXT("native")); - } - } - - // Blueprint SCS components (this blueprint and parents) - TArray Hierarchy; - UBlueprint::GetBlueprintHierarchyFromClass(GenClass, Hierarchy); - for (int32 i = Hierarchy.Num() - 1; i >= 0; --i) - { - UBlueprint* WalkBP = Hierarchy[i]; - if (!WalkBP->SimpleConstructionScript) continue; - const TCHAR* Annotation = (WalkBP == BP) ? nullptr : TEXT("inherited"); - for (USCS_Node* Node : WalkBP->SimpleConstructionScript->GetAllNodes()) - { - if (!bHasAny) { UWingServer::Print(TEXT("\nComponents:\n")); bHasAny = true; } - PrintComponent(Node, Annotation); - } + UWingServer::Printf(TEXT(" %s %s"), *Comp.GetClassName(), *Comp.GetName()); + FString ParentName = Comp.GetParentName(); + if (!ParentName.IsEmpty()) + UWingServer::Printf(TEXT(" [parent: %s]"), *ParentName); + if (Comp.IsNative()) + UWingServer::Print(TEXT(" [native]")); + else if (!Comp.IsOwnedBy(BP)) + UWingServer::Print(TEXT(" [inherited]")); + UWingServer::Print(TEXT("\n")); } } @@ -203,34 +187,6 @@ private: UWingServer::Printf(TEXT(" %s(%s)\n"), *WingUtils::FormatName(Graph), *Args); } - void PrintComponent(USCS_Node* Node, const TCHAR* Annotation = nullptr) - { - FString ClassName = Node->ComponentClass - ? WingUtils::FormatName(Node->ComponentClass) - : TEXT("None"); - UWingServer::Printf(TEXT(" %s %s"), - *ClassName, - *WingUtils::FormatName(Node)); - if (Node->ParentComponentOrVariableName != NAME_None) - UWingServer::Printf(TEXT(" [parent: %s]"), *Node->ParentComponentOrVariableName.ToString()); - if (Annotation) - UWingServer::Printf(TEXT(" [%s]"), Annotation); - UWingServer::Print(TEXT("\n")); - } - - void PrintComponent(UActorComponent* Comp, const TCHAR* Annotation = nullptr) - { - UWingServer::Printf(TEXT(" %s %s"), - *WingUtils::FormatName(Comp->GetClass()), - *Comp->GetName()); - if (USceneComponent* Scene = Cast(Comp)) - if (USceneComponent* Parent = Scene->GetAttachParent()) - UWingServer::Printf(TEXT(" [parent: %s]"), *Parent->GetName()); - if (Annotation) - UWingServer::Printf(TEXT(" [%s]"), Annotation); - UWingServer::Print(TEXT("\n")); - } - void PrintGraph(UEdGraph* Graph, const TCHAR* Type, UClass* Interface = nullptr) { TWeakObjectPtr EntryNode; diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingActorComponent.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingActorComponent.cpp new file mode 100644 index 00000000..d0c3650c --- /dev/null +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingActorComponent.cpp @@ -0,0 +1,81 @@ +#include "WingActorComponent.h" +#include "WingUtils.h" +#include "Engine/Blueprint.h" +#include "Engine/SCS_Node.h" +#include "Engine/SimpleConstructionScript.h" +#include "Components/ActorComponent.h" +#include "Components/SceneComponent.h" +#include "GameFramework/Actor.h" +#include "Kismet2/BlueprintEditorUtils.h" + +FString FWingActorComponent::GetName() const +{ + if (SCSNode) return WingUtils::FormatName(SCSNode); + if (NativeComponent) return WingUtils::FormatName(NativeComponent); + return FString(); +} + +FString FWingActorComponent::GetClassName() const +{ + if (SCSNode) + return SCSNode->ComponentClass ? WingUtils::FormatName(SCSNode->ComponentClass) : TEXT("None"); + if (NativeComponent) + return WingUtils::FormatName(NativeComponent->GetClass()); + return FString(); +} + +FString FWingActorComponent::GetParentName() const +{ + if (SCSNode) + { + if (SCSNode->ParentComponentOrVariableName != NAME_None) + return WingUtils::SanitizeName(SCSNode->ParentComponentOrVariableName); + return FString(); + } + if (NativeComponent) + { + if (USceneComponent* Scene = Cast(NativeComponent)) + if (USceneComponent* Parent = Scene->GetAttachParent()) + return WingUtils::FormatName(Parent); + return FString(); + } + return FString(); +} + +bool FWingActorComponent::IsOwnedBy(const UBlueprint* BP) const +{ + return BP && BP->GeneratedClass == Owner; +} + +TArray FWingActorComponent::GetAll(UBlueprint* BP) +{ + TArray Result; + if (!BP) return Result; + + UClass* GenClass = BP->GeneratedClass; + if (!GenClass) return Result; + + // Native components from CDO + if (AActor* CDO = Cast(GenClass->GetDefaultObject())) + { + UClass* NativeClass = FBlueprintEditorUtils::FindFirstNativeClass(GenClass); + TArray NativeComponents; + CDO->GetComponents(NativeComponents); + for (UActorComponent* Comp : NativeComponents) + Result.Emplace(Comp, NativeClass); + } + + // SCS nodes, walking hierarchy from oldest ancestor to current BP + TArray Hierarchy; + UBlueprint::GetBlueprintHierarchyFromClass(GenClass, Hierarchy); + for (int32 i = Hierarchy.Num() - 1; i >= 0; --i) + { + UBlueprint* WalkBP = Hierarchy[i]; + if (!WalkBP->SimpleConstructionScript) continue; + UClass* OwnerClass = WalkBP->GeneratedClass; + for (USCS_Node* Node : WalkBP->SimpleConstructionScript->GetAllNodes()) + Result.Emplace(Node, OwnerClass); + } + + return Result; +} diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp index 0e18b989..52743f4f 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp @@ -1,4 +1,5 @@ #include "WingUtils.h" +#include "WingActorComponent.h" #include "WingJson.h" #include "WingTypes.h" #include "WingServer.h" @@ -215,6 +216,11 @@ FString WingUtils::FormatName(const FBPInterfaceDescription &IFace) return FormatName(IFace.Interface); } +FString WingUtils::FormatName(const FWingActorComponent &Comp) +{ + return Comp.GetName(); +} + // ============================================================ // Formatting other things // ============================================================ diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingActorComponent.h b/Plugins/UEWingman/Source/UEWingman/Public/WingActorComponent.h new file mode 100644 index 00000000..40038c20 --- /dev/null +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingActorComponent.h @@ -0,0 +1,30 @@ +#pragma once + +#include "CoreMinimal.h" + +class UBlueprint; +class USCS_Node; +class UActorComponent; + +struct FWingActorComponent +{ + USCS_Node* SCSNode = nullptr; + UActorComponent* NativeComponent = nullptr; + UClass* Owner = nullptr; + + FWingActorComponent() = default; + FWingActorComponent(USCS_Node* InSCSNode, UClass* InOwner) : SCSNode(InSCSNode), Owner(InOwner) {} + FWingActorComponent(UActorComponent* InNative, UClass* InOwner) : NativeComponent(InNative), Owner(InOwner) {} + + explicit operator bool() const { return SCSNode || NativeComponent; } + + FString GetName() const; + FString GetClassName() const; + FString GetParentName() const; + bool IsNative() const { return NativeComponent != nullptr; } + bool IsOwnedBy(const UBlueprint* BP) const; + + // Collect all components: native from CDO first, then SCS nodes + // walking the hierarchy from oldest ancestor to current blueprint. + static TArray GetAll(UBlueprint* BP); +}; diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h b/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h index 5382cc6c..8789274f 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h @@ -32,6 +32,7 @@ struct FMemberReference; struct FBPVariableDescription; struct FUserPinInfo; struct FBPInterfaceDescription; +struct FWingActorComponent; // Stateless utility functions used by MCP handlers and the MCP server. // This is effectively a namespace — all methods are static. @@ -76,6 +77,7 @@ public: static FString FormatName(const FProperty *Prop); static FString FormatName(const FUserPinInfo &Pin); static FString FormatName(const FBPInterfaceDescription &IFace); + static FString FormatName(const FWingActorComponent &Comp); //////////////////////////////////////////////////////// //