From 4dabb98d058a9ae85c65a66eb90540becf8167b2 Mon Sep 17 00:00:00 2001 From: jyelon Date: Wed, 4 Mar 2026 17:17:00 -0500 Subject: [PATCH] Added 'ReadLuaValues', initialized with a copy of LuaCallNode --- Source/Integration/ReadLuaValues.cpp | 297 +++++++++++++++++++++++++++ Source/Integration/ReadLuaValues.h | 65 ++++++ 2 files changed, 362 insertions(+) create mode 100644 Source/Integration/ReadLuaValues.cpp create mode 100644 Source/Integration/ReadLuaValues.h diff --git a/Source/Integration/ReadLuaValues.cpp b/Source/Integration/ReadLuaValues.cpp new file mode 100644 index 00000000..a3ae55a6 --- /dev/null +++ b/Source/Integration/ReadLuaValues.cpp @@ -0,0 +1,297 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "ReadLuaValues.h" + +#include "BlueprintActionDatabaseRegistrar.h" +#include "BlueprintNodeSpawner.h" +#include "EdGraphSchema_K2.h" +#include "GameFramework/Actor.h" +#include "K2Node_CallFunction.h" +#include "KismetCompiler.h" +#include "LuaCall.h" + +#define LOCTEXT_NAMESPACE "LuaCall" + +// All argument pins will have internal Names that start with "A:" + +const FName UK2Node_ReadLuaValues::FunctionPinName(TEXT("Lua Function Prototype")); +const FName UK2Node_ReadLuaValues::PlacePinName(TEXT("Place Tangible")); +const FName UK2Node_ReadLuaValues::ExtraResultsPinName(TEXT("Extra Results")); +const FName UK2Node_ReadLuaValues::ErrorPinName(TEXT("Error")); + +FText UK2Node_ReadLuaValues::GetTooltipText() const +{ + static FText Tooltip = FText::FromString(FString::Printf(TEXT( + "Call a Lua function.\n" + "\n" + "The lua function prototype must be a hardwired string which must look like\n" + "one of the following:\n" + "\n" + " classname.funcname(int arg1, int arg2)\n" + " classname.funcname(int arg1, int arg2) : int ret1, int ret2\n" + " classname.funcname(int arg1, int arg2) : int ret1, int ret2, ...\n" + "\n" + "You must specify types for the argument and return value pins. The\n" + "types that you may specify are:\n" + "\n" + "Arguments: %s\n" + "Return Values: %s\n" + "\n" + "The prototype is parsed to determine what lua function to call.\n" + "The lua call node will automatically add pins for the arguments and\n" + "return values. If the function has no return values, you can omit those\n" + "from the proto. If you put an ellipsis at the end of the return values,\n" + "then any additional return values will be collected into a\n" + "dynamically typed array of values which you can iterate over later.\n" + "\n" + "Optionally, you may use the * wildcard for the classname. In that\n" + "case, the class of the 'place' tangible will be used.\n" + "\n"), *UlxLuaCallLibrary::AllKnownArgumentTypes(), *UlxLuaCallLibrary::AllKnownReturnValueTypes())); + return Tooltip; +} + +void UK2Node_ReadLuaValues::ReconstructNode() +{ + // Save the value of the Prototype Pin before it gets reconstructed. + UEdGraphPin* FunctionPin = FindPin(FunctionPinName); + if (FunctionPin != nullptr) + { + LuaFunctionPrototype = FunctionPin->DefaultValue; + } + + Super::ReconstructNode(); +} + +void UK2Node_ReadLuaValues::AllocateDefaultPins() +{ + Pins.Reset(); + Super::AllocateDefaultPins(); + + // Parse the lua function prototype. + // Note that we use the saved value of the function + // prototype, because the function prototype pin + // has been deleted at this point. + FlxParsedProto ParsedProto(LuaFunctionPrototype); + if (!ParsedProto.ErrorMessage.IsEmpty()) + { + SetErrorMsg(FString::Printf(TEXT("Syntax error in lua function prototype: %s"), *ParsedProto.ErrorMessage)); + } + + CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute); + CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then); + + if (!IsInvoke()) + { + CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, ErrorPinName); + } + + UEdGraphPin *FunctionPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_String, FunctionPinName); + FunctionPin->DefaultValue = LuaFunctionPrototype; + + CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, AActor::StaticClass(), PlacePinName); + + // Create argument pins. + for (const FlxParsedProto::Pin & Pin : ParsedProto.Arguments) + { + FName PrefixedName = AddPrefix(Pin.Name, 'A'); + UFunction *Accessor = UlxLuaCallLibrary::GetArgumentPacker(Pin.Type); + if (Accessor == nullptr) { + SetErrorMsg(FString::Printf(TEXT("Unknown argument type: %s"), *Pin.Type)); + continue; + } + CreatePin(EGPD_Input, PropertyToPinType(Accessor->FindPropertyByName(TEXT("Value"))), PrefixedName); + } + + // Create return value pins. + for (const FlxParsedProto::Pin & Pin : ParsedProto.ReturnValues) + { + FName PrefixedName = AddPrefix(Pin.Name, 'R'); + UFunction *Accessor = UlxLuaCallLibrary::GetReturnValueUnpacker(Pin.Type); + if (Accessor == nullptr) { + SetErrorMsg(FString::Printf(TEXT("Unknown return value type: %s"), *Pin.Type)); + continue; + } + CreatePin(EGPD_Output, PropertyToPinType(Accessor->FindPropertyByName(TEXT("Result"))), PrefixedName); + } + + if (ParsedProto.ExtraReturnValues) + { + CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Object, UlxLuaValues::StaticClass(), ExtraResultsPinName); + } +} + + +FText UK2Node_ReadLuaValues::GetNodeTitle(ENodeTitleType::Type TitleType) const +{ + if (IsInvoke()) + { + return LOCTEXT("LuaInvoke_Title", "Invoke a Lua Function"); + } + else + { + return LOCTEXT("LuaProbe_Title", "Probe a Lua Function"); + } +} + +FText UK2Node_ReadLuaValues::GetPinDisplayName(const UEdGraphPin* Pin) const +{ + // These pins don't need labels. + if ((Pin->PinName == UEdGraphSchema_K2::PN_Execute) || + (Pin->PinName == UEdGraphSchema_K2::PN_Then) || + (Pin->PinName == FunctionPinName)) + { + return FText::GetEmpty(); + } + + // Return the pin name, removing A: or R: if present. + return FText::FromName(RemovePrefix(Pin->PinName)); +} + +void UK2Node_ReadLuaValues::PinDefaultValueChanged(UEdGraphPin* Pin) +{ + if ((Pin->PinName == FunctionPinName) && (Pin->DefaultValue != LuaFunctionPrototype)) + { + ReconstructNode(); + } +} + + +#define LuaCallLibraryFunction(name) (UlxLuaCallLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxLuaCallLibrary, name))) + + +void UK2Node_ReadLuaValues::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) +{ + Super::ExpandNode(CompilerContext, SourceGraph); + // The BeginNode function packs the class name and function name into the call buffer. + FlxParsedProto ParsedProto(LuaFunctionPrototype); + const UEdGraphSchema_K2* CCSchema = CompilerContext.GetSchema(); + UK2Node_CallFunction* BeginNode = MakeCallFunctionNode(CompilerContext, SourceGraph, LuaCallLibraryFunction(LuaCallBegin)); + CCSchema->TrySetDefaultValue(*BeginNode->FindPinChecked(TEXT("ClassName")), ParsedProto.ClassName); + CCSchema->TrySetDefaultValue(*BeginNode->FindPinChecked(TEXT("FunctionName")), ParsedProto.FunctionName); + UEdGraphPin *ThenPin = BeginNode->GetThenPin(); + + // Add Packing operations for all argument pins. + for (const FlxParsedProto::Pin &PinInfo : ParsedProto.Arguments) + { + UEdGraphPin *Pin = FindPinChecked(AddPrefix(PinInfo.Name, 'A')); + UFunction *PackingFunc = UlxLuaCallLibrary::GetArgumentPacker(PinInfo.Type); + if (PackingFunc == nullptr) + { + // This codepath should be unreachable, but just in case. + CompilerContext.MessageLog.Error(TEXT("All argument pins must have known types")); + continue; + } + UK2Node_CallFunction *PackNode = MakeCallFunctionNode(CompilerContext, SourceGraph, PackingFunc); + CompilerContext.MovePinLinksToIntermediate(*Pin, *PackNode->FindPinChecked(TEXT("Value"))); + ThenPin = ChainExecPin(ThenPin, PackNode, TEXT("Then")); + } + + // Add the invoke or probe node. + UEdGraphPin *ReturnArrayPin = nullptr; + if (IsInvoke()) + { + UK2Node_CallFunction* ActionNode = MakeCallFunctionNode(CompilerContext, SourceGraph, LuaCallLibraryFunction(LuaCallInvoke)); + CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(PlacePinName), *ActionNode->FindPinChecked(TEXT("Place"))); + ThenPin = ChainExecPin(ThenPin, ActionNode, TEXT("Then")); + } + else + { + UK2Node_CallFunction* ActionNode = MakeCallFunctionNode(CompilerContext, SourceGraph, LuaCallLibraryFunction(LuaCallProbe)); + CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(PlacePinName), *ActionNode->FindPinChecked(TEXT("Place"))); + CompilerContext.CopyPinLinksToIntermediate(*FindPinChecked(ErrorPinName), *ActionNode->FindPinChecked(TEXT("False"))); + ReturnArrayPin = ActionNode->FindPinChecked(TEXT("ReturnArray")); + ThenPin = ChainExecPin(ThenPin, ActionNode, TEXT("True")); + } + + if (IsInvoke()) + { + // Make sure we didn't have return values for an invoke. + if ((ParsedProto.ReturnValues.Num() > 0) || (ParsedProto.ExtraReturnValues)) + { + CompilerContext.MessageLog.Error(TEXT("Lua Call in Invoke mode does not support return values")); + } + } + else + { + // Add Unpacking operations for all return value pins. + for (const FlxParsedProto::Pin &PinInfo : ParsedProto.ReturnValues) + { + UEdGraphPin *Pin = FindPinChecked(AddPrefix(PinInfo.Name, 'R')); + UFunction *UnpackingFunc = UlxLuaCallLibrary::GetReturnValueUnpacker(PinInfo.Type); + if (UnpackingFunc == nullptr) + { + // This codepath should be unreachable, but just in case. + CompilerContext.MessageLog.Error(TEXT("All return value pins must have known types.")); + continue; + } + UK2Node_CallFunction *UnpackNode = MakeCallFunctionNode(CompilerContext, SourceGraph, UnpackingFunc); + ReturnArrayPin->MakeLinkTo(UnpackNode->FindPinChecked(UEdGraphSchema_K2::PN_Self)); + CompilerContext.CopyPinLinksToIntermediate(*FindPinChecked(ErrorPinName), *UnpackNode->FindPinChecked(TEXT("WrongType"))); + CompilerContext.MovePinLinksToIntermediate(*Pin, *UnpackNode->FindPinChecked(TEXT("Result"))); + ThenPin = ChainExecPin(ThenPin, UnpackNode, TEXT("Success")); + } + + // If there is an extra results pin, hook it up. + if (ParsedProto.ExtraReturnValues) + { + UEdGraphPin *ExtraPin = FindPinChecked(ExtraResultsPinName); + UFunction *DiscardFunc = UlxLuaValues::StaticClass()->FindFunctionByName(TEXT("DiscardBeforeCursor")); + UK2Node_CallFunction *DiscardNode = MakeCallFunctionNode(CompilerContext, SourceGraph, DiscardFunc); + ReturnArrayPin->MakeLinkTo(DiscardNode->FindPinChecked(UEdGraphSchema_K2::PN_Self)); + ThenPin = ChainExecPin(ThenPin, DiscardNode, TEXT("Then")); + CompilerContext.MovePinLinksToIntermediate(*ExtraPin, *ReturnArrayPin); + } + } + + // Link up the Exec pins. + CompilerContext.MovePinLinksToIntermediate(*GetExecPin(), *BeginNode->GetExecPin()); + CompilerContext.MovePinLinksToIntermediate(*GetThenPin(), *ThenPin); + + + BreakAllNodeLinks(); +} + + +UK2Node::ERedirectType UK2Node_ReadLuaValues::DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const +{ + if (IsTemplate() || (GetGraph() == nullptr)) return ERedirectType_None; + if ((NewPin->PinName == OldPin->PinName) && + (NewPin->Direction == OldPin->Direction) && + (NewPin->PinType == OldPin->PinType)) + { + return ERedirectType_Name; + } + return ERedirectType_None; +} + +bool UK2Node_ReadLuaValues::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const +{ + // The function pin cannot be connected. + if (MyPin->PinName == FunctionPinName) + { + OutReason = LOCTEXT("Error_FunctionPrototypeMustBeHardwired", "Lua function prototype must be a hardwired constant.").ToString(); + return true; + } + + return Super::IsConnectionDisallowed(MyPin, OtherPin, OutReason); +} + +void UK2Node_ReadLuaValues::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const +{ + UClass* ActionKey = GetClass(); + if (ActionRegistrar.IsOpenForRegistration(ActionKey)) + { + UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass()); + check(NodeSpawner != nullptr); + ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner); + } +} + +FText UK2Node_ReadLuaValues::GetMenuCategory() const +{ + return FText::FromString(FString(TEXT("Luprex|Lua"))); +} + + +#undef LOCTEXT_NAMESPACE diff --git a/Source/Integration/ReadLuaValues.h b/Source/Integration/ReadLuaValues.h new file mode 100644 index 00000000..c7e2d3ae --- /dev/null +++ b/Source/Integration/ReadLuaValues.h @@ -0,0 +1,65 @@ + +#pragma once + +#include "LuprexK2Node.h" + +#include "ReadLuaValues.generated.h" + +class FBlueprintActionDatabaseRegistrar; +class FString; +class UEdGraph; +class UObject; + + +// +// The Lua Call K2Node. +// +UCLASS(MinimalAPI) +class UK2Node_ReadLuaValues : public UlxK2Node +{ + GENERATED_BODY() + +public: + //~ Begin UEdGraphNode Interface. + virtual void AllocateDefaultPins() override; + virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; + virtual bool ShouldShowNodeProperties() const override { return true; } + virtual void PinDefaultValueChanged(UEdGraphPin* Pin) override; + virtual FText GetTooltipText() const override; + virtual FText GetPinDisplayName(const UEdGraphPin* Pin) const override; + //~ End UEdGraphNode Interface. + + //~ Begin UK2Node Interface. + virtual bool IsNodePure() const override { return false; } + virtual void ReconstructNode() override; + virtual bool NodeCausesStructuralBlueprintChange() const override { return true; } + virtual void ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override; + virtual ERedirectType DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const override; + virtual bool IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const override; + virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override; + virtual FText GetMenuCategory() const override; + virtual int32 GetNodeRefreshPriority() const override { return EBaseNodeRefreshPriority::Low_UsesDependentWildcard; } + //~ End UK2Node Interface. + +private: + + virtual bool IsInvoke() const { return true; } + + /** Pin Names for the three built-in Pins **/ + static const FName FunctionPinName; + static const FName PlacePinName; + static const FName ExtraResultsPinName; + static const FName ErrorPinName; + +private: + // Whenever the function value changes, we call + // ReconstructNode, which backs up the value into this + // property. This cache is needed because during + // ReconstructNode, we blow away the function pin. The + // function pin is also absent when the node is first + // created. + // + UPROPERTY() + FString LuaFunctionPrototype = TEXT("class.func(int arg1, int arg2) : int ret1, int ret2"); + +};