// Copyright Epic Games, Inc. All Rights Reserved. #include "LuaCallNode.h" #include "BlueprintActionDatabaseRegistrar.h" #include "BlueprintNodeSpawner.h" #include "Containers/EnumAsByte.h" #include "Containers/UnrealString.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphSchema.h" #include "EdGraphSchema_K2.h" #include "EdGraphSchema_K2_Actions.h" #include "EditorCategoryUtils.h" #include "Engine/Blueprint.h" #include "HAL/PlatformCrt.h" #include "Internationalization/Internationalization.h" #include "K2Node_CallFunction.h" #include "K2Node_MakeArray.h" #include "K2Node_MakeStruct.h" #include "Kismet/KismetMathLibrary.h" #include "Kismet/KismetTextLibrary.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/CompilerResultsLog.h" #include "KismetCompiler.h" #include "LuaCall.h" #include "Math/Vector2D.h" #include "Misc/AssertionMacros.h" #include "Misc/CString.h" #include "ScopedTransaction.h" #include "Templates/Casts.h" #include "Templates/SubclassOf.h" #include "UObject/Class.h" #include "UObject/ObjectPtr.h" #include "UObject/Package.h" #include "UObject/UnrealNames.h" #include "UObject/UnrealType.h" #include "UObject/WeakObjectPtr.h" #include "UObject/WeakObjectPtrTemplates.h" #define LOCTEXT_NAMESPACE "LuaCall" // All argument pins will have internal Names that start with "A:" static bool IsArgumentPin(const UEdGraphPin *Pin) { TCHAR pname[FName::StringBufferSize]; Pin->PinName.ToString(pname); return pname[0] == 'A' && pname[1] == ':'; } static FName ArgumentNameAddPrefix(const FString &name) { FString Prefixed = FString("A:") + name; return FName(*Prefixed); } static FString ArgumentNameRemovePrefix(const FName &name) { return name.ToString().Mid(2, FName::StringBufferSize); } // All return value pins will have internal Names that start with "R:" static bool IsReturnValuePin(const UEdGraphPin *Pin) { TCHAR pname[FName::StringBufferSize]; Pin->PinName.ToString(pname); return pname[0] == 'R' && pname[1] == ':'; } static FName ReturnValueNameAddPrefix(const FString &name) { FString Prefixed = FString("R:") + name; return FName(*Prefixed); } static FString ReturnValueNameRemovePrefix(const FName &name) { return name.ToString().Mid(2, FName::StringBufferSize); } // Builtin pins will have names with no prefixes. static const FName FunctionPinName(TEXT("Lua Function Prototype")); static bool IsFunctionPin(const UEdGraphPin *Pin) { return (Pin->PinName == FunctionPinName); } static const FName InvokeOrProbePinName(TEXT("Invoke or Probe")); static bool IsInvokeOrProbePin(const UEdGraphPin *Pin) { return (Pin->PinName == InvokeOrProbePinName); } static const FName PlacePinName(TEXT("Place Tangible")); static bool IsPlacePin(const UEdGraphPin *Pin) { return (Pin->PinName == PlacePinName); } #define LuaCallLibraryFunction(name) (UlxLuaCallLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxLuaCallLibrary, name))) static FEdGraphPinType GetPinType(const FProperty *Property) { FEdGraphPinType PinType; bool IsWeak; FName PinCat, PinSubCat; UObject *PinSubCatObj = nullptr; UEdGraphSchema_K2::GetPropertyCategoryInfo(Property, PinCat, PinSubCat, PinSubCatObj, IsWeak); PinType.PinCategory = PinCat; PinType.PinSubCategory = PinSubCat; PinType.PinSubCategoryObject = PinSubCatObj; return PinType; } UK2Node_LuaCall::UK2Node_LuaCall(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { FString ArgTypes = UlxLuaCallLibrary::AllKnownArgumentTypes(); FString RetTypes = UlxLuaCallLibrary::AllKnownReturnValueTypes(); NodeTooltip = 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"), *ArgTypes, *RetTypes)); } void UK2Node_LuaCall::AllocateDefaultPins() { Super::AllocateDefaultPins(); CreateCorrectPins(); } void UK2Node_LuaCall::CreateCorrectPins() { if (LuaFunctionPrototype.IsEmpty()) { LuaFunctionPrototype = TEXT("class.func(int arg1, int arg2) : int ret1, int ret2"); } if (FindPin(UEdGraphSchema_K2::PN_Execute) == nullptr) { CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute); } if (FindPin(UEdGraphSchema_K2::PN_Then) == nullptr) { CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then); } if (FindPin(FunctionPinName, EGPD_Input) == nullptr) { UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_String, FunctionPinName); P->DefaultValue = LuaFunctionPrototype; } if (FindPin(InvokeOrProbePinName, EGPD_Input) == nullptr) { UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, StaticEnum(), InvokeOrProbePinName); P->DefaultValue = TEXT("Probe"); P->AutogeneratedDefaultValue = P->DefaultValue; } if (FindPin(PlacePinName, EGPD_Input) == nullptr) { UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, AActor::StaticClass(), PlacePinName); } // Parse the lua function prototype. FlxParsedProto ParsedProto(LuaFunctionPrototype); if (!ParsedProto.ErrorMessage.IsEmpty()) { bHasCompilerMessage = true; ErrorType = EMessageSeverity::Error; ErrorMsg = FString::Printf(TEXT("Syntax error in lua function prototype: %s"), *ParsedProto.ErrorMessage); } // Transfer all Existing argument and return value pins to the Old Pins Maps. TMap OldArgumentPins; TMap OldReturnValuePins; for (auto It = Pins.CreateIterator(); It; ++It) { UEdGraphPin* CheckPin = *It; if (IsArgumentPin(CheckPin)) { OldArgumentPins.Add(CheckPin->PinName, CheckPin); It.RemoveCurrent(); } if (IsReturnValuePin(CheckPin)) { OldReturnValuePins.Add(CheckPin->PinName, CheckPin); It.RemoveCurrent(); } } // Create Argument pins in the correct order, reusing old pins where possible. for (const FlxParsedProto::Pin & Pin : ParsedProto.Arguments) { FName PrefixedName = ArgumentNameAddPrefix(Pin.Name); UFunction *Accessor = UlxLuaCallLibrary::GetArgumentPacker(Pin.Type); if (Accessor == nullptr) { bHasCompilerMessage = true; ErrorType = EMessageSeverity::Error; ErrorMsg = FString::Printf(TEXT("Unknown argument type: %s"), *Pin.Type); continue; } FEdGraphPinType PinType = GetPinType(Accessor->FindPropertyByName(TEXT("Value"))); UEdGraphPin **OldPin = OldArgumentPins.Find(PrefixedName); if ((OldPin != nullptr) && ((*OldPin)->PinType == PinType)) { Pins.Emplace(*OldPin); OldArgumentPins.Remove(PrefixedName); } else { CreatePin(EGPD_Input, PinType, PrefixedName); } } // Create ReturnValue pins in the correct order, reusing old pins where possible. for (const FlxParsedProto::Pin & Pin : ParsedProto.ReturnValues) { FName PrefixedName = ReturnValueNameAddPrefix(Pin.Name); UFunction *Accessor = UlxLuaCallLibrary::GetReturnValueUnpacker(Pin.Type); if (Accessor == nullptr) { bHasCompilerMessage = true; ErrorType = EMessageSeverity::Error; ErrorMsg = FString::Printf(TEXT("Unknown return value type: %s"), *Pin.Type); continue; } FEdGraphPinType PinType = GetPinType(Accessor->GetReturnProperty()); UEdGraphPin **OldPin = OldReturnValuePins.Find(PrefixedName); if ((OldPin != nullptr) && ((*OldPin)->PinType == PinType)) { Pins.Emplace(*OldPin); OldReturnValuePins.Remove(PrefixedName); } else { CreatePin(EGPD_Output, PinType, PrefixedName); } } // Delete any unused pins. for (auto &iter : OldArgumentPins) { iter.Value->Modify(); iter.Value->MarkAsGarbage(); } for (auto &iter : OldReturnValuePins) { iter.Value->Modify(); iter.Value->MarkAsGarbage(); } OldArgumentPins.Empty(); OldReturnValuePins.Empty(); } FText UK2Node_LuaCall::GetNodeTitle(ENodeTitleType::Type TitleType) const { return LOCTEXT("LuaCall_Title", "Call a Lua Function"); } FText UK2Node_LuaCall::GetPinDisplayName(const UEdGraphPin* Pin) const { // The exec pins don't need labels. if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) { return FText::GetEmpty(); } // Many pins can go unlabeled if they have default values. if (IsFunctionPin(Pin) || IsInvokeOrProbePin(Pin)) { if (Pin->LinkedTo.Num() == 0) { return FText::GetEmpty(); } } // For argument pins, we must strip off the Argument Pin Prefix. if (IsArgumentPin(Pin)) { return FText::FromString(ArgumentNameRemovePrefix(Pin->PinName)); } // For return value pins, we must strip off the Return Value Pin Prefix. if (IsReturnValuePin(Pin)) { return FText::FromString(ReturnValueNameRemovePrefix(Pin->PinName)); } // Otherwise, just return the Pin Name the normal way. return FText::FromName(Pin->PinName); } void UK2Node_LuaCall::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) { Super::PostEditChangeProperty(PropertyChangedEvent); GetGraph()->NotifyNodeChanged(this); } void UK2Node_LuaCall::PinConnectionListChanged(UEdGraphPin* Pin) { Modify(); } void UK2Node_LuaCall::PinDefaultValueChanged(UEdGraphPin* Pin) { if(IsFunctionPin(Pin)) { LuaFunctionPrototype = Pin->DefaultValue; CreateCorrectPins(); GetGraph()->NotifyNodeChanged(this); } } FText UK2Node_LuaCall::GetTooltipText() const { return NodeTooltip; } void UK2Node_LuaCall::PostReconstructNode() { Super::PostReconstructNode(); CreateCorrectPins(); } #define LuaCallLibraryFunction(name) (UlxLuaCallLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxLuaCallLibrary, name))) void UK2Node_LuaCall::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) { Super::ExpandNode(CompilerContext, SourceGraph); const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema(); // Define a local function that creates a CallFunctionNode auto MakeCallFunctionNode = [&](UFunction *func) { UK2Node_CallFunction* CallNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); CallNode->SetFromFunction(func); CallNode->AllocateDefaultPins(); CompilerContext.MessageLog.NotifyIntermediateObjectCreation(CallNode, this); return CallNode; }; // The BeginNode function packs the class name and function name into the call buffer. FlxParsedProto ParsedProto(LuaFunctionPrototype); UK2Node_CallFunction* BeginNode = MakeCallFunctionNode(LuaCallLibraryFunction(LuaCallBegin)); Schema->TrySetDefaultValue(*BeginNode->FindPinChecked(TEXT("ClassName")), ParsedProto.ClassName); Schema->TrySetDefaultValue(*BeginNode->FindPinChecked(TEXT("FunctionName")), ParsedProto.FunctionName); UK2Node_CallFunction* PrevNode = BeginNode; // Add Packing operations for all argument pins. for (const FlxParsedProto::Pin &PinInfo : ParsedProto.Arguments) { UEdGraphPin *Pin = FindPinChecked(ArgumentNameAddPrefix(PinInfo.Name)); 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(PackingFunc); CompilerContext.MovePinLinksToIntermediate(*Pin, *PackNode->FindPinChecked(TEXT("Value"))); PrevNode->GetThenPin()->MakeLinkTo(PackNode->GetExecPin()); PrevNode = PackNode; } // Add the invoke or probe node. bool IsInvoke = (FindPinChecked(InvokeOrProbePinName, EGPD_Input)->DefaultValue == TEXT("Invoke")); UFunction *Action = IsInvoke ? LuaCallLibraryFunction(LuaCallInvoke) : LuaCallLibraryFunction(LuaCallProbe); UK2Node_CallFunction* ActionNode = MakeCallFunctionNode(Action); CompilerContext.MovePinLinksToIntermediate(*FindPin(PlacePinName, EGPD_Input), *ActionNode->FindPinChecked(TEXT("Place"))); PrevNode->GetThenPin()->MakeLinkTo(ActionNode->GetExecPin()); PrevNode = ActionNode; // Add Unpacking operations for all argument pins. for (const FlxParsedProto::Pin &PinInfo : ParsedProto.ReturnValues) { UEdGraphPin *Pin = FindPinChecked(ReturnValueNameAddPrefix(PinInfo.Name)); 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(UnpackingFunc); CompilerContext.MovePinLinksToIntermediate(*Pin, *UnpackNode->FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue)); PrevNode->GetThenPin()->MakeLinkTo(UnpackNode->GetExecPin()); PrevNode = UnpackNode; } // Make sure we didn't have return values for an invoke. if (IsInvoke && (ParsedProto.ReturnValues.Num() > 0)) { CompilerContext.MessageLog.Error(TEXT("Lua Call in Invoke mode does not support return values")); } // Link up the Exec pins. CompilerContext.MovePinLinksToIntermediate(*GetExecPin(), *BeginNode->GetExecPin()); CompilerContext.MovePinLinksToIntermediate(*GetThenPin(), *PrevNode->GetThenPin()); BreakAllNodeLinks(); } UK2Node::ERedirectType UK2Node_LuaCall::DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const { ERedirectType RedirectType = ERedirectType_None; // if the pin names do match if (NewPin->PinName.ToString().Equals(OldPin->PinName.ToString(), ESearchCase::CaseSensitive)) { // Make sure we're not dealing with a menu node UEdGraph* OuterGraph = GetGraph(); if( OuterGraph && OuterGraph->Schema ) { const UEdGraphSchema_K2* K2Schema = Cast(GetSchema()); if( !K2Schema || K2Schema->IsSelfPin(*NewPin) || K2Schema->ArePinTypesCompatible(OldPin->PinType, NewPin->PinType) ) { RedirectType = ERedirectType_Name; } else { RedirectType = ERedirectType_None; } } } else { // try looking for a redirect if it's a K2 node if (UK2Node* Node = Cast(NewPin->GetOwningNode())) { // if you don't have matching pin, now check if there is any redirect param set TArray OldPinNames; GetRedirectPinNames(*OldPin, OldPinNames); FName NewPinName; RedirectType = ShouldRedirectParam(OldPinNames, /*out*/ NewPinName, Node); // make sure they match if ((RedirectType != ERedirectType_None) && (!NewPin->PinName.ToString().Equals(NewPinName.ToString(), ESearchCase::CaseSensitive))) { RedirectType = ERedirectType_None; } } } return RedirectType; } bool UK2Node_LuaCall::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const { // The function pin cannot be connected. if (IsFunctionPin(MyPin)) { OutReason = LOCTEXT("Error_FunctionPrototypeMustBeHardwired", "Lua function prototype must be a hardwired constant.").ToString(); return true; } // The invoke-or-probe pin cannot be connected. if (IsInvokeOrProbePin(MyPin)) { OutReason = LOCTEXT("Error_InvokeOrProbeMustBeHardwired", "Invoke vs Probe must be a hardwired constant.").ToString(); return true; } return Super::IsConnectionDisallowed(MyPin, OtherPin, OutReason); } void UK2Node_LuaCall::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const { // actions get registered under specific object-keys; the idea is that // actions might have to be updated (or deleted) if their object-key is // mutated (or removed)... here we use the node's class (so if the node // type disappears, then the action should go with it) UClass* ActionKey = GetClass(); // to keep from needlessly instantiating a UBlueprintNodeSpawner, first // check to make sure that the registrar is looking for actions of this type // (could be regenerating actions for a specific asset, and therefore the // registrar would only accept actions corresponding to that asset) if (ActionRegistrar.IsOpenForRegistration(ActionKey)) { UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass()); check(NodeSpawner != nullptr); ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner); } } FText UK2Node_LuaCall::GetMenuCategory() const { return FText::FromString(FString(TEXT("Luprex|Lua"))); } #undef LOCTEXT_NAMESPACE