diff --git a/Content/Testing/BP_Test.uasset b/Content/Testing/BP_Test.uasset index 03cc8235..55a14655 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:1e27dd9414b234eabaf81287996097a56f2bb670a10a84f8b125233caebb5ae8 -size 30335 +oid sha256:a05f0d08018fb2e11072556cacabdf1dbf0c84f5ce668da3d9b4a325576ea63a +size 32465 diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintDispatcher_Create.h b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintDispatcher_Create.h new file mode 100644 index 00000000..4e0d275e --- /dev/null +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintDispatcher_Create.h @@ -0,0 +1,100 @@ +#pragma once + +#include "CoreMinimal.h" +#include "WingServer.h" +#include "WingHandler.h" +#include "WingFetcher.h" +#include "WingJson.h" +#include "WingBlueprintVar.h" +#include "WingUtils.h" +#include "Engine/Blueprint.h" +#include "EdGraphSchema_K2.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "BlueprintDispatcher_Create.generated.h" + + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS() +class UWing_BlueprintDispatcher_Create : public UObject, public IWingHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Blueprint name or package path")) + FString Blueprint; + + 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; + + virtual FString GetDescription() const override + { + return TEXT("Add a new event dispatcher to a Blueprint. Pass Config to set parameters, category, flags, etc."); + } + + virtual void Handle() override + { + WingFetcher F; + 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; + } + } + + // Add the delegate variable + FEdGraphPinType DelegateType; + DelegateType.PinCategory = UEdGraphSchema_K2::PC_MCDelegate; + if (!FBlueprintEditorUtils::AddMemberVariable(BP, VarFName, DelegateType)) + { + UWingServer::Printf(TEXT("ERROR: Failed to add event dispatcher '%s' to %s\n"), *Name, *WingUtils::FormatName(BP)); + return; + } + + // Create the signature graph + const UEdGraphSchema_K2* K2Schema = GetDefault(); + UEdGraph* SigGraph = FBlueprintEditorUtils::CreateNewGraph(BP, VarFName, + UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass()); + if (!SigGraph) + { + UWingServer::Printf(TEXT("ERROR: Failed to create signature graph for '%s'\n"), *Name); + return; + } + + SigGraph->bEditable = false; + K2Schema->CreateDefaultNodesForGraph(*SigGraph); + K2Schema->CreateFunctionGraphTerminators(*SigGraph, static_cast(nullptr)); + K2Schema->AddExtraFunctionFlags(SigGraph, FUNC_BlueprintCallable | FUNC_BlueprintEvent | FUNC_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) + { + if (!Editor.ApplyJson(Config.Json.Get())) + 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 new file mode 100644 index 00000000..d1e06d89 --- /dev/null +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintDispatcher_Dump.h @@ -0,0 +1,51 @@ +#pragma once + +#include "CoreMinimal.h" +#include "WingServer.h" +#include "WingHandler.h" +#include "WingFetcher.h" +#include "WingUtils.h" +#include "WingBlueprintVar.h" +#include "Engine/Blueprint.h" +#include "BlueprintDispatcher_Dump.generated.h" + + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS() +class UWing_BlueprintDispatcher_Dump : public UObject, public IWingHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Blueprint name or package path")) + FString Blueprint; + + UPROPERTY(meta=(Description="Name of the event dispatcher to inspect")) + FString Dispatcher; + + virtual FString GetDescription() const override + { + return TEXT("Show all editable properties of a Blueprint event dispatcher."); + } + + virtual void Handle() override + { + WingFetcher F; + UBlueprint* BP = F.Walk(Blueprint).Cast(); + if (!BP) return; + + FWingBlueprintVar Editor(BP, Dispatcher); + if (Editor.NotFound()) return; + if (!Editor.IsEventDispatcher()) + { + UWingServer::Printf(TEXT("ERROR: '%s' is not an event dispatcher\n"), *Dispatcher); + return; + } + + UWingServer::Printf(TEXT("Event dispatcher %s in %s:\n"), *Dispatcher, *WingUtils::FormatName(BP)); + Editor.Dump(); + } +}; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Dump.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Dump.h index 3f874801..1aed6a7a 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Dump.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Dump.h @@ -67,6 +67,7 @@ public: UWingServer::Print(TEXT("\nVariables:\n")); for (const FBPVariableDescription& V : BP->NewVariables) { + if (V.VarType.PinCategory == UEdGraphSchema_K2::PC_MCDelegate) continue; UWingServer::Printf(TEXT(" %s %s"), *UWingTypes::TypeToText(V.VarType), *WingUtils::FormatName(V)); diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingBlueprintVar.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingBlueprintVar.cpp index 30bc82d9..40d4c403 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingBlueprintVar.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingBlueprintVar.cpp @@ -1,9 +1,11 @@ #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) @@ -11,13 +13,34 @@ FWingBlueprintVar::FWingBlueprintVar(UBlueprint* BP, const FString& VarName) Desc = WingUtils::FindExactlyOneNamed(VarName, BP->NewVariables); if (!Desc) return; - // Try to find the default value property on the CDO. - if (BP->GeneratedClass) + if (Desc->VarType.PinCategory == UEdGraphSchema_K2::PC_MCDelegate) { - UObject* CDO = BP->GeneratedClass->GetDefaultObject(); - FProperty* Prop = BP->GeneratedClass->FindPropertyByName(Desc->VarName); - if (CDO && Prop) - DefaultValueProp = FWingProperty(Prop, CDO); + // 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; + } + } + else + { + // 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); + } } } @@ -25,6 +48,7 @@ void FWingBlueprintVar::Dump() { LoadFlags(); LoadDefault(); + LoadDelegateArgs(); TArray Props = MergedProperties(); for (FWingProperty& P : Props) { @@ -38,6 +62,7 @@ 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) { @@ -49,6 +74,7 @@ bool FWingBlueprintVar::ApplyJson(const FJsonObject* Json) } LoadFlags(); + LoadDelegateArgs(); TArray Props = MergedProperties(); if (!WingJson::PopulateFromJson(Props, Json, true)) @@ -56,7 +82,9 @@ bool FWingBlueprintVar::ApplyJson(const FJsonObject* Json) SaveFlags(); if (bHasDefault) - return SaveDefault(); + if (!SaveDefault()) return false; + if (bHasDelegateArgs) + if (!SaveDelegateArgs()) return false; return true; } @@ -82,6 +110,18 @@ 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 @@ -125,6 +165,20 @@ 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( @@ -139,9 +193,20 @@ TArray FWingBlueprintVar::MergedProperties() Props.Append(FWingProperty::GetAll( FWingBlueprintVar::StaticStruct(), this, (EPropertyFlags)0)); - // Remove DefaultValue if we don't have a CDO property to back it. - if (!DefaultValueProp) + if (SigGraph) + { + FWingProperty::Remove(Props, TEXT("VarType")); 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/Public/WingBlueprintVar.h b/Plugins/UEWingman/Source/UEWingman/Public/WingBlueprintVar.h index 244aac12..27c2b047 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingBlueprintVar.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingBlueprintVar.h @@ -16,11 +16,13 @@ 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; @@ -43,6 +45,9 @@ 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); @@ -52,7 +57,9 @@ struct FWingBlueprintVar private: void LoadFlags(); void LoadDefault(); + void LoadDelegateArgs(); void SaveFlags(); bool SaveDefault(); + bool SaveDelegateArgs(); TArray MergedProperties(); };