From ea74f5fe761a0e035977b3e0a827be92924d339b Mon Sep 17 00:00:00 2001 From: jyelon Date: Fri, 10 Apr 2026 16:56:17 -0400 Subject: [PATCH] Add support for Blueprint Overrides, and make more progress on the hotkey widget. --- Content/Widgets/WB_Hotkey_Action.uasset | 2 +- Content/Widgets/WB_Hotkey_Image.uasset | 4 +- Content/Widgets/WB_Hotkeys.uasset | 4 +- Docs/TODO-List.md | 4 +- .../Handlers/BlueprintOverride_Add.h | 85 +++++++++++++++++++ .../Handlers/BlueprintOverride_ShowMenu.h | 48 +++++++++++ .../UEWingman/Private/WingGraphActions.cpp | 3 +- .../Source/UEWingman/Private/WingUtils.cpp | 77 +++++++++++++++++ .../Source/UEWingman/Public/WingUtils.h | 6 ++ 9 files changed, 225 insertions(+), 8 deletions(-) create mode 100644 Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintOverride_Add.h create mode 100644 Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintOverride_ShowMenu.h diff --git a/Content/Widgets/WB_Hotkey_Action.uasset b/Content/Widgets/WB_Hotkey_Action.uasset index 13829f3a..1c6f27db 100644 --- a/Content/Widgets/WB_Hotkey_Action.uasset +++ b/Content/Widgets/WB_Hotkey_Action.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88695ebd7d3369e1a3c643c4a8b1fd21117e10473d30d0f7a1b0f130025b76ad +oid sha256:fa9e9326febe31d322b37fceadaa9ded35e309b94bedf6b509a565a63d3696cc size 40300 diff --git a/Content/Widgets/WB_Hotkey_Image.uasset b/Content/Widgets/WB_Hotkey_Image.uasset index 05ca7ada..af0f3580 100644 --- a/Content/Widgets/WB_Hotkey_Image.uasset +++ b/Content/Widgets/WB_Hotkey_Image.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bcd15920cc53024b33fcc8a91b90b3979a2955977ef91a981182d20e0433bd3f -size 101754 +oid sha256:23830a9f0cf927941588e7c966190212d5b15c85c34e634b3a3d8ea36268f42a +size 101608 diff --git a/Content/Widgets/WB_Hotkeys.uasset b/Content/Widgets/WB_Hotkeys.uasset index 5741bf3b..990ee526 100644 --- a/Content/Widgets/WB_Hotkeys.uasset +++ b/Content/Widgets/WB_Hotkeys.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6c0fd9b7e92f1a3ea10dc70801d9c92591019578242b2ef14daabd9238d2460f -size 260266 +oid sha256:6232b9c8443422d01e13a2a15940db36ea5ed8f5cf1be8ace5fb38694e149dc4 +size 347817 diff --git a/Docs/TODO-List.md b/Docs/TODO-List.md index c885c5aa..c3af0047 100644 --- a/Docs/TODO-List.md +++ b/Docs/TODO-List.md @@ -1,6 +1,6 @@ -* UE Wingman Widgets: cannot edit 'Is Variable' flag or widget name. - TSharedPtr NameValidator = MakeShareable(new FKismetNameValidator(Blueprint, OldObjectName)); +* UE Wingman Function Overrides. + * Keyboard Event Handling diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintOverride_Add.h b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintOverride_Add.h new file mode 100644 index 00000000..314af620 --- /dev/null +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintOverride_Add.h @@ -0,0 +1,85 @@ +#pragma once + +#include "CoreMinimal.h" +#include "WingServer.h" +#include "WingBasics.h" +#include "WingFetcher.h" +#include "WingUtils.h" +#include "Engine/Blueprint.h" +#include "EdGraphSchema_K2.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "BlueprintOverride_Add.generated.h" + + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS() +class UWing_BlueprintOverride_Add : public UWingHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(EditAnywhere, meta=(Description="Blueprint path")) + FString Blueprint; + + UPROPERTY(EditAnywhere, meta=(Description="Function to override, in Class|Function format")) + FString Function; + + virtual void Register() override + { + UWingServer::AddHandler(this, + TEXT("Add a function override to a Blueprint.")); + } + + virtual void Handle() override + { + WingFetcher F(WingOut::Stdout); + UBlueprint* BP = F.Walk(Blueprint).Cast(); + if (!BP) return; + + // Find the function by matching against the menu string format (Class|Function) + TArray Overridable = WingUtils::GetOverridableFunctions(BP); + UFunction* OverrideFunc = nullptr; + for (UFunction* Candidate : Overridable) + { + FString MenuString = WingUtils::GetFunctionMenuString(Candidate); + if (MenuString.Equals(Function, ESearchCase::IgnoreCase)) + { + OverrideFunc = Candidate; + break; + } + } + + if (!OverrideFunc) + { + WingOut::Stdout.Printf(TEXT("ERROR: '%s' is not an overridable function in this Blueprint.\n"), *Function); + return; + } + + // Check if it already exists + FName FuncName = OverrideFunc->GetFName(); + TSet ExistingNames; + FBlueprintEditorUtils::GetAllGraphNames(BP, ExistingNames); + if (ExistingNames.Contains(FuncName)) + { + WingOut::Stdout.Printf(TEXT("ERROR: Override graph '%s' already exists.\n"), *Function); + return; + } + + UClass* OverrideFuncClass = WingUtils::GetFunctionClass(OverrideFunc); + + UEdGraph* NewGraph = FBlueprintEditorUtils::CreateNewGraph(BP, FuncName, + UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass()); + if (!NewGraph) + { + WingOut::Stdout.Print(TEXT("ERROR: Failed to create graph.\n")); + return; + } + + FBlueprintEditorUtils::AddFunctionGraph(BP, NewGraph, /*bIsUserCreated=*/false, OverrideFuncClass); + + WingOut::Stdout.Printf(TEXT("Created override: %s\n"), *WingUtils::FormatName(NewGraph)); + } +}; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintOverride_ShowMenu.h b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintOverride_ShowMenu.h new file mode 100644 index 00000000..844f91e3 --- /dev/null +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintOverride_ShowMenu.h @@ -0,0 +1,48 @@ +#pragma once + +#include "CoreMinimal.h" +#include "WingServer.h" +#include "WingBasics.h" +#include "WingFetcher.h" +#include "WingUtils.h" +#include "Engine/Blueprint.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "BlueprintOverride_ShowMenu.generated.h" + + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS() +class UWing_BlueprintOverride_ShowMenu : public UWingHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(EditAnywhere, meta=(Description="Blueprint path")) + FString Blueprint; + + virtual void Register() override + { + UWingServer::AddHandler(this, + TEXT("Show the functions that can be overridden in a Blueprint.")); + } + + virtual void Handle() override + { + WingFetcher F(WingOut::Stdout); + UBlueprint* BP = F.Walk(Blueprint).Cast(); + if (!BP) return; + + TArray Results = WingUtils::GetOverridableFunctions(BP); + for (UFunction *Func : Results) + { + UClass *Class = WingUtils::GetFunctionClass(Func); + WingOut::Stdout.Print(WingUtils::GetFunctionMenuString(Func)); + WingOut::Stdout.Print(TEXT("\n")); + } + if (Results.IsEmpty()) + WingOut::Stdout.Print(TEXT("No overridable functions.\n")); + } +}; diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingGraphActions.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingGraphActions.cpp index 4b84b650..4456cbe2 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingGraphActions.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingGraphActions.cpp @@ -33,9 +33,10 @@ FName FWingGraphAction::GetSpawnerOwnerClass(const UBlueprintNodeSpawner* Spawne if (const UBlueprintVariableNodeSpawner* VariableSpawner = Cast(Spawner)) { + if (VariableSpawner->IsLocalVariable()) return FName(TEXT("LocalVariable")); return GetPropertyClassName(VariableSpawner->GetVarProperty()); } - + if (const UBlueprintDelegateNodeSpawner* DelegateSpawner = Cast(Spawner)) { return GetPropertyClassName(DelegateSpawner->GetDelegateProperty()); diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp index 8cae4a43..ce41638e 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp @@ -13,6 +13,10 @@ #include "WidgetBlueprint.h" #include "Blueprint/WidgetTree.h" +// Blueprint override helpers +#include "EdGraphSchema_K2.h" +#include "ObjectEditorUtils.h" + // Reparent validation #include "Engine/LevelScriptActor.h" #include "Animation/AnimInstance.h" @@ -403,6 +407,79 @@ bool WingUtils::CheckNewObjectNotNull(UObject *Obj, const TCHAR *Kind, WingOut E return true; } +// ============================================================ +// Blueprint override helpers +// ============================================================ + +bool WingUtils::CanBlueprintOverride(UBlueprint* BP, const UFunction* Function) +{ + if (!Function) return false; + if (!UEdGraphSchema_K2::CanKismetOverrideFunction(Function)) return false; + UClass* HiddenFromClass = BP->SkeletonGeneratedClass ? BP->SkeletonGeneratedClass->GetSuperClass() : BP->ParentClass.Get(); + if (HiddenFromClass && FObjectEditorUtils::IsFunctionHiddenFromClass(Function, HiddenFromClass)) return false; + if (Function->HasAnyFunctionFlags(FUNC_Private)) return false; + if (!BP->AllowFunctionOverride(Function)) return false; + return true; +} + +TArray WingUtils::GetOverridableFunctions(UBlueprint *BP) +{ + TArray Candidates, Results; + TSet Seen; + + UClass* ParentClass = BP->SkeletonGeneratedClass ? BP->SkeletonGeneratedClass->GetSuperClass() : BP->ParentClass.Get(); + for (TFieldIterator FunctionIt(ParentClass, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt) + { + Candidates.Add(*FunctionIt); + } + for (const FBPInterfaceDescription& InterfaceDesc : BP->ImplementedInterfaces) + { + UClass* InterfaceClass = InterfaceDesc.Interface.Get(); + if (!InterfaceClass) continue; + for (TFieldIterator FunctionIt(InterfaceClass, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt) + { + Candidates.Add(*FunctionIt); + } + } + for (UClass* TempClass = BP->ParentClass.Get(); TempClass; TempClass = TempClass->GetSuperClass()) + { + for (const FImplementedInterface& Interface : TempClass->Interfaces) + { + for (TFieldIterator FunctionIt(Interface.Class, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt) + { + Candidates.Add(*FunctionIt); + } + } + } + for (UFunction *Candidate : Candidates) + { + if (!Seen.Contains(Candidate)) + { + Seen.Add(Candidate); + if (CanBlueprintOverride(BP, Candidate)) Results.Add(Candidate); + } + } + return Results; +} + +UClass *WingUtils::GetFunctionClass(UFunction *Func) +{ + UClass *Class = Func->GetOuterUClass(); + UBlueprintGeneratedClass *BPGC = Cast(Class); + if (BPGC) return BPGC->GetAuthoritativeClass(); + else return Class; +} + +FString WingUtils::GetFunctionMenuString(UFunction *Func) +{ + UClass *Class = GetFunctionClass(Func); + TStringBuilder<256> Result; + Result.Append(FormatName(Class)); + Result.AppendChar('|'); + Result.Append(FormatName(Func)); + return Result.ToString(); +} + // ============================================================ // Reparent validation // ============================================================ diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h b/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h index db7c3e43..b20cfff3 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h @@ -280,6 +280,12 @@ public: static FString GetHandlerName(UClass* HandlerClass); static FString GetHandlerGroup(UClass* HandlerClass); + // ----- Blueprint override helpers ----- + static bool CanBlueprintOverride(UBlueprint* BP, const UFunction* Function); + static TArray GetOverridableFunctions(UBlueprint *BP); + static UClass *GetFunctionClass(UFunction *Func); + static FString GetFunctionMenuString(UFunction *Func); + // ----- Reparent validation ----- static bool CanReparentBlueprint(UClass* CurrentGenerated, UClass* Proposed);