diff --git a/Content/Testing/BP_Test.uasset b/Content/Testing/BP_Test.uasset index d2cbe5c4..30ece353 100644 --- a/Content/Testing/BP_Test.uasset +++ b/Content/Testing/BP_Test.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1015b7d5370d68bc7b558306903a597445c8c59da2aa30d80a016f52876f2013 -size 30959 +oid sha256:58fb9910a80406f04f7e83a256d01af2dcc9f15d26bb2526a5ccf943c5e4a457 +size 39744 diff --git a/Content/Testing/BP_Test1.uasset b/Content/Testing/BP_Test1.uasset index 94a3c751..00a61a77 100644 --- a/Content/Testing/BP_Test1.uasset +++ b/Content/Testing/BP_Test1.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ca3f1a9f39cd94659f6b6e282660f0f143b2b35fa5ba4a6f2862f54052c077af -size 20001 +oid sha256:23a8acb077ec95e1f6d6f367b1984c4cbdb7593970924e0d53738553fdc12c40 +size 27311 diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/ActorComponent_Add.h b/Plugins/UEWingman/Source/UEWingman/Handlers/ActorComponent_Add.h index 9c499ff1..f42663d8 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/ActorComponent_Add.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/ActorComponent_Add.h @@ -48,16 +48,8 @@ public: UBlueprint* BP = F.Asset(Blueprint).Cast(); if (!BP) return; - USimpleConstructionScript* SCS = BP->SimpleConstructionScript; - if (!SCS) - { - UWingServer::Printf(TEXT("ERROR: Blueprint '%s' does not have a SimpleConstructionScript (not an Actor Blueprint)\n"), - *WingUtils::FormatName(BP)); - return; - } - // Check that the proposed name is valid - TArray AllComponents = FWingActorComponent::GetAll(BP); + TArray AllComponents = UWingComponentReference::GetAll(BP); if (!WingUtils::FindExactlyNoneNamed(Component, AllComponents, TEXT("Component"))) return; FString InternalName = WingUtils::CheckProposedName(Component); if (InternalName.IsEmpty()) return; @@ -70,25 +62,15 @@ public: Req.IsChildOf = UActorComponent::StaticClass(); UClass* ComponentClass = UWingTypes::TextToOneObjectType(Class, Req); if (!ComponentClass) return; + if (!UWingComponentReference::CheckValidComponentClass(ComponentClass)) return; // Find the specified parent component - FWingActorComponent* ParentComp = WingUtils::FindExactlyOneNamed(Parent, AllComponents, TEXT("Component")); + UWingComponentReference* ParentComp = WingUtils::FindExactlyOneNamed(Parent, AllComponents, TEXT("Component")); if (!ParentComp) return; // Create the SCS node - USCS_Node *NewNode = FWingActorComponent::AddComponent(BP, ComponentClass, ParentComp, InternalName); - if (!NewNode) - { - UWingServer::Printf(TEXT("ERROR: Failed to create SCS node for component '%s' with class '%s'\n"), - *Component, *WingUtils::FormatName(ComponentClass)); - return; - } + if (!UWingComponentReference::AddComponent(BP, ComponentClass, ParentComp, FName(*InternalName))) return; - UWingServer::Printf(TEXT("Added component %s (%s)"), - *WingUtils::FormatName(NewNode), - *WingUtils::FormatName(ComponentClass)); - if (ParentComp) - UWingServer::Printf(TEXT(" under %s"), *ParentComp->GetName()); - UWingServer::Print(TEXT("\n")); + UWingServer::Printf(TEXT("Component Added.\n")); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/ActorComponent_Dump.h b/Plugins/UEWingman/Source/UEWingman/Handlers/ActorComponent_Dump.h index fe5904b2..9eabfda8 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/ActorComponent_Dump.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/ActorComponent_Dump.h @@ -53,7 +53,7 @@ public: UWingServer::Printf(TEXT("Parent: %s\n"), *WingUtils::SanitizeName(Node->ParentComponentOrVariableName)); // Properties (already sorted and grouped by category) - TArray Props = FWingProperty::GetAll(Template, CPF_Edit); + TArray Props = FWingProperty::GetDetailsMutable(Template, CPF_Edit); if (Props.IsEmpty()) { diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/ActorComponent_Reparent.h b/Plugins/UEWingman/Source/UEWingman/Handlers/ActorComponent_Reparent.h index 34e053cd..b3188421 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/ActorComponent_Reparent.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/ActorComponent_Reparent.h @@ -36,22 +36,17 @@ public: virtual void Handle() override { WingFetcher F; - USCS_Node* Node = F.Walk(Component).Cast(); - if (!Node) return; - - USimpleConstructionScript* SCS = Node->GetSCS(); + UWingComponentReference* CompRef = F.Walk(Component).Cast(); + if (!CompRef) return; // Find the new parent among all components (if specified) - UBlueprint *BP = SCS->GetBlueprint(); - TArray AllComponents = FWingActorComponent::GetAll(BP); - FWingActorComponent* NewParent = WingUtils::FindExactlyOneNamed(Parent, AllComponents, TEXT("Component")); + UBlueprint *BP = CompRef->BP; + TArray AllComponents = UWingComponentReference::GetAll(BP); + UWingComponentReference* NewParent = WingUtils::FindExactlyOneNamed(Parent, AllComponents, TEXT("Component")); if (!NewParent) return; - if (!FWingActorComponent::ReparentComponent(BP, - Node, NewParent)) - return; + if (!CompRef->ReparentComponent(NewParent)) return; - UWingServer::Printf(TEXT("Reparented %s under %s.\n"), - *WingUtils::FormatName(Node), *WingUtils::FormatName(*NewParent)); + UWingServer::Printf(TEXT("Reparented component.")); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Dump.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Dump.h index dd127ac1..67064d9b 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Dump.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Dump.h @@ -83,17 +83,18 @@ public: } // Components - TArray Components = FWingActorComponent::GetAll(BP); - if (!Components.IsEmpty()) + TArray Components3 = UWingComponentReference::GetAll(BP); + if (!Components3.IsEmpty()) { UWingServer::Print(TEXT("\nComponents:\n")); - for (const FWingActorComponent& Comp : Components) + for (const UWingComponentReference* Ref : Components3) { - UWingServer::Printf(TEXT(" %s %s"), *Comp.GetClassName(), *WingUtils::FormatName(Comp)); - FString ParentName = Comp.GetParentName(); - if (!ParentName.IsEmpty()) - UWingServer::Printf(TEXT(" [parent: %s]"), *ParentName); - if (!Comp.IsOwnedBy(BP)) + UWingServer::Printf(TEXT(" %s %s"), + *Ref->TypeName, + *WingUtils::FormatName(Ref)); + if (!Ref->ParentName.IsEmpty()) + UWingServer::Printf(TEXT(" [parent: %s]"), *Ref->ParentName); + if (Ref->Inherited) UWingServer::Print(TEXT(" [inherited]")); UWingServer::Print(TEXT("\n")); } diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_SetDefaults.h b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_SetDefaults.h index d34603dc..8c411dae 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_SetDefaults.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_SetDefaults.h @@ -91,7 +91,7 @@ public: UEdGraphNode* Node = F.Node(Entry.Node).Cast(); if (!Node) return; - TArray All = FWingProperty::GetAll(Node, CPF_Edit); + TArray All = FWingProperty::GetDetailsMutable(Node, CPF_Edit); FWingProperty P = FWingProperty::FindOneExactMatch(All, Entry.Name); if (!P) return; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Dump.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Dump.h index b7507f58..6c81add1 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Dump.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Dump.h @@ -40,7 +40,7 @@ public: WingFetcher F; UObject* Template = F.Walk(Object).Cast(); if (!Template) return; - TArray AllProps = FWingProperty::GetAll(Template, CPF_Edit); + TArray AllProps = FWingProperty::GetDetailsImmutable(Template, CPF_Edit); TArray Props = FWingProperty::FindAllSubstring(AllProps, Query); if (Local) { diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Get.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Get.h index 1919c154..0ac6773b 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Get.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Get.h @@ -36,7 +36,7 @@ public: UObject* Obj = F.Walk(Object).Cast(); if (!Obj) return; - TArray All = FWingProperty::GetAll(Obj, CPF_Edit); + TArray All = FWingProperty::GetDetailsMutable(Obj, CPF_Edit); FWingProperty P = FWingProperty::FindOneExactMatch(All, Property); if (!P) return; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Set.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Set.h index 799d6e3a..edb5fd47 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Set.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Set.h @@ -42,7 +42,7 @@ public: UWingServer::Print(TEXT("Error: No properties specified\n")); return; } - TArray All = FWingProperty::GetAll(Obj, CPF_Edit); + TArray All = FWingProperty::GetDetailsMutable(Obj, CPF_Edit); int SuccessCount = 0; // Validation pass — resolve all properties and values before modifying anything. diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingActorComponent.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingActorComponent.cpp index d29ddbb6..af714280 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingActorComponent.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingActorComponent.cpp @@ -1,5 +1,6 @@ #include "WingActorComponent.h" #include "WingServer.h" +#include "WingTypes.h" #include "WingUtils.h" #include "Engine/Blueprint.h" #include "Engine/SCS_Node.h" @@ -8,191 +9,250 @@ #include "Components/SceneComponent.h" #include "GameFramework/Actor.h" #include "Kismet2/BlueprintEditorUtils.h" +#include "Engine/InheritableComponentHandler.h" -FWingActorComponent::FWingActorComponent(USCS_Node *Node) : - SCSNode(Node), Owner(Node->GetSCS()->GetBlueprint()->GeneratedClass) {} - -FString FWingActorComponent::GetName() const +UActorComponent *UWingComponentReference::FindComponentInCDO(UClass *Class, FName Name) { - if (SCSNode) return WingUtils::FormatName(SCSNode); - if (NativeComponent) return WingUtils::FormatName(NativeComponent); - return FString(); + if (!Class) return nullptr; + AActor* CDO = Cast(Class->GetDefaultObject()); + if (!CDO) return nullptr; + return FindObjectFast(CDO, Name); } -FString FWingActorComponent::GetClassName() const +USCS_Node* UWingComponentReference::FindSCSNodeByName(UBlueprint *BP, FName Name) { - 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) + for (UBlueprint *WalkBP : WingUtils::GetAncestorBlueprints(BP)) { - // Cross-blueprint case: parent is inherited, stored by name. - if (SCSNode->ParentComponentOrVariableName != NAME_None) - return WingUtils::SanitizeName(SCSNode->ParentComponentOrVariableName); - - // Same-blueprint case: find the node whose child list contains us. - for (USCS_Node* Candidate : SCSNode->GetSCS()->GetAllNodes()) - { - if (Candidate->ChildNodes.Contains(SCSNode)) - return WingUtils::FormatName(Candidate); - } - return FString(); + if (!WalkBP->SimpleConstructionScript) continue; + USCS_Node* Node = WalkBP->SimpleConstructionScript->FindSCSNode(Name); + if (Node != nullptr) return Node; } - if (NativeComponent) - { - if (USceneComponent* Scene = Cast(NativeComponent)) - if (USceneComponent* Parent = Scene->GetAttachParent()) - return WingUtils::FormatName(Parent); - return FString(); - } - return FString(); + return nullptr; } -bool FWingActorComponent::IsOwnedBy(const UBlueprint* BP) const +UWingComponentReference::FoundComponent UWingComponentReference::FindComponent(UBlueprint *BP, FName Name) { - return BP && BP->GeneratedClass == Owner; + UClass* NativeClass = FBlueprintEditorUtils::FindFirstNativeClass(BP->ParentClass); + FoundComponent Result; + Result.Name = Name; + Result.SCS = FindSCSNodeByName(BP, Name); + Result.Native = FindComponentInCDO(NativeClass, Name); + return Result; } -bool FWingActorComponent::CheckEditableComponent(UBlueprint *BP, const FWingActorComponent *Component) +bool UWingComponentReference::CheckValidParent(UWingComponentReference::FoundComponent FC) { - if ((!Component) || (Component->IsEmpty())) + if (FC.SCS && FC.Native) { - UWingServer::Printf(TEXT("Component does not exist.\n")); + UWingServer::Printf(TEXT("Weirdness, two components named %s\n"), + *WingUtils::SanitizeName(FC.Name)); return false; } - if (Component->IsNative()) + if ((FC.SCS == nullptr) && (FC.Native == nullptr)) { - UWingServer::Printf(TEXT("Cannot edit native components.\n")); + UWingServer::Printf(TEXT("No such parent component: %s\n"), + *WingUtils::SanitizeName(FC.Name)); return false; } - if (!Component->IsOwnedBy(BP)) + if (FC.Native && (!Cast(FC.Native))) { - UWingServer::Printf(TEXT("Component %s is inherited, to edit it, you must edit BP %s\n"), - *WingUtils::FormatName(*Component), *WingUtils::FormatName(Component->Owner)); + UWingServer::Printf(TEXT("Not a SceneComponent, so cannot be parent: %s\n"), + *WingUtils::SanitizeName(FC.Name)); return false; } return true; } -bool FWingActorComponent::CheckValidParent(const FWingActorComponent *Parent) +bool UWingComponentReference::CheckNoSuchComponent(FoundComponent FC) { - if ((!Parent) || (Parent->IsEmpty())) + if (FC.SCS || FC.Native) { - UWingServer::Printf(TEXT("Cannot create a component without a parent.\n")); - return false; - } - if (Parent->IsNative() && (!Parent->NativeComponent->GetClass()->IsChildOf(USceneComponent::StaticClass()))) - { - UWingServer::Printf(TEXT("Native component %s is not a scene component, cannot be a parent."), - *WingUtils::FormatName(Parent->NativeComponent)); + UWingServer::Printf(TEXT("A component named %s already exists"), + *WingUtils::SanitizeName(FC.Name)); return false; } return true; } -bool FWingActorComponent::CheckValidComponentClass(UClass *Class) +bool UWingComponentReference::CheckValidComponentClass(UClass *Class) { if (!Class->IsChildOf(UActorComponent::StaticClass())) { - UWingServer::Printf(TEXT("Cannot create a component of class %s, which is not an actor component.\n"), - *WingUtils::FormatName(Class)); + UWingServer::Printf(TEXT("Class does not derive from ActorComponent: %s\n"), + *WingUtils::FormatName(Class)); return false; } return true; } -void FWingActorComponent::AddNodeInternal(UBlueprint *BP, USCS_Node *NewNode, const FWingActorComponent *Parent) +void UWingComponentReference::AddChildNode(UBlueprint *BP, USCS_Node *NewNode, FoundComponent Parent) { - if (Parent->SCSNode && Parent->IsOwnedBy(BP)) + if (Parent.SCS) { - Parent->SCSNode->AddChildNode(NewNode, true); - } - else if (Parent->SCSNode) - { - NewNode->SetParent(Parent->SCSNode); - BP->SimpleConstructionScript->AddNode(NewNode); + if (Parent.SCS->GetSCS() == BP->SimpleConstructionScript) + { + Parent.SCS->AddChildNode(NewNode); + } + else + { + NewNode->SetParent(Parent.SCS); + BP->SimpleConstructionScript->AddNode(NewNode); + } } else { - NewNode->SetParent(Cast(Parent->NativeComponent)); + NewNode->SetParent(Cast(Parent.Native)); BP->SimpleConstructionScript->AddNode(NewNode); } } -USCS_Node *FWingActorComponent::AddComponent(UBlueprint *BP, UClass *Class, const FWingActorComponent* Parent, const FString &Name) +bool UWingComponentReference::AddComponent(UBlueprint *BP, UClass *Class, UWingComponentReference *Parent, FName Name) { - if (!CheckValidParent(Parent)) return nullptr; - if (!CheckValidComponentClass(Class)) return nullptr; + FoundComponent ExistingComponent = FindComponent(BP, Name); + if (!CheckNoSuchComponent(ExistingComponent)) return false; + FoundComponent ParentComponent = FindComponent(BP, Parent->VariableName); + if (!CheckValidParent(ParentComponent)) return false; + if (!CheckValidComponentClass(Class)) return false; - USCS_Node *NewNode = BP->SimpleConstructionScript->CreateNode(Class, FName(*Name)); + USCS_Node *NewNode = BP->SimpleConstructionScript->CreateNode(Class, Name); if (NewNode == nullptr) { UWingServer::Printf(TEXT("Could not create new component %s of class %s, unknown reason.\n"), - *Name, *WingUtils::FormatName(Class)); - return nullptr; - } - - AddNodeInternal(BP, NewNode, Parent); - return NewNode; -} - -bool FWingActorComponent::ReparentComponent(UBlueprint *BP, USCS_Node *SCSNode, const FWingActorComponent* Parent) -{ - FWingActorComponent Component(SCSNode); - if (!CheckEditableComponent(BP, &Component)) return false; - if (!CheckValidParent(Parent)) return false; - - if (Parent->SCSNode == SCSNode) - { - UWingServer::Printf(TEXT("You may not parent a component to itself.\n")); - return false; - } - if (Parent->SCSNode && Parent->SCSNode->IsChildOf(SCSNode)) - { - UWingServer::Printf(TEXT("You may not parent a component to one of its own children.\n")); + *WingUtils::SanitizeName(Name), *WingUtils::FormatName(Class)); return false; } - BP->SimpleConstructionScript->RemoveNode(SCSNode); - AddNodeInternal(BP, SCSNode, Parent); + AddChildNode(BP, NewNode, ParentComponent); return true; } - -TArray FWingActorComponent::GetAll(UBlueprint* BP) +bool UWingComponentReference::ReparentComponent(UWingComponentReference *Parent) { - TArray Result; - if (!BP) return Result; + FoundComponent ParentComponent = FindComponent(BP, Parent->VariableName); + if (!CheckValidParent(ParentComponent)) return false; + FoundComponent ThisComponent = FindComponent(BP, VariableName); - UClass* GenClass = BP->GeneratedClass; - if (!GenClass) return Result; - - // Native components from CDO - if (AActor* CDO = Cast(GenClass->GetDefaultObject())) + if (ThisComponent.SCS == nullptr) { - UClass* NativeClass = FBlueprintEditorUtils::FindFirstNativeClass(GenClass); - TArray NativeComponents; - CDO->GetComponents(NativeComponents); - for (UActorComponent* Comp : NativeComponents) - Result.Emplace(Comp, NativeClass); + UWingServer::Printf(TEXT("Cannot reparent native components.")); + return false; + } + if (ThisComponent.SCS->GetSCS() != BP->SimpleConstructionScript) + { + UWingServer::Printf(TEXT("Component belongs to blueprint %s, edit that blueprint to reparent it."), + *WingUtils::FormatName(ThisComponent.SCS->GetSCS()->GetBlueprint())); + return false; + } + if (ParentComponent.SCS && ParentComponent.SCS->IsChildOf(ThisComponent.SCS)) + { + UWingServer::Printf(TEXT("Cannot parent a component to itself or its own child.")); + return false; } - // 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) + BP->SimpleConstructionScript->RemoveNode(ThisComponent.SCS); + AddChildNode(BP, ThisComponent.SCS, ParentComponent); + return true; +} + +TMap UWingComponentReference::CalculateParentNames(USimpleConstructionScript *Script) +{ + TMap ParentNames; + for (USCS_Node* Node : Script->GetAllNodes()) { - UBlueprint* WalkBP = Hierarchy[i]; - if (!WalkBP->SimpleConstructionScript) continue; + ParentNames.Add(Node->GetVariableName(), Node->ParentComponentOrVariableName); + } + for (USCS_Node* Parent : Script->GetAllNodes()) + { + for (USCS_Node *Child : Parent->GetChildNodes()) + { + ParentNames[Child->GetVariableName()] = Parent->GetVariableName(); + } + } + return ParentNames; +} + +UActorComponent* UWingComponentReference::GetImmutableTemplate() const +{ + if (Native) return FindComponentInCDO(BP->GeneratedClass, VariableName); + USCS_Node *Node = FindSCSNodeByName(BP, VariableName); + if (Node == nullptr) return nullptr; + FComponentKey Key(Node); + + for (UBlueprint *WalkBP : WingUtils::GetAncestorBlueprints(BP, false)) + { + if (Node->GetSCS()->GetBlueprint() == WalkBP) return Node->ComponentTemplate; + UInheritableComponentHandler* ICH = WalkBP->GetInheritableComponentHandler(/*bCreateIfNecessary=*/ false); + if (!ICH) continue; + UActorComponent* Override = ICH->GetOverridenComponentTemplate(Key); + if (Override) return Override; + } + return nullptr; +} + +UActorComponent* UWingComponentReference::GetMutableTemplate() const +{ + if (Native) return FindComponentInCDO(BP->GeneratedClass, VariableName); + USCS_Node* Node = FindSCSNodeByName(BP, VariableName); + if (!Node) return nullptr; + if (Node->GetSCS()->GetBlueprint() == BP) return Node->ComponentTemplate; + + FComponentKey Key(Node); + UInheritableComponentHandler* ICH = BP->GetInheritableComponentHandler(/*bCreateIfNecessary=*/ true); + if (!ICH) return nullptr; + UActorComponent* Override = ICH->GetOverridenComponentTemplate(Key); + if (!Override) Override = ICH->CreateOverridenComponentTemplate(Key); + return Override; +} + +TArray UWingComponentReference::GetAll(UBlueprint* BP) +{ + TArray Result; + if (!BP) return Result; + + // Find the native ancestor class + UClass* NativeClass = FBlueprintEditorUtils::FindFirstNativeClass(BP->ParentClass); + + // Native components from the native ancestor's CDO + if (NativeClass) + { + if (AActor* CDO = Cast(NativeClass->GetDefaultObject())) + { + TArray NativeComponents; + CDO->GetComponents(NativeComponents); + for (UActorComponent* Comp : NativeComponents) + { + UWingComponentReference* Ref = NewObject(); + Ref->BP = BP; + Ref->VariableName = Comp->GetFName(); + Ref->TypeName = UWingTypes::TypeToText(Comp->GetClass()); + if (USceneComponent* Scene = Cast(Comp)) + if (USceneComponent* AttachParent = Scene->GetAttachParent()) + Ref->ParentName = WingUtils::SanitizeName(AttachParent->GetFName()); + Ref->Native = true; + Ref->Inherited = true; + Result.Add(Ref); + } + } + } + + // Visit all SCS nodes, oldest first. + for (UBlueprint *WalkBP : WingUtils::GetAncestorBlueprints(BP, true)) + { + if (WalkBP->SimpleConstructionScript == nullptr) continue; + TMap ParentNames = CalculateParentNames(WalkBP->SimpleConstructionScript); for (USCS_Node* Node : WalkBP->SimpleConstructionScript->GetAllNodes()) - Result.Emplace(Node); + { + UWingComponentReference* Ref = NewObject(); + Ref->BP = BP; + Ref->VariableName = Node->GetVariableName(); + Ref->TypeName = UWingTypes::TypeToText(Node->ComponentClass); + Ref->ParentName = WingUtils::SanitizeName(ParentNames[Node->GetVariableName()]); + Ref->Native = false; + Ref->Inherited = (WalkBP != BP); + Result.Add(Ref); + } } return Result; } + diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingFetcher.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingFetcher.cpp index 6f24d9af..b747da88 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingFetcher.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingFetcher.cpp @@ -302,25 +302,19 @@ WingFetcher& WingFetcher::Component(const FString& Value) return SetError(); } - TArray AllComponents = FWingActorComponent::GetAll(BP); - FWingActorComponent* Comp = WingUtils::FindExactlyOneNamed(Value, AllComponents, TEXT("component")); - if (!Comp) + TArray AllComponents = UWingComponentReference::GetAll(BP); + UWingComponentReference* Found = WingUtils::FindExactlyOneNamed(Value, AllComponents, TEXT("component")); + if (!Found) { UWingServer::Printf(TEXT("Components that exist in the blueprint:\n")); - for (const FWingActorComponent &C : AllComponents) + for (const UWingComponentReference* C : AllComponents) { UWingServer::Printf(TEXT(" %s\n"), *WingUtils::FormatName(C)); } return SetError(); } - if (!Comp->IsOwnedBy(BP)) - { - UWingServer::Printf(TEXT("ERROR: Component '%s' belongs to %s, to edit it, you must go through that blueprint.\n"), - *Comp->GetName(), *WingUtils::FormatName(Comp->Owner)); - return SetError(); - } - SetObj(Comp->SCSNode); + SetObj(Found); return *this; } diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp index e51e09d7..31b96ded 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp @@ -275,7 +275,7 @@ void WingGraphExport::EmitMaterialProperties(UEdGraphNode* Node, FStringBuilderB UMaterialExpression* Expression = MatNode->MaterialExpression; FString PrimaryCategory = Expression->GetClass()->GetName(); - TArray Props = FWingProperty::GetAll(Expression, CPF_Edit); + TArray Props = FWingProperty::GetDetailsMutable(Expression, CPF_Edit); for (const FWingProperty& WP : Props) { diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp index 3dfe12a9..63b78e67 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp @@ -1,4 +1,5 @@ #include "WingProperty.h" +#include "WingActorComponent.h" #include "WingUtils.h" #include "WingHandler.h" #include "WingServer.h" @@ -256,14 +257,12 @@ void FWingProperty::Move(TArray &Out, TArray &In, In.SetNum(Dst); } -TArray FWingProperty::GetAll(UObject* Obj, EPropertyFlags Flags) +TArray FWingProperty::GetDetailsGeneral(UObject* Obj, EPropertyFlags Flags, bool Mutable) { if (!Obj) return {}; // Blueprints don't have editable properties. So - // instead, we fetch properties from the generated CDO, - // which is probably what the user intended. - // + // instead, we fetch properties from the generated CDO. if (UBlueprint *BP = ::Cast(Obj)) { if (BP->GeneratedClass == nullptr) @@ -274,17 +273,15 @@ TArray FWingProperty::GetAll(UObject* Obj, EPropertyFlags Flags) Obj = BP->GeneratedClass->GetDefaultObject(); } - // SCS nodes don't have useful editable properties. - // Redirect to the component template instead. - // - if (USCS_Node* Node = ::Cast(Obj)) + // Component references: get the proper template. + if (UWingComponentReference* Ref = ::Cast(Obj)) { - if (!Node->ComponentTemplate) + Obj = Mutable ? Ref->GetMutableTemplate() : Ref->GetImmutableTemplate(); + if (!Obj) { - UWingServer::Printf(TEXT("ERROR: SCS node '%s' has no component template\n"), *Obj->GetName()); + UWingServer::Printf(TEXT("ERROR: Component '%s' has no template\n"), *Ref->VariableName.ToString()); return {}; } - Obj = Node->ComponentTemplate; } TArray Result; @@ -315,6 +312,11 @@ TArray FWingProperty::GetAll(UObject* Obj, EPropertyFlags Flags) return Result; } +TArray FWingProperty::GetAll(UObject* Object, EPropertyFlags Flags) +{ + return GetAll(Object->GetClass(), Object, Flags); +} + TArray FWingProperty::GetAll(UStruct* StructType, void* Container, EPropertyFlags Flags) { TArray Result; diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp index ed1d44d2..6ed6e6bf 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp @@ -269,9 +269,9 @@ FString WingUtils::FormatName(const FBPInterfaceDescription &IFace) return FormatName(IFace.Interface); } -FString WingUtils::FormatName(const FWingActorComponent &Comp) +FString WingUtils::FormatName(const UWingComponentReference *Ref) { - return SanitizeName(Comp.GetName()); + return SanitizeName(Ref->VariableName); } FString WingUtils::FormatName(const UWidget *Widget) @@ -473,6 +473,18 @@ TArray WingUtils::AllNodes(UEdGraph *Graph) return Result; } +TArray WingUtils::GetAncestorBlueprints(UBlueprint *BP, bool OldestFirst) +{ + TArray Blueprints; + for (UBlueprint* WalkBP = BP; WalkBP; ) + { + Blueprints.Add(WalkBP); + WalkBP = UBlueprint::GetBlueprintFromClass(WalkBP->ParentClass); + } + if (OldestFirst) Algo::Reverse(Blueprints); + return Blueprints; +} + // ============================================================ // Material helpers // ============================================================ diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingActorComponent.h b/Plugins/UEWingman/Source/UEWingman/Public/WingActorComponent.h index 85799521..23a5b54d 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingActorComponent.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingActorComponent.h @@ -6,42 +6,62 @@ class UBlueprint; class USCS_Node; class UActorComponent; +class USimpleConstructionScript; -USTRUCT() -struct FWingActorComponent +// a 'Component Reference' is basically just a pointer to a +// blueprint plus a component name. The other fields in here +// are purely for convenience of printing things out, they +// are *never* used for any analysis. +// +UCLASS() +class UWingComponentReference : public UObject { GENERATED_BODY() - USCS_Node* SCSNode = nullptr; - UActorComponent* NativeComponent = nullptr; - UClass* Owner = nullptr; +public: + // The blueprint that was queried: not necessarily the + // same blueprint that defined the component. + UPROPERTY() + UBlueprint* BP = nullptr; + + // The raw component name, not sanitized yet. + FName VariableName; - FWingActorComponent() = default; - FWingActorComponent(USCS_Node* Node); - FWingActorComponent(UActorComponent* InNative, UClass* InOwner) : NativeComponent(InNative), Owner(InOwner) {} + // Sanitized Parent Name (for display only) + FString ParentName; - explicit operator bool() const { return SCSNode || NativeComponent; } + // Sanitized TypeName of the component (for display only) + FString TypeName; - FString GetName() const; - FString GetClassName() const; - FString GetParentName() const; - bool IsEmpty() const { return SCSNode == nullptr && NativeComponent == nullptr; } - bool IsNative() const { return NativeComponent != nullptr; } - bool IsOwnedBy(const UBlueprint* BP) const; + // True if the component is native (for display only) + bool Native = false; - // Add a new SCS node, underneath the existing parent. - static USCS_Node *AddComponent(UBlueprint *BP, UClass *Class, const FWingActorComponent* Parent, const FString &Name); + // True if the component was inherited (for display only) + bool Inherited = false; - // Reparent an existing SCS node to a specified parent. - static bool ReparentComponent(UBlueprint *BP, USCS_Node *Component, const FWingActorComponent *Parent); + UActorComponent* GetImmutableTemplate() const; + UActorComponent* GetMutableTemplate() const; + bool ReparentComponent(UWingComponentReference *Parent); + bool DeleteComponent(); + static bool AddComponent(UBlueprint *BP, UClass *Class, UWingComponentReference *Parent, FName Name); - // Collect all components: native from CDO first, then SCS nodes - // walking the hierarchy from oldest ancestor to current blueprint. - static TArray GetAll(UBlueprint* BP); + static TArray GetAll(UBlueprint* BP); -private: - static bool CheckEditableComponent(UBlueprint *BP, const FWingActorComponent *Component); - static bool CheckValidParent(const FWingActorComponent *Component); static bool CheckValidComponentClass(UClass *Class); - static void AddNodeInternal(UBlueprint *BP, USCS_Node *NewNode, const FWingActorComponent *Parent); +private: + struct FoundComponent + { + FName Name; + USCS_Node *SCS; + UActorComponent *Native; + }; + + static UActorComponent *FindComponentInCDO(UClass *Class, FName Name); + static USCS_Node* FindSCSNodeByName(UBlueprint *BP, FName VariableName); + static FoundComponent FindComponent(UBlueprint *BP, FName Name); + static bool CheckValidParent(FoundComponent FC); + static bool CheckNoSuchComponent(FoundComponent FC); + static void AddChildNode(UBlueprint *BP, USCS_Node *Node, FoundComponent Parent); + static TMap CalculateParentNames(USimpleConstructionScript *Script); }; + diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h b/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h index 635d7315..d661dee9 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h @@ -28,19 +28,53 @@ struct FWingProperty explicit operator bool() const { return Prop != nullptr; } FProperty* operator->() const { return Prop; } - static TArray GetAll(UObject* Obj, EPropertyFlags Flags); + // Return the "Details Panel" properties for the specified object. + // + // This is the set of properties that would typically appear in the + // details panel if you were to click the object in the editor. Note + // that this is not always the properties of the object itself. For + // example, if you click a widget, the details panel shows you + // properties of the widget, but also properties of the slot. If you + // click a material graph node, the details panel shows you properties + // of the node, but also properties of the material expression. + // + // When editing an inherited ActorComponent, you're actually editing + // properties that *override* the original properties of the ActorComponent. + // The mutable version, 'GetDetailsMutable', tells this function to first + // create the overrides, and then edit those. + // + // For a more direct "just give me the properties of this object," + // use the GetAll function. + // + static TArray GetDetailsMutable(UObject* Obj, EPropertyFlags Flags) + { return GetDetailsGeneral(Obj, Flags, true); } + static TArray GetDetailsImmutable(UObject *Obj, EPropertyFlags Flags) + { return GetDetailsGeneral(Obj, Flags, false); } + + // Get the raw properties of the specified object or struct. + // + // This gets the properties that are literally present in the + // specified object or struct. No special interpretation is done. + // + static TArray GetAll(UObject* Object, EPropertyFlags Flags); static TArray GetAll(UStruct* StructType, void* Container, EPropertyFlags Flags); + + // Functions to find items by name in an array of properties. + // static TArray FindAllSubstring(const TArray& Props, const FString& Substring); static FWingProperty FindOneExactMatch(const TArray& Props, const FString& Name); static void Remove(TArray& Props, const FString& Name); static void Move(TArray &Out, TArray &In, const FString &Name); + // Functions to populate properties from a JSON object. + // static bool PopulateFromJson(FWingProperty& Prop, const FJsonObject* Json, bool AllOptional = false); static bool PopulateFromJson(TArray& Props, const FJsonObject* Json, bool AllOptional = false); static bool PopulateFromJson(UStruct* StructType, void* Container, const FJsonObject* Object); static bool PopulateFromJson(UStruct* StructType, void* Container, const TSharedPtr& Object); private: + static TArray GetDetailsGeneral(UObject* Obj, EPropertyFlags Flags, bool Mutable); void PrintExpectsReceived(const TCHAR *Type); static void Collect(UStruct* Struct, void* Container, TArray &Props, EPropertyFlags Flags); }; diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h b/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h index a65879d4..f0e5f794 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h @@ -34,6 +34,7 @@ struct FBPVariableDescription; struct FUserPinInfo; struct FBPInterfaceDescription; struct FWingActorComponent; +class UWingComponentReference; // Stateless utility functions used by MCP handlers and the MCP server. // This is effectively a namespace — all methods are static. @@ -80,6 +81,7 @@ public: static FString FormatName(const FUserPinInfo &Pin); static FString FormatName(const FBPInterfaceDescription &IFace); static FString FormatName(const FWingActorComponent &Comp); + static FString FormatName(const UWingComponentReference *Ref); static FString FormatName(const UWidget *Widget); //////////////////////////////////////////////////////// @@ -208,7 +210,7 @@ public: static TArray AllGraphs(UBlueprint* BP); static TArray AllNodes(UBlueprint* BP); static TArray AllNodes(UEdGraph *Graph); - + static TArray GetAncestorBlueprints(UBlueprint *BP, bool OldestFirst = false); // ----- Material helpers ----- static void EnsureMaterialGraph(UMaterial* Material); diff --git a/integration-MEMORY.md b/integration-MEMORY.md new file mode 100644 index 00000000..e1d49960 --- /dev/null +++ b/integration-MEMORY.md @@ -0,0 +1,56 @@ +## PATIENCE! + +The user likes to take his time. Do not start doing things until he gives +you explicit instructions! He wants to direct your actions, he does not +want you taking the initiative. + +## Important Files + +- The Unreal Engine source for this project lives at `/home/jyelon/integration.UE` (sibling to the project dir), not `~/UnrealEngine`. +- Many of Unreal's basic data types (FString, TArray, TSet, TMap, etc.) are in `integration.UE/Engine/Source/Runtime/Core/Public/Containers/`. +- Editor log files are at `integration/User/jyelon/Saved/Logs/`. + +## Critical: Minimize Diff Noise + +- The user reviews all changes via `git diff` in VS Code. Every unnecessary diff line costs review time. +- **Preserve all existing comments.** Comments describe the logic, not the syntax — they survive refactors. Only remove a comment if the code it described was genuinely deleted. +- **Don't rename variables, reorder code, or reformat lines** unless directly required by the task. If a variable name works, leave it. If a comment is in the right place, don't move it. +- Don't make gratuitous changes. If it doesn't need to change, don't touch it. + +## Coding Style Preferences + +- **NO GLOBALLY SCOPED STATIC FUNCTIONS in Unreal code.** Use class methods or namespace-scoped functions instead. This is a hard rule — the user has corrected this multiple times. +- Prefer in-class member initializers (`int x_ = 0;` in the header) over explicit initialization in constructor bodies. When touching constructors, migrate toward this style. +- Inline empty constructors/destructors in the header when possible. +- Avoid ternary operator unless very concise (e.g., `x ? 1 : 0`). For longer expressions, use early-return `if` instead. +- When asked for a small change, do exactly that. If it turns out to require broader refactoring, STOP and explain the situation instead of silently rewriting the module. + +## Tools + +- `tools/clangd-query.py` — Query clangd for C++ symbols. + - `python3 tools/clangd-query.py symbol ` — Find symbols by name across the whole project. + - `python3 tools/clangd-query.py definition ` — Go to definition + - `python3 tools/clangd-query.py references ` — Find all references + - `python3 tools/clangd-query.py diagnostics ` — Get errors/warnings for a file. + - `python3 tools/clangd-query.py stop` — Stop the daemon. + - Prefer this over grep when looking for C++ class/function definitions or references. + +- Use `python3 build.py c++` for lightweight C++ rebuilds, `python3 build.py all` for full rebuilds. + +- VS Code is the IDE. Use `code --goto :` to open files. + +## MCP + +The UE Wingman plugin for unreal allows you to control the unreal editor. Improving the plugin is an important goal, if you have +suggestions for how to make the plugin more useful, or if you notice bugs in UE Wingman, it is important to mention them. + +## Feedback + +- [Use mv for file renames](feedback_use_mv_for_renames.md) — use `mv` instead of copy-and-rewrite when renaming files + +## PATIENCE! AGAIN! + +You really do like solving problems, but this repo belongs to the +user. Remember, you are a guest here. No altering anything without +his direct instructions. +