Lots of work on lua call interface, also improved makefiles
This commit is contained in:
@@ -267,7 +267,7 @@ FText UK2Node_FormatError::GetTooltipText() const
|
||||
return NodeTooltip;
|
||||
}
|
||||
|
||||
UEdGraphPin* FindOutputStructPinChecked(UEdGraphNode* Node)
|
||||
static UEdGraphPin* FindOutputStructPinChecked(UEdGraphNode* Node)
|
||||
{
|
||||
check(NULL != Node);
|
||||
UEdGraphPin* OutputPin = NULL;
|
||||
|
||||
@@ -13,6 +13,37 @@ static void CheckNotEmpty(const FlxStreamBuffer &sb) {
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr uint64_t ParseNameAsToken(std::string_view str) {
|
||||
uint64_t result = 0;
|
||||
uint64_t maxint = uint64_t(-1);
|
||||
|
||||
// Leading zeros are not allowed.
|
||||
if ((!str.empty()) && (str[0]=='0')) return 0;
|
||||
|
||||
for (int i = 0; i < int(str.size()); i++) {
|
||||
char c = str[i];
|
||||
uint64_t digit = 0;
|
||||
if ((c >= '0') && (c <= '9')) {
|
||||
digit = uint64_t(c - '0');
|
||||
} else if ((c >= 'a') && (c <= 'z')) {
|
||||
digit = uint64_t(c - 'a' + 10);
|
||||
} else if ((c >= 'A') && (c <= 'Z')) {
|
||||
digit = uint64_t(c - 'A' + 10);
|
||||
} else {
|
||||
return maxint;
|
||||
}
|
||||
// Multiply existing number by 36, then add the digit.
|
||||
// We have two checks to prevent integer overflow.
|
||||
if (result > (maxint / 36)) return 0;
|
||||
result *= 36;
|
||||
if (digit > (maxint - result)) return 0;
|
||||
result += digit;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void UlxLuaCallLibrary::LuaCallBegin(UObject *context, const FString &cname, const FString &fname) {
|
||||
AIntegrationGameModeBase *mode = AIntegrationGameModeBase::GetFromContext(context);
|
||||
FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
|
||||
@@ -31,11 +62,17 @@ void UlxLuaCallLibrary::LuaCallAddStringParameter(UObject *context, const FStrin
|
||||
}
|
||||
|
||||
void UlxLuaCallLibrary::LuaCallAddNameParameter(UObject *context, const FName &pname) {
|
||||
FTCHARToUTF8 utf8str(pname.ToString());
|
||||
std::string_view namestr(utf8str.Get(), utf8str.Length());
|
||||
uint64_t tokvalue = ParseNameAsToken(namestr);
|
||||
if ((tokvalue == 0) && !namestr.empty()) {
|
||||
FatalBlueprintError(TEXT("Names passed to lua must be short, and must contain only lowercase and digits"));
|
||||
}
|
||||
AIntegrationGameModeBase *mode = AIntegrationGameModeBase::GetFromContext(context);
|
||||
FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
|
||||
CheckNotEmpty(sb);
|
||||
sb.write_simple_dynamic_tag(SimpleDynamicTag::STRING);
|
||||
sb.write_fname(pname);
|
||||
sb.write_simple_dynamic_tag(SimpleDynamicTag::TOKEN);
|
||||
sb.write_string(namestr);
|
||||
}
|
||||
|
||||
void UlxLuaCallLibrary::LuaCallAddFloatParameter(UObject *context, double pfloat) {
|
||||
@@ -46,6 +83,13 @@ void UlxLuaCallLibrary::LuaCallAddFloatParameter(UObject *context, double pfloat
|
||||
sb.write_double(pfloat);
|
||||
}
|
||||
|
||||
void UlxLuaCallLibrary::LuaCallAddIntParameter(UObject *context, int value) {
|
||||
AIntegrationGameModeBase *mode = AIntegrationGameModeBase::GetFromContext(context);
|
||||
FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
|
||||
CheckNotEmpty(sb);
|
||||
sb.write_simple_dynamic_tag(SimpleDynamicTag::NUMBER);
|
||||
sb.write_double(value);
|
||||
}
|
||||
|
||||
void UlxLuaCallLibrary::LuaCallAddVectorParameter(UObject *context, const FVector &pvector) {
|
||||
AIntegrationGameModeBase *mode = AIntegrationGameModeBase::GetFromContext(context);
|
||||
@@ -55,6 +99,15 @@ void UlxLuaCallLibrary::LuaCallAddVectorParameter(UObject *context, const FVecto
|
||||
sb.write_fvector(pvector);
|
||||
}
|
||||
|
||||
void UlxLuaCallLibrary::LuaCallAddVector2DParameter(UObject *context, const FVector2D &pvector) {
|
||||
AIntegrationGameModeBase *mode = AIntegrationGameModeBase::GetFromContext(context);
|
||||
FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
|
||||
CheckNotEmpty(sb);
|
||||
sb.write_simple_dynamic_tag(SimpleDynamicTag::VECTOR);
|
||||
sb.write_double(pvector.X);
|
||||
sb.write_double(pvector.Y);
|
||||
sb.write_double(0.0);
|
||||
}
|
||||
|
||||
void UlxLuaCallLibrary::LuaCallAddBooleanParameter(UObject *context, bool pbool) {
|
||||
AIntegrationGameModeBase *mode = AIntegrationGameModeBase::GetFromContext(context);
|
||||
|
||||
@@ -28,28 +28,34 @@ class INTEGRATION_API UlxLuaCallLibrary : public UObject
|
||||
|
||||
public:
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
|
||||
static void LuaCallBegin(UObject *context, const FString &cname, const FString &fname);
|
||||
static void LuaCallBegin(UObject *context, const FString &ClassName, const FString &FunctionName);
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
|
||||
static void LuaCallAddStringParameter(UObject *context, const FString &pstring);
|
||||
static void LuaCallAddStringParameter(UObject *context, const FString &Value);
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
|
||||
static void LuaCallAddNameParameter(UObject *context, const FName &pname);
|
||||
static void LuaCallAddNameParameter(UObject *context, const FName &Value);
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
|
||||
static void LuaCallAddFloatParameter(UObject *context, double pfloat);
|
||||
static void LuaCallAddFloatParameter(UObject *context, double Value);
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
|
||||
static void LuaCallAddVectorParameter(UObject *context, const FVector &pvector);
|
||||
static void LuaCallAddIntParameter(UObject *context, int Value);
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
|
||||
static void LuaCallAddBooleanParameter(UObject *context, bool pbool);
|
||||
static void LuaCallAddVectorParameter(UObject *context, const FVector &Value);
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
|
||||
static void LuaCallAddVector2DParameter(UObject *context, const FVector2D &Value);
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
|
||||
static void LuaCallAddBooleanParameter(UObject *context, bool Value);
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
|
||||
static void LuaCallInvoke(UObject *context, AActor *place);
|
||||
static void LuaCallInvoke(UObject *context, AActor *Place);
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
|
||||
static void LuaCallProbe(UObject *context, AActor *place);
|
||||
static void LuaCallProbe(UObject *context, AActor *Place);
|
||||
|
||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
|
||||
static void InvokeEngioMove(UObject *context, const FString &action, const FVector &xyz, double facing);
|
||||
|
||||
697
Source/Integration/LuaCallNode.cpp
Normal file
697
Source/Integration/LuaCallNode.cpp
Normal file
@@ -0,0 +1,697 @@
|
||||
// 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);
|
||||
}
|
||||
|
||||
// A parser for lua function prototypes.
|
||||
//
|
||||
struct FlxParsedProto {
|
||||
FString ErrorMessage;
|
||||
TArray<FString> Tokens;
|
||||
int NextToken;
|
||||
FString ClassName;
|
||||
FString FunctionName;
|
||||
TArray<FString> Arguments;
|
||||
TArray<FString> ReturnValues;
|
||||
bool ExtraReturnValues;
|
||||
|
||||
// Check the next token to see if it's exactly equal to text.
|
||||
//
|
||||
bool IsLiteral(const TCHAR *text);
|
||||
|
||||
// Check the next token to see if it's an identifier.
|
||||
//
|
||||
bool IsIdent();
|
||||
|
||||
// Empty out the FlxParsedProto.
|
||||
//
|
||||
void Empty();
|
||||
|
||||
// Make a syntax error message, using the tokens.
|
||||
//
|
||||
void Syntax();
|
||||
|
||||
// Parse a function prototype.
|
||||
//
|
||||
void Parse(const FString &proto);
|
||||
|
||||
// Construct with a prototype.
|
||||
//
|
||||
FlxParsedProto(const FString &str) { Parse(str); }
|
||||
};
|
||||
|
||||
bool FlxParsedProto::IsLiteral(const TCHAR *text) {
|
||||
return ((NextToken < Tokens.Num()) && (Tokens[NextToken] == text));
|
||||
}
|
||||
|
||||
bool FlxParsedProto::IsIdent() {
|
||||
return ((NextToken < Tokens.Num()) && (FChar::IsAlpha(Tokens[NextToken][0])));
|
||||
}
|
||||
|
||||
void FlxParsedProto::Empty() {
|
||||
ErrorMessage = TEXT("");
|
||||
Tokens.Empty();
|
||||
NextToken = 0;
|
||||
ClassName = TEXT("");
|
||||
FunctionName = TEXT("");
|
||||
Arguments.Empty();
|
||||
ReturnValues.Empty();
|
||||
ExtraReturnValues = false;
|
||||
}
|
||||
|
||||
void FlxParsedProto::Syntax() {
|
||||
FString Message;
|
||||
if (Tokens.Num() == 0) {
|
||||
Message = TEXT("Function prototype cannot be blank");
|
||||
}
|
||||
for (int i = 0; i < Tokens.Num(); i++) {
|
||||
if (i == NextToken) {
|
||||
Message.Append(TEXT(" ? "));
|
||||
} else {
|
||||
if ((i > 0) && (FChar::IsAlpha(Tokens[i][0])) && (FChar::IsAlpha(Tokens[i-1][0]))) {
|
||||
Message.Append(TEXT(" "));
|
||||
}
|
||||
}
|
||||
Message.Append(Tokens[i]);
|
||||
}
|
||||
Empty();
|
||||
ErrorMessage = Message;
|
||||
}
|
||||
|
||||
void FlxParsedProto::Parse(const FString &str) {
|
||||
Empty();
|
||||
|
||||
// Step one: tokenize.
|
||||
int offset = 0;
|
||||
while (offset < str.Len()) {
|
||||
TCHAR c = str[offset];
|
||||
if (FChar::IsWhitespace(c)) {
|
||||
offset++;
|
||||
} else if (FChar::IsAlpha(c)) {
|
||||
int lo = offset;
|
||||
while ((offset < str.Len()) && FChar::IsAlnum(str[offset])) offset++;
|
||||
Tokens.Add(str.Mid(lo, offset-lo));
|
||||
} else if (str.Mid(offset, 3) == TEXT("...")) {
|
||||
Tokens.Add(str.Mid(offset, 3));
|
||||
offset += 3;
|
||||
} else if (FChar::IsPunct(c)) {
|
||||
Tokens.Add(str.Mid(offset, 1));
|
||||
offset += 1;
|
||||
} else {
|
||||
Empty();
|
||||
ErrorMessage = FString::Printf(TEXT("%s ? %s"), *str.Mid(0, offset), *str.Mid(offset));
|
||||
return;
|
||||
}
|
||||
}
|
||||
NextToken = 0;
|
||||
|
||||
// Step two: Parse.
|
||||
if (!IsLiteral(TEXT("function"))) return Syntax();
|
||||
NextToken++;
|
||||
if (!IsLiteral(TEXT("*")) && !IsIdent()) return Syntax();
|
||||
ClassName = Tokens[NextToken++];
|
||||
if (!IsLiteral(TEXT("."))) return Syntax();
|
||||
NextToken++;
|
||||
if (!IsIdent()) return Syntax();
|
||||
FunctionName = Tokens[NextToken++];
|
||||
if (!IsLiteral(TEXT("("))) return Syntax();
|
||||
NextToken++;
|
||||
if (IsIdent()) {
|
||||
while (true) {
|
||||
if (!IsIdent()) return Syntax();
|
||||
Arguments.Add(Tokens[NextToken++]);
|
||||
if (!IsLiteral(TEXT(","))) break;
|
||||
NextToken++;
|
||||
}
|
||||
}
|
||||
if (!IsLiteral(TEXT(")"))) return Syntax();
|
||||
NextToken++;
|
||||
if (IsLiteral(TEXT(":"))) {
|
||||
NextToken++;
|
||||
while (true) {
|
||||
if (IsLiteral(TEXT("..."))) {
|
||||
ExtraReturnValues = true;
|
||||
NextToken++;
|
||||
break;
|
||||
} else if (IsIdent()) {
|
||||
ReturnValues.Add(Tokens[NextToken++]);
|
||||
if (!IsLiteral(TEXT(","))) break;
|
||||
NextToken++;
|
||||
} else {
|
||||
return Syntax();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (NextToken != Tokens.Num()) return Syntax();
|
||||
}
|
||||
|
||||
UK2Node_LuaCall::UK2Node_LuaCall(const FObjectInitializer& ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
{
|
||||
NodeTooltip = LOCTEXT("NodeTooltip",
|
||||
"Probe or Invoke a Lua function.\n"
|
||||
"\n"
|
||||
"The lua function prototype must be a hardwired string which must look like\n"
|
||||
"one of the following:"
|
||||
"\n"
|
||||
" function cname.fname(arg1, arg2)"
|
||||
" function cname.fname(arg1, arg2) : ret1, ret2\n"
|
||||
" function cname.fname(arg1, arg2) : ret1, ret2, ...\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"
|
||||
"Argument and return value pins have wildcard types initially, you can\n"
|
||||
"hook them to inputs and outputs of the following types:\n"
|
||||
"\n"
|
||||
" string, name, float, boolean, vector\n"
|
||||
"\n");
|
||||
}
|
||||
|
||||
void UK2Node_LuaCall::AllocateDefaultPins()
|
||||
{
|
||||
Super::AllocateDefaultPins();
|
||||
CreateCorrectPins();
|
||||
}
|
||||
|
||||
void UK2Node_LuaCall::CreateCorrectPins()
|
||||
{
|
||||
if (LuaFunctionPrototype.IsEmpty())
|
||||
{
|
||||
LuaFunctionPrototype = TEXT("function class.func(arg1, arg2) : ret1, 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<ElxInvokeOrProbe>(), 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<FName, UEdGraphPin *> OldArgumentPins;
|
||||
TMap<FName, UEdGraphPin *> 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 FString& Name : ParsedProto.Arguments)
|
||||
{
|
||||
FName PrefixedName = ArgumentNameAddPrefix(Name);
|
||||
UEdGraphPin **OldPin = OldArgumentPins.Find(PrefixedName);
|
||||
if (OldPin != nullptr) {
|
||||
Pins.Emplace(*OldPin);
|
||||
OldArgumentPins.Remove(PrefixedName);
|
||||
} else {
|
||||
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, PrefixedName);
|
||||
}
|
||||
}
|
||||
|
||||
// Create ReturnValue pins in the correct order, reusing old pins where possible.
|
||||
for (const FString& Name : ParsedProto.ReturnValues)
|
||||
{
|
||||
FName PrefixedName = ReturnValueNameAddPrefix(Name);
|
||||
UEdGraphPin **OldPin = OldReturnValuePins.Find(PrefixedName);
|
||||
if (OldPin != nullptr) {
|
||||
Pins.Emplace(*OldPin);
|
||||
OldReturnValuePins.Remove(PrefixedName);
|
||||
} else {
|
||||
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Wildcard, 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();
|
||||
}
|
||||
|
||||
|
||||
void UK2Node_LuaCall::SynchronizePinType(UEdGraphPin* Pin)
|
||||
{
|
||||
if (IsArgumentPin(Pin) || IsReturnValuePin(Pin))
|
||||
{
|
||||
const UEdGraphSchema_K2* K2Schema = Cast<const UEdGraphSchema_K2>(GetSchema());
|
||||
|
||||
bool bPinTypeChanged = false;
|
||||
if (Pin->LinkedTo.Num() == 0)
|
||||
{
|
||||
static const FEdGraphPinType WildcardPinType = FEdGraphPinType(UEdGraphSchema_K2::PC_Wildcard, NAME_None, nullptr, EPinContainerType::None, false, FEdGraphTerminalType());
|
||||
|
||||
// Ensure wildcard
|
||||
if (Pin->PinType != WildcardPinType)
|
||||
{
|
||||
Pin->PinType = WildcardPinType;
|
||||
bPinTypeChanged = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UEdGraphPin* OtherPin = Pin->LinkedTo[0];
|
||||
|
||||
// Take the type of the connected pin
|
||||
if (Pin->PinType != OtherPin->PinType)
|
||||
{
|
||||
Pin->PinType = OtherPin->PinType;
|
||||
bPinTypeChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (bPinTypeChanged)
|
||||
{
|
||||
// Let the graph know to refresh
|
||||
GetGraph()->NotifyNodeChanged(this);
|
||||
|
||||
UBlueprint* Blueprint = GetBlueprint();
|
||||
if (!Blueprint->bBeingCompiled)
|
||||
{
|
||||
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FText UK2Node_LuaCall::GetNodeTitle(ENodeTitleType::Type TitleType) const
|
||||
{
|
||||
return LOCTEXT("LuaCall_Title", "Probe or Invoke 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();
|
||||
SynchronizePinType(Pin);
|
||||
}
|
||||
|
||||
void UK2Node_LuaCall::PinDefaultValueChanged(UEdGraphPin* Pin)
|
||||
{
|
||||
if(IsFunctionPin(Pin))
|
||||
{
|
||||
LuaFunctionPrototype = Pin->DefaultValue;
|
||||
CreateCorrectPins();
|
||||
GetGraph()->NotifyNodeChanged(this);
|
||||
}
|
||||
}
|
||||
|
||||
void UK2Node_LuaCall::PinTypeChanged(UEdGraphPin* Pin)
|
||||
{
|
||||
SynchronizePinType(Pin);
|
||||
Super::PinTypeChanged(Pin);
|
||||
}
|
||||
|
||||
FText UK2Node_LuaCall::GetTooltipText() const
|
||||
{
|
||||
return NodeTooltip;
|
||||
}
|
||||
|
||||
void UK2Node_LuaCall::PostReconstructNode()
|
||||
{
|
||||
Super::PostReconstructNode();
|
||||
|
||||
UEdGraph* OuterGraph = GetGraph();
|
||||
if (!IsTemplate() && OuterGraph && OuterGraph->Schema) {
|
||||
for (UEdGraphPin* CurrentPin : Pins)
|
||||
{
|
||||
SynchronizePinType(CurrentPin);
|
||||
}
|
||||
}
|
||||
|
||||
CreateCorrectPins();
|
||||
}
|
||||
|
||||
#define LuaCallLibraryFunction(name) (UlxLuaCallLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxLuaCallLibrary, name)))
|
||||
|
||||
static UFunction *GetArgumentPackingFunction(const FEdGraphPinType &Type)
|
||||
{
|
||||
if (Type.PinCategory == UEdGraphSchema_K2::PC_Real)
|
||||
{
|
||||
return LuaCallLibraryFunction(LuaCallAddFloatParameter);
|
||||
}
|
||||
if (Type.PinCategory == UEdGraphSchema_K2::PC_Int)
|
||||
{
|
||||
return LuaCallLibraryFunction(LuaCallAddIntParameter);
|
||||
}
|
||||
else if (Type.PinCategory == UEdGraphSchema_K2::PC_Boolean)
|
||||
{
|
||||
return LuaCallLibraryFunction(LuaCallAddBooleanParameter);
|
||||
}
|
||||
else if (Type.PinCategory == UEdGraphSchema_K2::PC_Name)
|
||||
{
|
||||
return LuaCallLibraryFunction(LuaCallAddNameParameter);
|
||||
}
|
||||
else if (Type.PinCategory == UEdGraphSchema_K2::PC_String)
|
||||
{
|
||||
return LuaCallLibraryFunction(LuaCallAddStringParameter);
|
||||
}
|
||||
else if ((Type.PinCategory == UEdGraphSchema_K2::PC_Struct) && (Type.PinSubCategoryObject == TBaseStructure<FVector>::Get()))
|
||||
{
|
||||
return LuaCallLibraryFunction(LuaCallAddVectorParameter);
|
||||
}
|
||||
else if ((Type.PinCategory == UEdGraphSchema_K2::PC_Struct) && (Type.PinSubCategoryObject == TBaseStructure<FVector2D>::Get()))
|
||||
{
|
||||
return LuaCallLibraryFunction(LuaCallAddVector2DParameter);
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
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<UK2Node_CallFunction>(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 (UEdGraphPin* Pin : Pins)
|
||||
{
|
||||
if (IsArgumentPin(Pin))
|
||||
{
|
||||
UFunction *PackingFunc = GetArgumentPackingFunction(Pin->PinType);
|
||||
if (PackingFunc != nullptr)
|
||||
{
|
||||
UK2Node_CallFunction *PackNode = MakeCallFunctionNode(PackingFunc);
|
||||
CompilerContext.MovePinLinksToIntermediate(*Pin, *PackNode->FindPinChecked(TEXT("Value")));
|
||||
PrevNode->GetThenPin()->MakeLinkTo(PackNode->GetExecPin());
|
||||
PrevNode = PackNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
FText PinName = GetPinDisplayName(Pin);
|
||||
FText PinType = FText::FromName(Pin->PinType.PinCategory);
|
||||
FText Error = FText::Format(LOCTEXT("Error_UnexpectedPinType", "Pin '{0}' has an unexpected type: {1}"), PinName, PinType);
|
||||
CompilerContext.MessageLog.Error(*Error.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the invoke or probe node.
|
||||
bool IsInvoke = (FindPin(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());
|
||||
|
||||
// Link up the Exec pins.
|
||||
CompilerContext.MovePinLinksToIntermediate(*GetExecPin(), *BeginNode->GetExecPin());
|
||||
CompilerContext.MovePinLinksToIntermediate(*GetThenPin(), *ActionNode->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<const UEdGraphSchema_K2>(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<UK2Node>(NewPin->GetOwningNode()))
|
||||
{
|
||||
// if you don't have matching pin, now check if there is any redirect param set
|
||||
TArray<FString> 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;
|
||||
}
|
||||
|
||||
// Argument input pins may only be connected to packable types.
|
||||
if (IsArgumentPin(MyPin))
|
||||
{
|
||||
UFunction *Packer = GetArgumentPackingFunction(OtherPin->PinType);
|
||||
if (Packer == nullptr)
|
||||
{
|
||||
OutReason = LOCTEXT("Error_InvalidArgumentType", "Lua Call Arguments may be float, boolean, string, name, or vector.").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 FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::Text);
|
||||
}
|
||||
|
||||
|
||||
#undef LOCTEXT_NAMESPACE
|
||||
87
Source/Integration/LuaCallNode.h
Normal file
87
Source/Integration/LuaCallNode.h
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BlueprintErrors.h"
|
||||
#include "Containers/Array.h"
|
||||
#include "CoreMinimal.h"
|
||||
#include "EdGraph/EdGraphNode.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
#include "HAL/Platform.h"
|
||||
#include "Internationalization/Text.h"
|
||||
#include "K2Node.h"
|
||||
#include "UObject/NameTypes.h"
|
||||
#include "UObject/ObjectMacros.h"
|
||||
#include "UObject/UObjectGlobals.h"
|
||||
#include "BlueprintErrors.h"
|
||||
|
||||
#include "LuaCallNode.generated.h"
|
||||
|
||||
class FBlueprintActionDatabaseRegistrar;
|
||||
class FString;
|
||||
class UEdGraph;
|
||||
class UObject;
|
||||
|
||||
UENUM(BlueprintType)
|
||||
enum class ElxInvokeOrProbe : uint8 {
|
||||
|
||||
/* Invoke the lua function: call it on the server, mutating the world state. */
|
||||
Invoke,
|
||||
|
||||
/* Probe the lua function: call it locally, not mutating the world state. */
|
||||
Probe,
|
||||
};
|
||||
|
||||
|
||||
|
||||
//
|
||||
// The Lua Call K2Node.
|
||||
//
|
||||
UCLASS(MinimalAPI)
|
||||
class UK2Node_LuaCall : public UK2Node
|
||||
{
|
||||
GENERATED_UCLASS_BODY()
|
||||
|
||||
//~ Begin UObject Interface
|
||||
virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
|
||||
//~ End UObject Interface
|
||||
|
||||
//~ Begin UEdGraphNode Interface.
|
||||
virtual void AllocateDefaultPins() override;
|
||||
virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
|
||||
virtual bool ShouldShowNodeProperties() const override { return true; }
|
||||
virtual void PinConnectionListChanged(UEdGraphPin* Pin) override;
|
||||
virtual void PinDefaultValueChanged(UEdGraphPin* Pin) override;
|
||||
virtual void PinTypeChanged(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 PostReconstructNode() 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:
|
||||
/** Create all necessary pins. */
|
||||
void CreateCorrectPins();
|
||||
|
||||
/** Synchronize the type of the given pin with the type its connected to, or reset it to a wildcard pin if there's no connection */
|
||||
void SynchronizePinType(UEdGraphPin* Pin);
|
||||
|
||||
private:
|
||||
/** The lua function prototype, which must be saved as a property **/
|
||||
UPROPERTY()
|
||||
FString LuaFunctionPrototype;
|
||||
|
||||
/** Tooltip text for this node. */
|
||||
FText NodeTooltip;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user