A lot of work on the Lua Call interface and some work on animation queues

This commit is contained in:
2025-02-26 14:47:53 -05:00
parent bed4f3e805
commit 72eda3026f
11 changed files with 530 additions and 409 deletions

View File

@@ -114,7 +114,7 @@ void UK2Node_FormatError::CreateCorrectPins()
if (FindPin(DisplayDurationPinName, EGPD_Input) == nullptr) {
UEdGraphPin *P = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Byte, StaticEnum<ElxErrorDisplayDuration>(), DisplayDurationPinName);
P->DefaultValue = TEXT("No_Display");
P->DefaultValue = TEXT("No Show");
P->AutogeneratedDefaultValue = P->DefaultValue;
}

View File

@@ -1,6 +1,7 @@
#include "LuaCall.h"
#include "IntegrationGameModeBase.h"
#include "EdGraphSchema_K2.h"
static void FatalBlueprintError(const TCHAR *message) {
FBlueprintExceptionInfo ExceptionInfo(EBlueprintExceptionType::FatalError, FText::FromString(FString(message)));
@@ -43,6 +44,164 @@ static constexpr uint64_t ParseNameAsToken(std::string_view str) {
}
/////////////////////////////////////////////////////////////////
//
// The lua function prototype parser.
//
/////////////////////////////////////////////////////////////////
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("*")) && !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();
FString Type = Tokens[NextToken++];
if (!IsIdent()) return Syntax();
FString Name = Tokens[NextToken++];
Arguments.Add(Pin(Type, Name));
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()) {
FString Type = Tokens[NextToken++];
if (!IsIdent()) return Syntax();
FString Name = Tokens[NextToken++];
ReturnValues.Add(Pin(Type, Name));
if (!IsLiteral(TEXT(","))) break;
NextToken++;
} else {
return Syntax();
}
}
}
if (NextToken != Tokens.Num()) return Syntax();
}
/////////////////////////////////////////////////////////////////
//
// General functions of the Lua Call Library.
//
/////////////////////////////////////////////////////////////////
UFunction *UlxLuaCallLibrary::GetArgumentPacker(const FString &Type)
{
FString LType = Type.ToLower();
FName PackerName(FString::Printf(TEXT("LuaCallArgument_%s"), *LType));
return UlxLuaCallLibrary::StaticClass()->FindFunctionByName(PackerName);
}
UFunction *UlxLuaCallLibrary::GetReturnValueUnpacker(const FString &Type)
{
FString LType = Type.ToLower();
FName PackerName(FString::Printf(TEXT("LuaCallReturnValue_%s"), *LType));
return UlxLuaCallLibrary::StaticClass()->FindFunctionByName(PackerName);
}
FString UlxLuaCallLibrary::AllFunctionsWithPrefix(const TCHAR *Prefix)
{
FString Result;
for ( TFieldIterator<UFunction> FIT ( UlxLuaCallLibrary::StaticClass(), EFieldIteratorFlags::ExcludeSuper); FIT; ++FIT)
{
UFunction* Function = *FIT;
FString Name = Function->GetName();
if (Name.RemoveFromStart(Prefix))
{
if (!Result.IsEmpty()) Result += TEXT(", ");
Result += Name;
}
}
return Result;
}
/////////////////////////////////////////////////////////////////
//
// General Lua-Callable functions of the Lua Call Library.
//
/////////////////////////////////////////////////////////////////
void UlxLuaCallLibrary::LuaCallBegin(UObject *context, const FString &cname, const FString &fname) {
AIntegrationGameModeBase *mode = AIntegrationGameModeBase::GetFromContext(context);
@@ -53,71 +212,6 @@ void UlxLuaCallLibrary::LuaCallBegin(UObject *context, const FString &cname, con
}
void UlxLuaCallLibrary::LuaCallAddStringParameter(UObject *context, const FString &pstring) {
AIntegrationGameModeBase *mode = AIntegrationGameModeBase::GetFromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
CheckNotEmpty(sb);
sb.write_simple_dynamic_tag(SimpleDynamicTag::STRING);
sb.write_string(pstring);
}
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::TOKEN);
sb.write_string(namestr);
}
void UlxLuaCallLibrary::LuaCallAddFloatParameter(UObject *context, double pfloat) {
AIntegrationGameModeBase *mode = AIntegrationGameModeBase::GetFromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
CheckNotEmpty(sb);
sb.write_simple_dynamic_tag(SimpleDynamicTag::NUMBER);
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);
FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
CheckNotEmpty(sb);
sb.write_simple_dynamic_tag(SimpleDynamicTag::VECTOR);
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);
FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
CheckNotEmpty(sb);
sb.write_simple_dynamic_tag(SimpleDynamicTag::BOOLEAN);
sb.write_bool(pbool);
}
void UlxLuaCallLibrary::LuaCallInvoke(UObject *context, AActor *place) {
AIntegrationGameModeBase *mode = AIntegrationGameModeBase::GetFromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
@@ -166,7 +260,85 @@ ELpxSimpleDynamicTag UlxLuaCallLibrary::LuaCallNextResultType(UObject *context)
}
}
FString UlxLuaCallLibrary::LuaCallGetStringResult(UObject *context) {
/////////////////////////////////////////////////////////////////
//
// Argument Packing functions
//
/////////////////////////////////////////////////////////////////
void UlxLuaCallLibrary::LuaCallArgument_string(UObject *context, const FString &pstring) {
AIntegrationGameModeBase *mode = AIntegrationGameModeBase::GetFromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
CheckNotEmpty(sb);
sb.write_simple_dynamic_tag(SimpleDynamicTag::STRING);
sb.write_string(pstring);
}
void UlxLuaCallLibrary::LuaCallArgument_name(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::TOKEN);
sb.write_string(namestr);
}
void UlxLuaCallLibrary::LuaCallArgument_float(UObject *context, double pfloat) {
AIntegrationGameModeBase *mode = AIntegrationGameModeBase::GetFromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
CheckNotEmpty(sb);
sb.write_simple_dynamic_tag(SimpleDynamicTag::NUMBER);
sb.write_double(pfloat);
}
void UlxLuaCallLibrary::LuaCallArgument_int(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::LuaCallArgument_vector(UObject *context, const FVector &pvector) {
AIntegrationGameModeBase *mode = AIntegrationGameModeBase::GetFromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
CheckNotEmpty(sb);
sb.write_simple_dynamic_tag(SimpleDynamicTag::VECTOR);
sb.write_fvector(pvector);
}
void UlxLuaCallLibrary::LuaCallArgument_vector2d(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::LuaCallArgument_boolean(UObject *context, bool pbool) {
AIntegrationGameModeBase *mode = AIntegrationGameModeBase::GetFromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetBuffer();
CheckNotEmpty(sb);
sb.write_simple_dynamic_tag(SimpleDynamicTag::BOOLEAN);
sb.write_bool(pbool);
}
/////////////////////////////////////////////////////////////////
//
// Return Value Unpacking functions
//
/////////////////////////////////////////////////////////////////
FString UlxLuaCallLibrary::LuaCallReturnValue_string(UObject *context) {
AIntegrationGameModeBase *mode = AIntegrationGameModeBase::GetFromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetResult();
SimpleDynamicTag tag = sb.read_simple_dynamic_tag();
@@ -174,7 +346,7 @@ FString UlxLuaCallLibrary::LuaCallGetStringResult(UObject *context) {
return sb.read_fstring();
}
FName UlxLuaCallLibrary::LuaCallGetNameResult(UObject *context) {
FName UlxLuaCallLibrary::LuaCallReturnValue_name(UObject *context) {
AIntegrationGameModeBase *mode = AIntegrationGameModeBase::GetFromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetResult();
SimpleDynamicTag tag = sb.read_simple_dynamic_tag();
@@ -182,7 +354,7 @@ FName UlxLuaCallLibrary::LuaCallGetNameResult(UObject *context) {
return sb.read_fname();
}
double UlxLuaCallLibrary::LuaCallGetFloatResult(UObject *context) {
double UlxLuaCallLibrary::LuaCallReturnValue_float(UObject *context) {
AIntegrationGameModeBase *mode = AIntegrationGameModeBase::GetFromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetResult();
SimpleDynamicTag tag = sb.read_simple_dynamic_tag();
@@ -190,7 +362,15 @@ double UlxLuaCallLibrary::LuaCallGetFloatResult(UObject *context) {
return sb.read_double();
}
FVector UlxLuaCallLibrary::LuaCallGetVectorResult(UObject *context) {
int UlxLuaCallLibrary::LuaCallReturnValue_int(UObject *context) {
AIntegrationGameModeBase *mode = AIntegrationGameModeBase::GetFromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetResult();
SimpleDynamicTag tag = sb.read_simple_dynamic_tag();
if (tag != SimpleDynamicTag::NUMBER) FatalBlueprintError(TEXT("expected lua to return a number"));
return int(sb.read_double());
}
FVector UlxLuaCallLibrary::LuaCallReturnValue_vector(UObject *context) {
AIntegrationGameModeBase *mode = AIntegrationGameModeBase::GetFromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetResult();
SimpleDynamicTag tag = sb.read_simple_dynamic_tag();
@@ -198,10 +378,20 @@ FVector UlxLuaCallLibrary::LuaCallGetVectorResult(UObject *context) {
return sb.read_fvector();
}
bool UlxLuaCallLibrary::LuaCallGetBooleanResult(UObject *context) {
FVector2D UlxLuaCallLibrary::LuaCallReturnValue_vector2d(UObject *context) {
AIntegrationGameModeBase *mode = AIntegrationGameModeBase::GetFromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetResult();
SimpleDynamicTag tag = sb.read_simple_dynamic_tag();
if (tag != SimpleDynamicTag::VECTOR) FatalBlueprintError(TEXT("expected lua to return a vector"));
FVector v = sb.read_fvector();
return FVector2D(v.X, v.Y);
}
bool UlxLuaCallLibrary::LuaCallReturnValue_boolean(UObject *context) {
AIntegrationGameModeBase *mode = AIntegrationGameModeBase::GetFromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetResult();
SimpleDynamicTag tag = sb.read_simple_dynamic_tag();
if (tag != SimpleDynamicTag::BOOLEAN) FatalBlueprintError(TEXT("expected lua to return a boolean"));
return sb.read_bool();
}

View File

@@ -1,8 +1,18 @@
#pragma once
#include "CoreMinimal.h"
#include "EdGraph/EdGraphPin.h"
#include "LuaCall.generated.h"
/////////////////////////////////////////////////////////////////
//
// These are the types that can actually be packed into
// an argument or return value buffer. Any other type is
// subject to conversion before packing.
//
/////////////////////////////////////////////////////////////////
UENUM(BlueprintType)
enum class ELpxSimpleDynamicTag : uint8 {
None,
@@ -13,13 +23,88 @@ enum class ELpxSimpleDynamicTag : uint8 {
Vector
};
////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////
//
// This UClass is never instantiated. It exists to
// expose certain static functions to the blueprint
// library.
// This is a little parser that parses Lua function 'prototypes'.
// The prototypes look like this:
//
////////////////////////////////////////////////
// class.name(int arg1, int arg2) : int ret1, int ret2
//
// The return values can be omitted if the function has no return
// values. Optionally, there can be one last return value which
// is just an ellipsis. The class name can be asterisk.
//
// For more information about the meaning of all this, see the docs
// for the 'Call Lua' blueprint node.
//
/////////////////////////////////////////////////////////////////
class FlxParsedProto
{
public:
struct Pin
{
FString Type;
FString Name;
Pin(const FString &T, const FString &N) : Type(T), Name(N) {}
};
FString ErrorMessage;
TArray<FString> Tokens;
int NextToken;
FString ClassName;
FString FunctionName;
TArray<Pin> Arguments;
TArray<Pin> ReturnValues;
bool ExtraReturnValues;
private:
// 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();
// Make a syntax error message, using the tokens.
//
void Syntax();
// Empty out the FlxParsedProto.
//
void Empty();
// Parse a function prototype.
//
void Parse(const FString &proto);
public:
// Construct with a prototype.
//
FlxParsedProto(const FString &str) { Parse(str); }
};
/////////////////////////////////////////////////////////////////
//
// To make a Lua Call from inside of a blueprint, use the convenient "Call Lua"
// blueprint node. This node macroexpands into a sequence of low-level
// function calls. This library contains the low-level functions that
// "Call Lua" macroexpands into.
//
// The procedure for making a lua call using the low-level functions is as follows:
//
// * Use LuaCallBegin to put the class name and function name into the argument buffer.
// * Use LuaCallArgumentXXX to put arguments into the argument buffer.
// * Use LuaCallInvoke or LuaCallProbe to actually make the call.
// * Use LuaCallReturnValueXXX to fetch return values from the return buffer.
//
// The two buffers are basically global variables, they are part of the
// IntegrationGameModeBase. This is okay because blueprint is single-threaded.
//
// The following three libraries contain all the low-level functions.
//
/////////////////////////////////////////////////////////////////
UCLASS()
class INTEGRATION_API UlxLuaCallLibrary : public UObject
@@ -27,30 +112,25 @@ class INTEGRATION_API UlxLuaCallLibrary : public UObject
GENERATED_BODY()
public:
// Get an argument packing function or a return value unpacking function.
//
static UFunction *GetArgumentPacker(const FString &Type);
static UFunction *GetReturnValueUnpacker(const FString &Type);
// Get the types supported for arguments and return values, as a documentation string.
//
static FString AllFunctionsWithPrefix(const TCHAR *Prefix);
static FString AllKnownArgumentTypes() { return AllFunctionsWithPrefix(TEXT("LuaCallArgument_")); }
static FString AllKnownReturnValueTypes() { return AllFunctionsWithPrefix(TEXT("LuaCallReturnValue_")); }
public:
//
// Functions that do miscellaneous things.
//
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
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 &Value);
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
static void LuaCallAddNameParameter(UObject *context, const FName &Value);
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
static void LuaCallAddFloatParameter(UObject *context, double Value);
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
static void LuaCallAddIntParameter(UObject *context, int Value);
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
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);
@@ -63,19 +143,55 @@ public:
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
static ELpxSimpleDynamicTag LuaCallNextResultType(UObject *context);
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
static FString LuaCallGetStringResult(UObject *context);
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
static FName LuaCallGetNameResult(UObject *context);
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
static double LuaCallGetFloatResult(UObject *context);
//
// Functions that pack arguments into the call buffer.
//
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
static FVector LuaCallGetVectorResult(UObject *context);
static void LuaCallArgument_string(UObject *context, const FString &Value);
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
static bool LuaCallGetBooleanResult(UObject *context);
static void LuaCallArgument_name(UObject *context, const FName &Value);
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
static void LuaCallArgument_float(UObject *context, double Value);
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
static void LuaCallArgument_int(UObject *context, int Value);
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
static void LuaCallArgument_vector(UObject *context, const FVector &Value);
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
static void LuaCallArgument_vector2d(UObject *context, const FVector2D &Value);
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
static void LuaCallArgument_boolean(UObject *context, bool Value);
//
// Functions that extract return values from the return buffer.
//
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
static FString LuaCallReturnValue_string(UObject *context);
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
static FName LuaCallReturnValue_name(UObject *context);
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
static double LuaCallReturnValue_float(UObject *context);
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
static int LuaCallReturnValue_int(UObject *context);
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
static FVector LuaCallReturnValue_vector(UObject *context);
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
static FVector2D LuaCallReturnValue_vector2d(UObject *context);
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
static bool LuaCallReturnValue_boolean(UObject *context);
};

View File

@@ -91,160 +91,43 @@ 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;
#define LuaCallLibraryFunction(name) (UlxLuaCallLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UlxLuaCallLibrary, name)))
// 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));
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;
}
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"
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:"
"one of the following:\n"
"\n"
" function cname.fname(arg1, arg2)"
" function cname.fname(arg1, arg2) : ret1, ret2\n"
" function cname.fname(arg1, arg2) : ret1, ret2, ...\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"
@@ -255,12 +138,7 @@ UK2Node_LuaCall::UK2Node_LuaCall(const FObjectInitializer& ObjectInitializer)
"\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");
"\n"), *ArgTypes, *RetTypes));
}
void UK2Node_LuaCall::AllocateDefaultPins()
@@ -273,7 +151,7 @@ void UK2Node_LuaCall::CreateCorrectPins()
{
if (LuaFunctionPrototype.IsEmpty())
{
LuaFunctionPrototype = TEXT("function class.func(arg1, arg2) : ret1, ret2");
LuaFunctionPrototype = TEXT("class.func(int arg1, int arg2) : int ret1, int ret2");
}
if (FindPin(UEdGraphSchema_K2::PN_Execute) == nullptr) {
@@ -325,28 +203,44 @@ void UK2Node_LuaCall::CreateCorrectPins()
}
// Create Argument pins in the correct order, reusing old pins where possible.
for (const FString& Name : ParsedProto.Arguments)
for (const FlxParsedProto::Pin & Pin : ParsedProto.Arguments)
{
FName PrefixedName = ArgumentNameAddPrefix(Name);
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) {
if ((OldPin != nullptr) && ((*OldPin)->PinType == PinType)) {
Pins.Emplace(*OldPin);
OldArgumentPins.Remove(PrefixedName);
} else {
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, PrefixedName);
CreatePin(EGPD_Input, PinType, PrefixedName);
}
}
// Create ReturnValue pins in the correct order, reusing old pins where possible.
for (const FString& Name : ParsedProto.ReturnValues)
for (const FlxParsedProto::Pin & Pin : ParsedProto.ReturnValues)
{
FName PrefixedName = ReturnValueNameAddPrefix(Name);
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) {
if ((OldPin != nullptr) && ((*OldPin)->PinType == PinType)) {
Pins.Emplace(*OldPin);
OldReturnValuePins.Remove(PrefixedName);
} else {
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Wildcard, PrefixedName);
CreatePin(EGPD_Output, PinType, PrefixedName);
}
}
@@ -366,53 +260,9 @@ void UK2Node_LuaCall::CreateCorrectPins()
}
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");
return LOCTEXT("LuaCall_Title", "Call a Lua Function");
}
FText UK2Node_LuaCall::GetPinDisplayName(const UEdGraphPin* Pin) const
@@ -455,7 +305,6 @@ void UK2Node_LuaCall::PostEditChangeProperty(struct FPropertyChangedEvent& Prope
void UK2Node_LuaCall::PinConnectionListChanged(UEdGraphPin* Pin)
{
Modify();
SynchronizePinType(Pin);
}
void UK2Node_LuaCall::PinDefaultValueChanged(UEdGraphPin* Pin)
@@ -468,12 +317,6 @@ void UK2Node_LuaCall::PinDefaultValueChanged(UEdGraphPin* Pin)
}
}
void UK2Node_LuaCall::PinTypeChanged(UEdGraphPin* Pin)
{
SynchronizePinType(Pin);
Super::PinTypeChanged(Pin);
}
FText UK2Node_LuaCall::GetTooltipText() const
{
return NodeTooltip;
@@ -482,55 +325,11 @@ FText UK2Node_LuaCall::GetTooltipText() const
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)
{
@@ -555,38 +354,56 @@ void UK2Node_LuaCall::ExpandNode(class FKismetCompilerContext& CompilerContext,
UK2Node_CallFunction* PrevNode = BeginNode;
// Add Packing operations for all argument pins.
for (UEdGraphPin* Pin : Pins)
for (const FlxParsedProto::Pin &PinInfo : ParsedProto.Arguments)
{
if (IsArgumentPin(Pin))
UEdGraphPin *Pin = FindPinChecked(ArgumentNameAddPrefix(PinInfo.Name));
UFunction *PackingFunc = UlxLuaCallLibrary::GetArgumentPacker(PinInfo.Type);
if (PackingFunc == nullptr)
{
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());
}
// 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 = (FindPin(InvokeOrProbePinName, EGPD_Input)->DefaultValue == TEXT("Invoke"));
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(), *ActionNode->GetThenPin());
CompilerContext.MovePinLinksToIntermediate(*GetThenPin(), *PrevNode->GetThenPin());
BreakAllNodeLinks();
}
@@ -653,21 +470,9 @@ bool UK2Node_LuaCall::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEd
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

