From 90b35f785e5b484e2ca5106a6eb80e3630af4239 Mon Sep 17 00:00:00 2001 From: jyelon Date: Fri, 20 Mar 2026 19:40:29 -0400 Subject: [PATCH] More refactoring in MCP --- .../HalfBaked/BlueprintGraph_Create.h | 2 +- .../HalfBaked/Blueprint_AddEventDispatcher.h | 2 +- .../Handlers/BlueprintComponent_Add.h | 12 +-- .../Handlers/BlueprintComponent_Reparent.h | 2 +- .../Handlers/BlueprintDispatcher_Create.h | 47 +++++----- .../Handlers/BlueprintDispatcher_Dump.h | 19 ++-- .../Handlers/BlueprintVariable_Create.h | 12 ++- .../Handlers/Blueprint_RemoveInterface.h | 2 +- .../UEWingman/Private/WingBlueprintVar.cpp | 87 +++---------------- .../Source/UEWingman/Private/WingFetcher.cpp | 4 +- .../UEWingman/Private/WingFunctionArgs.cpp | 11 +++ .../Source/UEWingman/Private/WingProperty.cpp | 17 ++++ .../Source/UEWingman/Private/WingUtils.cpp | 45 ++++++---- .../UEWingman/Public/WingBlueprintVar.h | 7 -- .../UEWingman/Public/WingFunctionArgs.h | 5 +- .../Source/UEWingman/Public/WingProperty.h | 3 +- .../Source/UEWingman/Public/WingUtils.h | 73 ++++++++-------- 17 files changed, 166 insertions(+), 184 deletions(-) diff --git a/Plugins/UEWingman/Source/UEWingman/HalfBaked/BlueprintGraph_Create.h b/Plugins/UEWingman/Source/UEWingman/HalfBaked/BlueprintGraph_Create.h index 298c0db7..875b069e 100644 --- a/Plugins/UEWingman/Source/UEWingman/HalfBaked/BlueprintGraph_Create.h +++ b/Plugins/UEWingman/Source/UEWingman/HalfBaked/BlueprintGraph_Create.h @@ -51,7 +51,7 @@ public: if (!BP) return; // Check graph name uniqueness - if (!WingUtils::FindExactlyNoneNamed(Graph, WingUtils::AllGraphs(BP))) + if (!WingUtils::FindExactlyNoneNamed(Graph, WingUtils::AllGraphs(BP), TEXT("Graph"))) return; // For custom events, also check for existing custom events with the same name diff --git a/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_AddEventDispatcher.h b/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_AddEventDispatcher.h index d9651903..4d59f9ef 100644 --- a/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_AddEventDispatcher.h +++ b/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_AddEventDispatcher.h @@ -71,7 +71,7 @@ public: } // Check against existing graphs (functions, macros, etc.) - if (!WingUtils::FindExactlyNoneNamed(DispatcherName, WingUtils::AllGraphs(BP))) + if (!WingUtils::FindExactlyNoneNamed(DispatcherName, WingUtils::AllGraphs(BP), TEXT("Graph"))) return; // Add a member variable with PC_MCDelegate pin type diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintComponent_Add.h b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintComponent_Add.h index 4819a9b1..6f19766c 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintComponent_Add.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintComponent_Add.h @@ -56,22 +56,22 @@ public: return; } - // Check for duplicate component names (including native/inherited) + // Check that the proposed name is valid TArray AllComponents = FWingActorComponent::GetAll(BP); - if (!WingUtils::FindExactlyNoneNamed(Component, AllComponents)) - return; + if (!WingUtils::FindExactlyNoneNamed(Component, AllComponents, TEXT("Component"))) return; + FString InternalName = WingUtils::CheckProposedName(Component); + if (InternalName.IsEmpty()) return; // Resolve the component class by name UClass* ComponentClass = UWingTypes::TextToOneObjectType(Class); if (!ComponentClass) return; // Find the specified parent component - FWingActorComponent* ParentComp = WingUtils::FindExactlyOneNamed(Parent, AllComponents); + FWingActorComponent* ParentComp = WingUtils::FindExactlyOneNamed(Parent, AllComponents, TEXT("Component")); if (!ParentComp) return; // Create the SCS node - FString NewName = WingUtils::UnsanitizeName(Component); - USCS_Node *NewNode = FWingActorComponent::AddComponent(BP, ComponentClass, ParentComp, NewName); + 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"), diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintComponent_Reparent.h b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintComponent_Reparent.h index 7d1acdfc..bec03da7 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintComponent_Reparent.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintComponent_Reparent.h @@ -44,7 +44,7 @@ public: // Find the new parent among all components (if specified) UBlueprint *BP = SCS->GetBlueprint(); TArray AllComponents = FWingActorComponent::GetAll(BP); - FWingActorComponent* NewParent = WingUtils::FindExactlyOneNamed(Parent, AllComponents); + FWingActorComponent* NewParent = WingUtils::FindExactlyOneNamed(Parent, AllComponents, TEXT("Component")); if (!NewParent) return; if (!FWingActorComponent::ReparentComponent(BP, diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintDispatcher_Create.h b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintDispatcher_Create.h index 4e0d275e..cf8a563e 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintDispatcher_Create.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintDispatcher_Create.h @@ -5,7 +5,7 @@ #include "WingHandler.h" #include "WingFetcher.h" #include "WingJson.h" -#include "WingBlueprintVar.h" +#include "WingFunctionArgs.h" #include "WingUtils.h" #include "Engine/Blueprint.h" #include "EdGraphSchema_K2.h" @@ -29,12 +29,12 @@ public: UPROPERTY(meta=(Description="Name of the new event dispatcher")) FString Name; - UPROPERTY(meta=(Optional, Description="Configuration: DelegateArgs, Category, Description, InstanceEditable, BlueprintReadOnly, Private, etc.")) - FWingJsonObject Config; + UPROPERTY(meta=(Description="Arguments expressed as: int x,float y")) + FString Arguments; virtual FString GetDescription() const override { - return TEXT("Add a new event dispatcher to a Blueprint. Pass Config to set parameters, category, flags, etc."); + return TEXT("Add a new event dispatcher to a Blueprint."); } virtual void Handle() override @@ -43,21 +43,15 @@ public: UBlueprint* BP = F.Walk(Blueprint).Cast(); if (!BP) return; - // Check for duplicate variable name - FName VarFName(*WingUtils::UnsanitizeName(Name)); - if (FBlueprintEditorUtils::FindNewVariableIndex(BP, VarFName) != INDEX_NONE) - { - UWingServer::Printf(TEXT("ERROR: Variable or dispatcher '%s' already exists in %s\n"), *Name, *WingUtils::FormatName(BP)); - return; - } - for (UEdGraph* Graph : WingUtils::AllGraphs(BP)) - { - if (Graph->GetFName() == VarFName) - { - UWingServer::Printf(TEXT("ERROR: A graph named '%s' already exists in %s\n"), *Name, *WingUtils::FormatName(BP)); - return; - } - } + // Check for valid proposed name + if (!WingUtils::FindExactlyNoneNamed(Name, BP->NewVariables, TEXT("Variable"))) return; + if (!WingUtils::FindExactlyNoneNamed(Name, WingUtils::AllGraphs(BP), TEXT("Graph"))) return; + FString InternalName = WingUtils::CheckProposedName(Name); + if (InternalName.IsEmpty()) return; + FName VarFName(InternalName); + + // Make sure the argument types are valid. + if (!WingFunctionArgs::CheckArgs(Arguments)) return; // Add the delegate variable FEdGraphPinType DelegateType; @@ -85,15 +79,16 @@ public: K2Schema->MarkFunctionEntryAsEditable(SigGraph, true); BP->DelegateSignatureGraphs.Add(SigGraph); - // Find the newly created variable and apply config - FWingBlueprintVar Editor(BP, Name); - if (Editor.NotFound()) return; - - if (Config.Json && Config.Json->Values.Num() > 0) + // Store the function arguments + TWeakObjectPtr EntryNode; + TWeakObjectPtr ResultNode; + FBlueprintEditorUtils::GetEntryAndResultNodes(SigGraph, EntryNode, ResultNode); + if (!EntryNode.IsValid()) { - if (!Editor.ApplyJson(Config.Json.Get())) - return; + UWingServer::Print(TEXT("ERROR: Entry node not found in delegate signature graph\n")); + return; } + if (!WingFunctionArgs::SetArgs(EntryNode.Get(), Arguments)) return; UWingServer::Printf(TEXT("Created event dispatcher %s in %s\n"), *Name, *WingUtils::FormatName(BP)); } diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintDispatcher_Dump.h b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintDispatcher_Dump.h index d1e06d89..7397df6b 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintDispatcher_Dump.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintDispatcher_Dump.h @@ -7,6 +7,8 @@ #include "WingUtils.h" #include "WingBlueprintVar.h" #include "Engine/Blueprint.h" +#include "WingFunctionArgs.h" +#include "Kismet2/BlueprintEditorUtils.h" #include "BlueprintDispatcher_Dump.generated.h" @@ -37,15 +39,22 @@ public: UBlueprint* BP = F.Walk(Blueprint).Cast(); if (!BP) return; - FWingBlueprintVar Editor(BP, Dispatcher); - if (Editor.NotFound()) return; - if (!Editor.IsEventDispatcher()) + FBPVariableDescription* Var = WingUtils::FindExactlyOneNamed(Dispatcher, BP->NewVariables, TEXT("Dispatcher")); + if (!Var) return; + TObjectPtr* SigGraph = WingUtils::FindExactlyOneNamed(Dispatcher, BP->DelegateSignatureGraphs, TEXT("Dispatcher Signature Graph")); + if (!SigGraph) return; + + TWeakObjectPtr EntryNode; + TWeakObjectPtr ResultNode; + FBlueprintEditorUtils::GetEntryAndResultNodes(*SigGraph, EntryNode, ResultNode); + if (!EntryNode.IsValid()) { - UWingServer::Printf(TEXT("ERROR: '%s' is not an event dispatcher\n"), *Dispatcher); + UWingServer::Print(TEXT("ERROR: Entry node not found in delegate signature graph\n")); return; } + FString Args = WingFunctionArgs::GetArgs(EntryNode.Get()); UWingServer::Printf(TEXT("Event dispatcher %s in %s:\n"), *Dispatcher, *WingUtils::FormatName(BP)); - Editor.Dump(); + UWingServer::Printf(TEXT(" Args: %s\n"), *Args); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Create.h b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Create.h index cb91332c..73532a1c 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Create.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintVariable_Create.h @@ -45,13 +45,11 @@ public: UBlueprint* BP = F.Walk(Blueprint).Cast(); if (!BP) return; - // Check for duplicate variable name - FName VarFName(*WingUtils::UnsanitizeName(Name)); - if (FBlueprintEditorUtils::FindNewVariableIndex(BP, VarFName) != INDEX_NONE) - { - UWingServer::Printf(TEXT("ERROR: Variable '%s' already exists in %s\n"), *Name, *WingUtils::FormatName(BP)); - return; - } + // Check validity of the proposed name + if (!WingUtils::FindExactlyNoneNamed(Name, BP->NewVariables, TEXT("Variable"))) return; + FString InternalName = WingUtils::CheckProposedName(Name); + if (InternalName.IsEmpty()) return; + FName VarFName(InternalName); // Add the variable with a default type FEdGraphPinType DefaultType; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_RemoveInterface.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_RemoveInterface.h index de9547f1..fd51fbaf 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_RemoveInterface.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_RemoveInterface.h @@ -43,7 +43,7 @@ public: // Find the interface by name FBPInterfaceDescription *IFaceDesc = - WingUtils::FindExactlyOneNamed(Interface, BP->ImplementedInterfaces); + WingUtils::FindExactlyOneNamed(Interface, BP->ImplementedInterfaces, TEXT("Interface")); if (!IFaceDesc) return; UClass* FoundInterface = IFaceDesc->Interface; diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingBlueprintVar.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingBlueprintVar.cpp index 40d4c403..904865ba 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingBlueprintVar.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingBlueprintVar.cpp @@ -1,46 +1,29 @@ #include "WingBlueprintVar.h" -#include "WingFunctionArgs.h" #include "WingJson.h" #include "WingServer.h" #include "WingTypes.h" #include "WingUtils.h" #include "EdGraphSchema_K2.h" -#include "K2Node_EditablePinBase.h" #include "Kismet2/BlueprintEditorUtils.h" FWingBlueprintVar::FWingBlueprintVar(UBlueprint* BP, const FString& VarName) { - Desc = WingUtils::FindExactlyOneNamed(VarName, BP->NewVariables); + Desc = WingUtils::FindExactlyOneNamed(VarName, BP->NewVariables, TEXT("Variable")); if (!Desc) return; - if (Desc->VarType.PinCategory == UEdGraphSchema_K2::PC_MCDelegate) { - // Find the matching signature graph by name. - for (UEdGraph* Graph : BP->DelegateSignatureGraphs) - { - if (Graph->GetFName() == Desc->VarName) - { - SigGraph = Graph; - break; - } - } - if (!SigGraph) - { - UWingServer::Printf(TEXT("ERROR: Signature graph not found for event dispatcher '%s'\n"), *VarName); - Desc = nullptr; - return; - } + UWingServer::Printf(TEXT("Cannot edit event dispatchers using BlueprintVariable functions.\n")); + Desc = nullptr; + return; } - else + + // Try to find the default value property on the CDO. + if (BP->GeneratedClass) { - // Try to find the default value property on the CDO. - if (BP->GeneratedClass) - { - UObject* CDO = BP->GeneratedClass->GetDefaultObject(); - FProperty* Prop = BP->GeneratedClass->FindPropertyByName(Desc->VarName); - if (CDO && Prop) - DefaultValueProp = FWingProperty(Prop, CDO); - } + UObject* CDO = BP->GeneratedClass->GetDefaultObject(); + FProperty* Prop = BP->GeneratedClass->FindPropertyByName(Desc->VarName); + if (CDO && Prop) + DefaultValueProp = FWingProperty(Prop, CDO); } } @@ -48,7 +31,6 @@ void FWingBlueprintVar::Dump() { LoadFlags(); LoadDefault(); - LoadDelegateArgs(); TArray Props = MergedProperties(); for (FWingProperty& P : Props) { @@ -62,7 +44,6 @@ void FWingBlueprintVar::Dump() bool FWingBlueprintVar::ApplyJson(const FJsonObject* Json) { bool bHasDefault = Json->HasField(TEXT("DefaultValue")); - bool bHasDelegateArgs = Json->HasField(TEXT("DelegateArgs")); bool bHasType = Json->HasField(TEXT("VarType")); if (bHasDefault && bHasType) { @@ -74,7 +55,6 @@ bool FWingBlueprintVar::ApplyJson(const FJsonObject* Json) } LoadFlags(); - LoadDelegateArgs(); TArray Props = MergedProperties(); if (!WingJson::PopulateFromJson(Props, Json, true)) @@ -82,9 +62,7 @@ bool FWingBlueprintVar::ApplyJson(const FJsonObject* Json) SaveFlags(); if (bHasDefault) - if (!SaveDefault()) return false; - if (bHasDelegateArgs) - if (!SaveDelegateArgs()) return false; + return SaveDefault(); return true; } @@ -110,18 +88,6 @@ void FWingBlueprintVar::LoadDefault() DefaultValue.Empty(); } -void FWingBlueprintVar::LoadDelegateArgs() -{ - if (!SigGraph) return; - TWeakObjectPtr EntryNode; - TWeakObjectPtr ResultNode; - FBlueprintEditorUtils::GetEntryAndResultNodes(SigGraph, EntryNode, ResultNode); - if (EntryNode.IsValid()) - DelegateArgs = WingFunctionArgs::GetArgs(EntryNode.Get()); - else - DelegateArgs.Empty(); -} - void FWingBlueprintVar::SaveFlags() { // CPF flags @@ -165,20 +131,6 @@ bool FWingBlueprintVar::SaveDefault() return true; } -bool FWingBlueprintVar::SaveDelegateArgs() -{ - if (!SigGraph) return true; - TWeakObjectPtr EntryNode; - TWeakObjectPtr ResultNode; - FBlueprintEditorUtils::GetEntryAndResultNodes(SigGraph, EntryNode, ResultNode); - if (!EntryNode.IsValid()) - { - UWingServer::Print(TEXT("ERROR: Entry node not found in delegate signature graph\n")); - return false; - } - return WingFunctionArgs::SetArgs(EntryNode.Get(), DelegateArgs); -} - TArray FWingBlueprintVar::MergedProperties() { TArray Props = FWingProperty::GetAll( @@ -193,20 +145,9 @@ TArray FWingBlueprintVar::MergedProperties() Props.Append(FWingProperty::GetAll( FWingBlueprintVar::StaticStruct(), this, (EPropertyFlags)0)); - if (SigGraph) - { - FWingProperty::Remove(Props, TEXT("VarType")); + // Remove DefaultValue if we don't have a CDO property to back it. + if (!DefaultValueProp) FWingProperty::Remove(Props, TEXT("DefaultValue")); - FWingProperty::Remove(Props, TEXT("ExposeOnSpawn")); - FWingProperty::Remove(Props, TEXT("ExposeToCinematics")); - } - else - { - FWingProperty::Remove(Props, TEXT("DelegateArgs")); - // Remove DefaultValue if we don't have a CDO property to back it. - if (!DefaultValueProp) - FWingProperty::Remove(Props, TEXT("DefaultValue")); - } return Props; } diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingFetcher.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingFetcher.cpp index cb072546..2b0deb64 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingFetcher.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingFetcher.cpp @@ -195,7 +195,7 @@ WingFetcher& WingFetcher::Graph(const FString& Value) if (!BP) return TypeMismatch(TEXT("graph"), TEXT("Blueprint or Material")); - UEdGraph* Graph = WingUtils::FindExactlyOneNamed(Value, WingUtils::AllGraphs(BP)); + UEdGraph* Graph = WingUtils::FindExactlyOneNamed(Value, WingUtils::AllGraphs(BP), TEXT("Graph")); if (!Graph) return SetError(); SetObj(Graph); @@ -300,7 +300,7 @@ WingFetcher& WingFetcher::Component(const FString& Value) return TypeMismatch(TEXT("component"), TEXT("Blueprint")); TArray AllComponents = FWingActorComponent::GetAll(BP); - FWingActorComponent* Comp = WingUtils::FindExactlyOneNamed(Value, AllComponents); + FWingActorComponent* Comp = WingUtils::FindExactlyOneNamed(Value, AllComponents, TEXT("Component")); if (!Comp) return SetError(); if (!Comp->IsOwnedBy(BP)) { diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingFunctionArgs.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingFunctionArgs.cpp index b76025c9..abd2d0e7 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingFunctionArgs.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingFunctionArgs.cpp @@ -106,3 +106,14 @@ bool WingFunctionArgs::SetArgs(UEdGraphNode* Node, const FString& Args) Editable->ReconstructNode(); return true; } + +bool WingFunctionArgs::CheckArgs(const FString &Args) +{ + TArray NewArgs; + if (!ParseArgs(Args, NewArgs)) + { + UWingServer::Printf(TEXT("Invalid function arguments: %s\n"), *Args); + return false; + } + return true; +} diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp index bf1546fa..3b4dd8f4 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingProperty.cpp @@ -121,6 +121,23 @@ void FWingProperty::Remove(TArray& Props, const FString& Name) Props.RemoveAll([&](const FWingProperty& P) { return P.Prop->GetName() == Name; }); } +void FWingProperty::Move(TArray &Out, TArray &In, const FString &Name) +{ + int Dst = 0; + for (int i = 0; i < In.Num(); i++) + { + if (In[i]->GetName() == Name) + { + Out.Add(In[i]); + } + else + { + In[Dst++] = In[i]; + } + } + In.SetNum(Dst); +} + TArray FWingProperty::GetAll(UObject* Obj, EPropertyFlags Flags) { if (!Obj) return {}; diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp index 01dabf0d..2d88c416 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp @@ -70,15 +70,18 @@ FString WingUtils::SanitizeName(const FString &InName) FString WingUtils::UnsanitizeName(const FString &InName) { FString Name = InName; - for (int32 i = 0; i < Name.Len(); i++) + int32 Dst = 0; + for (int32 Src = 0; Src < Name.Len(); Src++) { - TCHAR c = Name[i]; + TCHAR c = Name[Src]; + if (c < 0x20 || c == 0x7F) continue; if (c == L'·') c=' '; if (c == L'◁') c='<'; if (c == L'▷') c='>'; if (c == L'▾') c=','; - Name[i] = c; + Name[Dst++] = c; } + Name.LeftInline(Dst); return Name; } @@ -113,6 +116,17 @@ FString WingUtils::StandardizeMenuItem(const FString &Item) return Sanitized; } +FString WingUtils::CheckProposedName(const FString &Name) +{ + FString Unsanitized = UnsanitizeName(Name); + if ((Unsanitized.IsEmpty()) || (SanitizeName(Unsanitized) != Name)) + { + UWingServer::Printf(TEXT("Names must not contain control characters or be empty\n")); + return FString(); + } + return Unsanitized; +} + // ============================================================ // Name Lookup // ============================================================ @@ -142,6 +156,11 @@ FString WingUtils::FormatName(const UEdGraph *Graph) return SanitizeName(Graph->GetName()); } +FString WingUtils::FormatName(const TObjectPtr &Graph) +{ + return SanitizeName(Graph->GetName()); +} + FString WingUtils::FormatName(const UEdGraphNode* Node) { return SanitizeName(Node->GetName()); @@ -335,41 +354,31 @@ bool WingUtils::StringToEnum(UEnum* Enum, const FString& Str, int64& OutValue) // Common Error Reporting // ============================================================ -bool WingUtils::CheckExactlyOneNamed(int Count, const FString &Kind, const FString &Name) +bool WingUtils::CheckExactlyOneNamed(int Count, const TCHAR *Kind, const FString &Name) { if (Count == 0) { - UWingServer::Printf(TEXT("Could not find a %s named %s.\n"), *Kind, *Name); + UWingServer::Printf(TEXT("Could not find a %s named %s.\n"), Kind, *Name); return false; } if (Count > 1) { - UWingServer::Printf(TEXT("More than one %s named %s\n"), *Kind, *Name); + UWingServer::Printf(TEXT("More than one %s named %s\n"), Kind, *Name); return false; } return true; } -bool WingUtils::CheckExactlyOneNamed(int Count, UClass *Class, const FString &Name) -{ - return CheckExactlyOneNamed(Count, Class->GetName(), Name); -} - -bool WingUtils::CheckExactlyNoneNamed(int Count, const FString &Kind, const FString &Name) +bool WingUtils::CheckExactlyNoneNamed(int Count, const TCHAR *Kind, const FString &Name) { if (Count > 0) { - UWingServer::Printf(TEXT("A %s named %s already exists."), *Kind, *Name); + UWingServer::Printf(TEXT("A %s named %s already exists."), Kind, *Name); return false; } return true; } -bool WingUtils::CheckExactlyNoneNamed(int Count, UClass *Class, const FString &Name) -{ - return CheckExactlyNoneNamed(Count, Class->GetName(), Name); -} - // ============================================================ // Blueprint helpers // ============================================================ diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingBlueprintVar.h b/Plugins/UEWingman/Source/UEWingman/Public/WingBlueprintVar.h index 27c2b047..244aac12 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingBlueprintVar.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingBlueprintVar.h @@ -16,13 +16,11 @@ struct FWingBlueprintVar FBPVariableDescription* Desc = nullptr; FWingProperty DefaultValueProp; - UEdGraph* SigGraph = nullptr; FWingBlueprintVar() = default; FWingBlueprintVar(UBlueprint* BP, const FString& VarName); bool NotFound() const { return Desc == nullptr; } - bool IsEventDispatcher() const { return SigGraph != nullptr; } UPROPERTY(EditAnywhere, meta=(Optional, Description="Default value in Unreal text format")) FString DefaultValue; @@ -45,9 +43,6 @@ struct FWingBlueprintVar UPROPERTY(EditAnywhere, meta=(Optional, Description="Expose to cinematics/sequencer")) bool ExposeToCinematics = false; - UPROPERTY(EditAnywhere, meta=(Optional, Description="Delegate parameters, e.g. 'int x,float y'")) - FString DelegateArgs; - // Load from Desc, populate from JSON, save back to Desc. bool ApplyJson(const FJsonObject* Json); @@ -57,9 +52,7 @@ struct FWingBlueprintVar private: void LoadFlags(); void LoadDefault(); - void LoadDelegateArgs(); void SaveFlags(); bool SaveDefault(); - bool SaveDelegateArgs(); TArray MergedProperties(); }; diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingFunctionArgs.h b/Plugins/UEWingman/Source/UEWingman/Public/WingFunctionArgs.h index 936326d3..c1966560 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingFunctionArgs.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingFunctionArgs.h @@ -20,6 +20,9 @@ struct WingFunctionArgs // Returns true on success. static bool SetArgs(UEdGraphNode* Node, const FString& Args); + // Returns true if the arguments are valid, if not prints error. + static bool CheckArgs(const FString &Args); + private: // A parsed argument: type + name. struct FParsedArg @@ -28,7 +31,7 @@ private: FName PinName; }; - // Parse "int◦x│float◦y" into an array of FParsedArg. + // Parse "int x,float y" into an array of FParsedArg. // Returns false and prints an error on failure. static bool ParseArgs(const FString& Args, TArray& OutArgs); diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h b/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h index e3c05f03..fea7bbe1 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingProperty.h @@ -27,11 +27,12 @@ struct FWingProperty explicit operator bool() const { return Prop != nullptr; } FProperty* operator->() const { return Prop; } - static void Remove(TArray& Props, const FString& Name); static TArray GetAll(UObject* Obj, EPropertyFlags Flags); static TArray GetAll(UStruct* StructType, void* Container, EPropertyFlags Flags); 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); private: 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 693c7056..00bb5f6a 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h @@ -57,6 +57,7 @@ public: static FString FormatName(const UActorComponent *C); static FString FormatName(const USCS_Node *Node); static FString FormatName(const UEdGraph *Graph); + static FString FormatName(const TObjectPtr &Graph); static FString FormatName(const UEdGraphNode* Node); static FString FormatName(const UEdGraphPin *Pin); static FString FormatName(const FMemberReference &Ref); @@ -80,15 +81,10 @@ public: static FString FormatName(const FWingActorComponent &Comp); //////////////////////////////////////////////////////// - // // Identifies // - // Return true if the name identifies the object. The - // FormatName functions, above, always return names that - // identify the object. However, there may be other - // names that also identify the object. Identifying names - // aren't 100% guaranteed to be unique, but very likely. - // + // Return true if the name matches the formatted name + // of the object, using the formatname routines above. //////////////////////////////////////////////////////// template @@ -97,6 +93,17 @@ public: return FormatName(std::forward(Obj)).Equals(Name, ESearchCase::IgnoreCase); } + //////////////////////////////////////////////////////// + // + + template + static TArray FormatNames(const TArray &Array) + { + TArray Result; + for (T& Elt : Array) Result.Add(FormatName(Elt)); + return Result; + } + template static TArray FindAllNamed(const FString &Name, const TArray &Array) { @@ -114,41 +121,41 @@ public: } template - static T* FindExactlyOneNamed(const FString &Name, const TArray &Array) + static T* FindExactlyOneNamed(const FString &Name, const TArray &Array, const TCHAR *Kind) { int Count = 0; T* Result = nullptr; for (T* Elt : Array) if (Identifies(Name, Elt)) { Count++; Result = Elt; } - if (!CheckExactlyOneNamed(Count, T::StaticClass(), Name)) return nullptr; + if (!CheckExactlyOneNamed(Count, Kind, Name)) return nullptr; return Result; } template - static T* FindExactlyOneNamed(const FString &Name, TArray &Array) + static T* FindExactlyOneNamed(const FString &Name, TArray &Array, const TCHAR *Kind) { int Count = 0; T* Result = nullptr; for (T& Elt : Array) if (Identifies(Name, Elt)) { Count++; Result = &Elt; } - if (!CheckExactlyOneNamed(Count, T::StaticStruct()->GetName(), Name)) return nullptr; + if (!CheckExactlyOneNamed(Count, Kind, Name)) return nullptr; return Result; } template - static bool FindExactlyNoneNamed(const FString &Name, const TArray &Array) + static bool FindExactlyNoneNamed(const FString &Name, const TArray &Array, const TCHAR *Kind) { for (T* Elt: Array) if (Identifies(Name, Elt)) { - return CheckExactlyNoneNamed(1, T::StaticClass(), Name); + return CheckExactlyNoneNamed(1, Kind, Name); } return true; } template - static bool FindExactlyNoneNamed(const FString &Name, const TArray &Array) + static bool FindExactlyNoneNamed(const FString &Name, const TArray &Array, const TCHAR *Kind) { for (const T& Elt: Array) if (Identifies(Name, Elt)) { - return CheckExactlyNoneNamed(1, T::StaticStruct()->GetName(), Name); + return CheckExactlyNoneNamed(1, Kind, Name); } return true; } @@ -165,20 +172,6 @@ public: static FString SanitizeName(const FString& Name); static FString SanitizeName(FName Name); - - //////////////////////////////////////////////////////// - // Our name sanitization routine, above, will turn names - // with spaces into names like "Post·Initiate·Action" - // containing middle dots instead. When the LLM creates - // new nodes, graphs, variables, or the like, it might - // suggest a name containing middle dots. In places - // like that, where the LLM is naming something new, we - // run this Unsanitize routine first. This is *not* - // used for lookups: Lookups are done by comparing - // sanitized names. - //////////////////////////////////////////////////////// - static FString UnsanitizeName(const FString& Name); - //////////////////////////////////////////////////////// // In Unreal, Menu items tend to be an unpredictable // mix of CamelCase without spaces, and with spaces. @@ -188,7 +181,19 @@ public: //////////////////////////////////////////////////////// static FString StandardizeMenuItem(const FString &Item); - + //////////////////////////////////////////////////////// + // This routine is used when the LLM is proposing a new + // name in order to create a new graph, new node, or + // something like that. This verifies that the name doesn't + // contain weird control characters that will confuse + // our name sanitization routines. If the name is valid, + // returns the *internal* name which should be stored in + // unreal's data structures. This is different than the + // name used by the LLM. If not valid, returns empty + // string. + //////////////////////////////////////////////////////// + [[nodiscard]] static FString CheckProposedName(const FString &Name); + static FString FormatNodeTitle(const UEdGraphNode *Node); // ----- Enum helpers ----- @@ -251,10 +256,10 @@ public: static void PrintHandlerHelp(UClass* HandlerClass); // ----- Common Error Reporting ----- - static bool CheckExactlyOneNamed(int Count, const FString &Kind, const FString &Name); - static bool CheckExactlyOneNamed(int Count, UClass *Class, const FString &Name); - static bool CheckExactlyNoneNamed(int Count, const FString &Kind, const FString &Name); - static bool CheckExactlyNoneNamed(int Count, UClass *Class, const FString &Name); + static bool CheckExactlyOneNamed(int Count, const TCHAR *Kind, const FString &Name); + static bool CheckExactlyNoneNamed(int Count, const TCHAR *Kind, const FString &Name); +private: + static FString UnsanitizeName(const FString &Input); };