From 7a09da8a4e2feff653df109431e44404bf9936f7 Mon Sep 17 00:00:00 2001 From: jyelon Date: Fri, 17 Apr 2026 17:56:10 -0400 Subject: [PATCH] Player controller code to sort input components better: widgets with 'Stop Input' go to top of priority stack, and also, priority actually works now. --- Docs/TODO-List.md | 4 +- .../Source/UEWingman/Handlers/Sequence.h | 13 +-- .../Source/UEWingman/Private/WingManual.cpp | 1 + Source/Integration/PlayerControllerBase.cpp | 95 +++++++++++++++++++ Source/Integration/PlayerControllerBase.h | 5 + 5 files changed, 110 insertions(+), 8 deletions(-) diff --git a/Docs/TODO-List.md b/Docs/TODO-List.md index c3af0047..1c00a872 100644 --- a/Docs/TODO-List.md +++ b/Docs/TODO-List.md @@ -1,6 +1,6 @@ -* UE Wingman Function Overrides. - +* UE Wingman rename functions. +* In the console, do not allow multi-line lua expressions unless it's something that reasonably should be multi-line, like a function definition or an if-statement. * Keyboard Event Handling diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Sequence.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Sequence.h index c9cfe38e..86e4aba4 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Sequence.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Sequence.h @@ -23,8 +23,10 @@ public: virtual void Register() override { UWingServer::AddHandler(this, - TEXT("Execute multiple commands in one request. Each subcommand produces its own content block in the response. " - "Nested Sequence commands are not allowed.")); + TEXT("Execute multiple commands in one request. Each subcommand " + "produces its own content block in the response. The big win " + "performance-wise is that fewer MCP calls means fewer " + "round-trip invocations of the LLM.")); } virtual void Handle() override { @@ -32,9 +34,8 @@ public: // WingServer. Because of that, this handler is never actually called // under normal conditions. The handler exists for two reasons: to // provide documentation, and also to catch the case where somebody - // nests a sequence inside another sequence (WingServer doesn't catch - // that). - // - WingOut::Stdout.Print(TEXT("ERROR: Sequence inside a Sequence is not allowed.\n")); + // nests a sequence inside another sequence. + WingOut::Stdout.Print( + TEXT("ERROR: Sequence inside a Sequence is not allowed.\n")); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingManual.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingManual.cpp index 83775ebb..742f4d3e 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingManual.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingManual.cpp @@ -211,6 +211,7 @@ void UWingManualSections::ImportantCommands() "\n Graph_Dump: a fairly detailed listing of any Graph" "\n Details_Dump: Dump the details panel for a given object" "\n Details_Set: Manipulate the details panel for a given object" + "\n Sequence: Batch commands together for faster execution" "\n" "\n You can use Documentation_Commands(Query=Command,Verbose=true)" "\n to get detailed help for a specific command." diff --git a/Source/Integration/PlayerControllerBase.cpp b/Source/Integration/PlayerControllerBase.cpp index 8ba0771a..392cf98f 100644 --- a/Source/Integration/PlayerControllerBase.cpp +++ b/Source/Integration/PlayerControllerBase.cpp @@ -2,6 +2,8 @@ #include "Common.h" #include "Tangible.h" #include "TangibleManager.h" +#include "Components/InputComponent.h" +#include "Engine/LevelScriptActor.h" #include "Kismet/GameplayStatics.h" #include "Engine/GameInstance.h" @@ -53,6 +55,99 @@ FVector2D AlxPlayerControllerBase::GetLookAtPixel(const UObject *Context) return ScreenPosition; } +void AlxPlayerControllerBase::PushInputComponent(UInputComponent* InInputComponent) +{ + if (InInputComponent) + { + CurrentInputStack.RemoveSingle(InInputComponent); + CurrentInputStack.Add(InInputComponent); + } +} + +bool AlxPlayerControllerBase::PopInputComponent(UInputComponent* InInputComponent) +{ + if (InInputComponent) + { + if (CurrentInputStack.RemoveSingle(InInputComponent) > 0) + { + InInputComponent->ClearBindingValues(); + return true; + } + } + return false; +} + +void AlxPlayerControllerBase::BuildInputStack(TArray& InputStack) +{ + // Controlled pawn gets last dibs on the input stack + APawn* ControlledPawn = GetPawnOrSpectator(); + if (ControlledPawn) + { + if (ControlledPawn->InputEnabled()) + { + // Get the explicit input component that is created upon Pawn possession. This one gets last dibs. + if (ControlledPawn->InputComponent) + { + InputStack.Push(ControlledPawn->InputComponent); + } + + // See if there is another InputComponent that was added to the Pawn's components array (possibly by script). + for (UActorComponent* ActorComponent : ControlledPawn->GetComponents()) + { + UInputComponent* PawnInputComponent = Cast(ActorComponent); + if (PawnInputComponent && PawnInputComponent != ControlledPawn->InputComponent) + { + InputStack.Push(PawnInputComponent); + } + } + } + } + + // LevelScriptActors are put on the stack next + for (ULevel* Level : GetWorld()->GetLevels()) + { + ALevelScriptActor* ScriptActor = Level->GetLevelScriptActor(); + if (ScriptActor) + { + if (ScriptActor->InputEnabled() && ScriptActor->InputComponent) + { + InputStack.Push(ScriptActor->InputComponent); + } + } + } + + if (InputEnabled()) + { + InputStack.Push(InputComponent); + } + + // Sort the components first by bBlockInput, then by + // priority. We don't touch the original array, because + // we want to preserve information about which component + // was pushed last. + TArray> Pushed; + for (int32 Idx = 0; Idx < CurrentInputStack.Num(); ++Idx) + { + UInputComponent* IC = CurrentInputStack[Idx].Get(); + if (IsValid(IC)) + { + Pushed.Add(IC); + } + else + { + CurrentInputStack.RemoveAt(Idx--); + } + } + + Pushed.StableSort([](const UInputComponent& A, const UInputComponent& B) + { + if (A.bBlockInput != B.bBlockInput) return !A.bBlockInput; + return A.Priority < B.Priority; + }); + + InputStack.Append(Pushed); +} + void AlxPlayerControllerBase::UpdateLookAt() { UlxTangibleManager *TM = GetGameInstance()->GetSubsystem(); diff --git a/Source/Integration/PlayerControllerBase.h b/Source/Integration/PlayerControllerBase.h index 8e70f54f..954c2ba5 100644 --- a/Source/Integration/PlayerControllerBase.h +++ b/Source/Integration/PlayerControllerBase.h @@ -36,6 +36,11 @@ public: // Called by GameMode each tick. void UpdateLookAt(); + // Input stack overrides: unsorted, append-on-push. + virtual void PushInputComponent(UInputComponent* InInputComponent) override; + virtual bool PopInputComponent(UInputComponent* InInputComponent) override; + virtual void BuildInputStack(TArray& InputStack) override; + // Get the player controller, cast to AlxPlayerControllerBase. static AlxPlayerControllerBase *FromContext(const UObject *Context);