View File

@@ -52,7 +52,6 @@ class UK2Node_LuaCall : public UK2Node
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.
@@ -73,9 +72,6 @@ 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()

View File

@@ -153,12 +153,22 @@ void UlxTangible::GetCurrentAnimation(AActor *target, FlxAnimationStep &step) {
step = GetActorTangible(target)->AnimTracker.GetCurrentAnimation();
}
void UlxTangible::FinishedAnimation(AActor *target, const FlxAnimationStep &step, bool autoxyz, bool autofacing, bool autoplane) {
void UlxTangible::FinishedAnimation(AActor *target, const FlxAnimationStep &step, bool AutoUpdate) {
if (target == nullptr)
{
UE_LOG(LogBlueprint, Error, TEXT("In FinishedAnimation, tangible cannot be null"));
return;
}
UlxTangible *tan = GetActorTangible(target);
if (autoxyz) step.AutoUpdateXYZ(target);
if (autofacing) step.AutoUpdateFacing(target);
if (autoplane) step.AutoUpdatePlane(&(tan->Plane));
if (AutoUpdate)
{
step.AutoUpdateXYZ(target);
step.AutoUpdateFacing(target);
step.AutoUpdatePlane(&(tan->Plane));
}
tan->AnimTracker.FinishedAnimation(step.Hash);
FString DebugString = UlxAnimationStepLibrary::AnimationStepDebugString(step);
UE_LOG(LogBlueprint, Display, TEXT("Animation Finished: %s"), *DebugString);
}
FString UlxTangible::GetTangiblePlane(AActor* target) {

View File

@@ -131,9 +131,8 @@ public:
UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "target"), Category = "Luprex|Animation Queue")
static void GetCurrentAnimation(AActor *target, FlxAnimationStep &step);
UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "target"), Category = "Luprex|Animation Queue")
static void FinishedAnimation(AActor *target, const FlxAnimationStep &step,
bool AutoUpdateXYZ = true, bool AutoUpdateFacing = true, bool AutoUpdatePlane = true);
UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "Target"), Category = "Luprex|Animation Queue")
static void FinishedAnimation(AActor *Target, const FlxAnimationStep &Step, bool AutoUpdate = true);
UFUNCTION(BlueprintCallable, Meta = (DefaultToSelf = "target"), Category = "Luprex|Animation Queue")
static void SetAutoFinish(AActor *target, const FString &action, const FVector &xyz);