Move a lot of EngineWrapper interfaces out of LuprexGameModeBase

This commit is contained in:
2026-02-14 03:35:08 -05:00
parent d046ef8161
commit 2eacc12cad
10 changed files with 155 additions and 165 deletions

View File

@@ -95,6 +95,29 @@ enum class ElxSuccessOrWrongType : uint8 {
WrongType, WrongType,
}; };
////////////////////////////////////////////////////////////
//
// ElxLuaSyntaxCheck
//
// Classifies console commands syntactically:
//
// SlashCommand: starts with a slash, not lua
// Whitespace: the input only contains whitespace
// ValidLua: the input is valid lua code
// TruncatedLua: the input is truncated
// InvalidLua: invalid lua
//
////////////////////////////////////////////////////////////
UENUM(BlueprintType)
enum class ElxLuaSyntaxCheck : uint8 {
SlashCommand,
Whitespace,
ValidLua,
TruncatedLua,
InvalidLua,
};
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// //
// Log Categories // Log Categories

View File

@@ -83,3 +83,42 @@ StringViewVec FlxLockedWrapper::GetAnimationQueues(IdView ids) {
} }
return result; return result;
} }
ElxLuaSyntaxCheck FlxLockedWrapper::ValidateLuaExpr(const FString &Code, FString &ErrorMessage) {
FTCHARToUTF8 UCode(*Code);
uint32_t retpklen;
const char *retpk;
Lockable.Wrapper.play_access(Get(), AccessKind::VALIDATE_LUA_EXPR, 0, UCode.Length(), UCode.Get(), &retpklen, &retpk);
ErrorMessage = FString(retpklen, (const UTF8CHAR*)retpk);
if (ErrorMessage.IsEmpty())
return ElxLuaSyntaxCheck::ValidLua;
if (ErrorMessage == TEXT("slash command"))
return ElxLuaSyntaxCheck::SlashCommand;
if (ErrorMessage == TEXT("white space"))
return ElxLuaSyntaxCheck::Whitespace;
if (ErrorMessage == TEXT("truncated lua"))
return ElxLuaSyntaxCheck::TruncatedLua;
return ElxLuaSyntaxCheck::InvalidLua;
}
void FlxLockedWrapper::ProbeLuaFunction(std::string_view datapk, int64 place_id, TFunction<void(std::string_view)> OnResult) {
if (place_id == 0) place_id = GetActor();
uint32_t retpklen;
const char *retpk;
Lockable.Wrapper.play_access(Get(), AccessKind::PROBE_LUA_CALL, place_id, datapk.size(), datapk.data(), &retpklen, &retpk);
OnResult(std::string_view(retpk, retpklen));
}
void FlxLockedWrapper::InvokeLuaFunction(std::string_view datapk, int64 place_id) {
if (place_id == 0) place_id = GetActor();
uint32_t retpklen;
const char *retpk;
Lockable.Wrapper.play_access(Get(), AccessKind::INVOKE_LUA_CALL, place_id, datapk.size(), datapk.data(), &retpklen, &retpk);
}
void FlxLockedWrapper::InvokeLuaExpr(const FString &Code) {
FTCHARToUTF8 UCode(*Code);
uint32_t retpklen;
const char *retpk;
Lockable.Wrapper.play_access(Get(), AccessKind::INVOKE_LUA_EXPR, 0, UCode.Length(), UCode.Get(), &retpklen, &retpk);
}

View File

@@ -119,4 +119,28 @@ public:
// next time you call this. // next time you call this.
// //
StringViewVec GetAnimationQueues(IdView ids); StringViewVec GetAnimationQueues(IdView ids);
// Call a Lua function. The datapk contains the
// serialized class name, function name, and
// arguments. If place_id is 0, defaults to the
// current actor. The OnResult callback receives
// the raw return data while the lock is held;
// use it to copy the data before it goes away.
//
void ProbeLuaFunction(std::string_view datapk, int64 place_id, TFunction<void(std::string_view)> OnResult);
// Invoke a Lua function (fire-and-forget, no
// return values).
//
void InvokeLuaFunction(std::string_view datapk, int64 place_id);
// Validate a Lua expression. Returns a syntax
// classification and an error message. The error
// message is empty if the code is valid.
//
ElxLuaSyntaxCheck ValidateLuaExpr(const FString &Code, FString &ErrorMessage);
// Execute a Lua expression.
//
void InvokeLuaExpr(const FString &Code);
}; };

View File

@@ -1,6 +1,7 @@
#include "LuaCall.h" #include "LuaCall.h"
#include "LuprexGameModeBase.h" #include "LuprexGameModeBase.h"
#include "Tangible.h"
#include "StringDecoder.h" #include "StringDecoder.h"
#include "EdGraphSchema_K2.h" #include "EdGraphSchema_K2.h"
@@ -196,51 +197,30 @@ FString UlxLuaCallLibrary::AllFunctionsWithPrefix(const TCHAR *Prefix)
return Result; return Result;
} }
/////////////////////////////////////////////////////////////////
//
// General Lua-Callable functions of the Lua Call Library.
//
/////////////////////////////////////////////////////////////////
void UlxLuaCallLibrary::ValidateLuaExpr(
ElxLuaSyntaxCheck &Status, FString &ErrorMessage, UObject *context, const FString &Code)
{
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
ErrorMessage = mode->ValidateLuaExpr(Code);
if (ErrorMessage.IsEmpty())
{
Status = ElxLuaSyntaxCheck::ValidLua;
}
else if (ErrorMessage == TEXT("slash command"))
{
Status = ElxLuaSyntaxCheck::SlashCommand;
}
else if (ErrorMessage == TEXT("white space"))
{
Status = ElxLuaSyntaxCheck::Whitespace;
}
else if (ErrorMessage == TEXT("truncated lua"))
{
Status = ElxLuaSyntaxCheck::TruncatedLua;
}
else
{
Status = ElxLuaSyntaxCheck::InvalidLua;
}
}
void UlxLuaCallLibrary::InvokeLuaExpr(UObject *context, const FString &Code) void UlxLuaCallLibrary::InvokeLuaExpr(UObject *context, const FString &Code)
{ {
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
mode->InvokeLuaExpr(Code); FlxLockedWrapper w(mode->GetLockableWrapper());
w.InvokeLuaExpr(Code);
} }
// Resolve an AActor to a tangible place_id.
// Returns 0 if place is null (meaning "use current
// actor"), or the tangible ID if found. Returns -1
// if place is non-null but has no tangible.
//
static int64 ResolvePlaceId(AActor *place)
{
if (place == nullptr) return 0;
UlxTangible *tan = UlxTangible::GetActorTangibleOrLog(place);
return tan ? tan->TangibleId : -1;
}
void UlxLuaCallLibrary::LuaCallBegin(UObject *context, const FString &cname, const FString &fname) void UlxLuaCallLibrary::LuaCallBegin(UObject *context, const FString &cname, const FString &fname)
{ {
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); FlxStreamBuffer &sb = mode->GetLuaCallBuffer();
mode->LuaCallBegin(); sb.clear();
sb.write_string(cname); sb.write_string(cname);
sb.write_string(fname); sb.write_string(fname);
} }
@@ -248,19 +228,33 @@ void UlxLuaCallLibrary::LuaCallBegin(UObject *context, const FString &cname, con
void UlxLuaCallLibrary::LuaCallInvoke(UObject *context, AActor *place) void UlxLuaCallLibrary::LuaCallInvoke(UObject *context, AActor *place)
{ {
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); FlxStreamBuffer &sb = mode->GetLuaCallBuffer();
if (NotInitialized(sb)) return; if (NotInitialized(sb)) return;
mode->LuaCallEnd(AccessKind::INVOKE_LUA_CALL, place); int64 place_id = ResolvePlaceId(place);
if (place_id < 0) { sb.clear(); return; }
FlxLockedWrapper w(mode->GetLockableWrapper());
w.InvokeLuaFunction(sb.view(), place_id);
sb.clear();
} }
bool UlxLuaCallLibrary::LuaCallProbe(UObject *context, AActor *place, UlxLuaValues *&ReturnArray) bool UlxLuaCallLibrary::LuaCallProbe(UObject *context, AActor *place, UlxLuaValues *&ReturnArray)
{ {
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); FlxStreamBuffer &sb = mode->GetLuaCallBuffer();
if (NotInitialized(sb)) return false; if (NotInitialized(sb)) return false;
ReturnArray = mode->LuaCallEnd(AccessKind::PROBE_LUA_CALL, place); int64 place_id = ResolvePlaceId(place);
if ((ReturnArray == nullptr) || (ReturnArray->Length() < 1)) if (place_id < 0) {
sb.clear();
ReturnArray = NewObject<UlxLuaValues>(mode);
return false;
}
ReturnArray = NewObject<UlxLuaValues>(mode);
FlxLockedWrapper w(mode->GetLockableWrapper());
w.ProbeLuaFunction(sb.view(), place_id, [&](std::string_view retpk) {
ReturnArray->Initialize(retpk);
});
sb.clear();
if (ReturnArray->Length() < 1)
{ {
UE_LOG(LogLuprexIntegration, Error, TEXT("corruption in lua_probe")); UE_LOG(LogLuprexIntegration, Error, TEXT("corruption in lua_probe"));
ReturnArray = nullptr; ReturnArray = nullptr;
@@ -292,7 +286,7 @@ bool UlxLuaCallLibrary::LuaCallProbe(UObject *context, AActor *place, UlxLuaValu
void UlxLuaCallLibrary::LuaCallArgument_string(UObject *context, const FString &pstring) { void UlxLuaCallLibrary::LuaCallArgument_string(UObject *context, const FString &pstring) {
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); FlxStreamBuffer &sb = mode->GetLuaCallBuffer();
if (NotInitialized(sb)) return; if (NotInitialized(sb)) return;
sb.write_simple_dynamic_tag(LuaValueType::STRING); sb.write_simple_dynamic_tag(LuaValueType::STRING);
sb.write_string(pstring); sb.write_string(pstring);
@@ -307,7 +301,7 @@ void UlxLuaCallLibrary::LuaCallArgument_name(UObject *context, const FName &pnam
UE_LOG(LogBlueprint, Error, TEXT("Names passed to lua must be short, and must contain only lowercase and digits")); UE_LOG(LogBlueprint, Error, TEXT("Names passed to lua must be short, and must contain only lowercase and digits"));
} }
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); FlxStreamBuffer &sb = mode->GetLuaCallBuffer();
if (NotInitialized(sb)) return; if (NotInitialized(sb)) return;
sb.write_simple_dynamic_tag(LuaValueType::TOKEN); sb.write_simple_dynamic_tag(LuaValueType::TOKEN);
sb.write_string(namestr); sb.write_string(namestr);
@@ -315,7 +309,7 @@ void UlxLuaCallLibrary::LuaCallArgument_name(UObject *context, const FName &pnam
void UlxLuaCallLibrary::LuaCallArgument_float(UObject *context, double pfloat) { void UlxLuaCallLibrary::LuaCallArgument_float(UObject *context, double pfloat) {
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); FlxStreamBuffer &sb = mode->GetLuaCallBuffer();
if (NotInitialized(sb)) return; if (NotInitialized(sb)) return;
sb.write_simple_dynamic_tag(LuaValueType::NUMBER); sb.write_simple_dynamic_tag(LuaValueType::NUMBER);
sb.write_double(pfloat); sb.write_double(pfloat);
@@ -323,7 +317,7 @@ void UlxLuaCallLibrary::LuaCallArgument_float(UObject *context, double pfloat) {
void UlxLuaCallLibrary::LuaCallArgument_int(UObject *context, int value) { void UlxLuaCallLibrary::LuaCallArgument_int(UObject *context, int value) {
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); FlxStreamBuffer &sb = mode->GetLuaCallBuffer();
if (NotInitialized(sb)) return; if (NotInitialized(sb)) return;
sb.write_simple_dynamic_tag(LuaValueType::NUMBER); sb.write_simple_dynamic_tag(LuaValueType::NUMBER);
sb.write_double(value); sb.write_double(value);
@@ -331,7 +325,7 @@ void UlxLuaCallLibrary::LuaCallArgument_int(UObject *context, int value) {
void UlxLuaCallLibrary::LuaCallArgument_vector(UObject *context, const FVector &pvector) { void UlxLuaCallLibrary::LuaCallArgument_vector(UObject *context, const FVector &pvector) {
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); FlxStreamBuffer &sb = mode->GetLuaCallBuffer();
if (NotInitialized(sb)) return; if (NotInitialized(sb)) return;
sb.write_simple_dynamic_tag(LuaValueType::VECTOR); sb.write_simple_dynamic_tag(LuaValueType::VECTOR);
sb.write_fvector(pvector); sb.write_fvector(pvector);
@@ -339,7 +333,7 @@ void UlxLuaCallLibrary::LuaCallArgument_vector(UObject *context, const FVector &
void UlxLuaCallLibrary::LuaCallArgument_vector2d(UObject *context, const FVector2D &pvector) { void UlxLuaCallLibrary::LuaCallArgument_vector2d(UObject *context, const FVector2D &pvector) {
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); FlxStreamBuffer &sb = mode->GetLuaCallBuffer();
if (NotInitialized(sb)) return; if (NotInitialized(sb)) return;
sb.write_simple_dynamic_tag(LuaValueType::VECTOR); sb.write_simple_dynamic_tag(LuaValueType::VECTOR);
sb.write_double(pvector.X); sb.write_double(pvector.X);
@@ -349,7 +343,7 @@ void UlxLuaCallLibrary::LuaCallArgument_vector2d(UObject *context, const FVector
void UlxLuaCallLibrary::LuaCallArgument_boolean(UObject *context, bool pbool) { void UlxLuaCallLibrary::LuaCallArgument_boolean(UObject *context, bool pbool) {
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); FlxStreamBuffer &sb = mode->GetLuaCallBuffer();
if (NotInitialized(sb)) return; if (NotInitialized(sb)) return;
sb.write_simple_dynamic_tag(LuaValueType::BOOLEAN); sb.write_simple_dynamic_tag(LuaValueType::BOOLEAN);
sb.write_bool(pbool); sb.write_bool(pbool);

View File

@@ -23,29 +23,6 @@
class UlxLuaValues; class UlxLuaValues;
////////////////////////////////////////////////////////////
//
// ElxLuaSyntaxCheck
//
// Classifies console commands syntactically:
//
// SlashCommand: starts with a slash, not lua
// Whitespace: the input only contains whitespace
// ValidLua: the input is valid lua code
// TruncatedLua: the input is truncated
// InvalidLua: invalid lua
//
////////////////////////////////////////////////////////////
UENUM(BlueprintType)
enum class ElxLuaSyntaxCheck : uint8 {
SlashCommand,
Whitespace,
ValidLua,
TruncatedLua,
InvalidLua,
};
//////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////
// //
// FlxParsedProto // FlxParsedProto
@@ -158,16 +135,6 @@ public:
static FString AllKnownReturnValueTypes() { return AllFunctionsWithPrefix(TEXT("LuaCallReturnValue_")); } static FString AllKnownReturnValueTypes() { return AllFunctionsWithPrefix(TEXT("LuaCallReturnValue_")); }
public: public:
// Syntactically validate lua code. Parses the
// code and returns an error message. If the code
// is error-free, the error message is empty.
//
// TODO: This doesn't belong here. This library
// is for LuaCallNode's internal implementation.
//
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function")
static void ValidateLuaExpr(ElxLuaSyntaxCheck &Status, FString &ErrorMessage, UObject *context, const FString &Code);
//////////////////////////////////////////////////////// ////////////////////////////////////////////////////////
// //
// Functions that initiate and complete the lua call. // Functions that initiate and complete the lua call.

View File

@@ -187,58 +187,6 @@ void ALuprexGameModeBase::UpdatePossessedTangible() {
} }
} }
UlxLuaValues *ALuprexGameModeBase::LuaCallEnd(AccessKind kind, int64 place_id) {
std::string_view datapk = LuaCallBuffer.view();
FlxLockedWrapper w(LockableWrapper);
if (place_id == 0) place_id = w.GetActor();
uint32_t retpklen;
const char *retpk;
w->play_access(w.Get(), kind, place_id, datapk.size(), datapk.data(), &retpklen, &retpk);
if (kind == AccessKind::PROBE_LUA_CALL)
{
UlxLuaValues *Result = NewObject<UlxLuaValues>(this);
Result->Initialize(std::string_view(retpk, retpklen));
return Result;
}
else return nullptr;
}
UlxLuaValues *ALuprexGameModeBase::LuaCallEnd(AccessKind kind) {
return LuaCallEnd(kind, int64(0));
}
UlxLuaValues *ALuprexGameModeBase::LuaCallEnd(AccessKind kind, AActor *place) {
if (place == nullptr) {
return LuaCallEnd(kind, int64(0));
} else {
UlxTangible *tan = UlxTangible::GetActorTangibleOrLog(place);
if (tan == nullptr) {
return NewObject<UlxLuaValues>(this);
} else {
return LuaCallEnd(kind, tan->TangibleId);
}
}
}
FString ALuprexGameModeBase::ValidateLuaExpr(const FString &Code)
{
FTCHARToUTF8 UCode(*Code);
FlxLockedWrapper w(LockableWrapper);
uint32_t retpklen;
const char *retpk;
w->play_access(w.Get(), AccessKind::VALIDATE_LUA_EXPR, 0, UCode.Length(), UCode.Get(), &retpklen, &retpk);
FString Result(retpklen, (const UTF8CHAR*)retpk);
return Result;
}
void ALuprexGameModeBase::InvokeLuaExpr(const FString &Code)
{
FTCHARToUTF8 UCode(*Code);
FlxLockedWrapper w(LockableWrapper);
uint32_t retpklen;
const char *retpk;
w->play_access(w.Get(), AccessKind::INVOKE_LUA_EXPR, 0, UCode.Length(), UCode.Get(), &retpklen, &retpk);
}
void ALuprexGameModeBase::OnWorldPreActorTick(UWorld* InWorld, ELevelTick InLevelTick, float deltaseconds) void ALuprexGameModeBase::OnWorldPreActorTick(UWorld* InWorld, ELevelTick InLevelTick, float deltaseconds)
{ {

View File

@@ -82,38 +82,12 @@ public:
void LookAtChanged(); void LookAtChanged();
// Assemble a lua call. Note that this is the lowest-level interface. // The Lua Call Assembly Buffer. Used by
// These functions are wrapped by the functions in UlxLuaCallLibrary, // UlxLuaCallLibrary to build up a call across
// and those in turn are wrapped by the K2Node "LuaInvoke" and "LuaProbe". // multiple UFUNCTION invocations.
// //
// At this level, the process of calling Lua is: FlxStreamBuffer &GetLuaCallBuffer() { return LuaCallBuffer; }
//
// * Use LuaCallBegin
// * Get the lua call buffer:
// - add a class name
// - add a function name
// - add function parameters
// * Use LuaCallEnd.
// * Process any return values in the UlxLuaValues array.
//
FlxStreamBuffer &LuaCallBegin() { LuaCallBuffer.clear(); return LuaCallBuffer; }
FlxStreamBuffer &LuaCallGetBuffer() { return LuaCallBuffer; }
UlxLuaValues *LuaCallEnd(AccessKind kind);
UlxLuaValues *LuaCallEnd(AccessKind kind, int64 place_id);
UlxLuaValues *LuaCallEnd(AccessKind kind, AActor *place);
void LuaCallClear() { LuaCallBuffer.clear(); }
// Validate some lua code. Returns an error message.
// If the lua is well-formed, the error message is the
// empty string. The syntax of the code is checked using
// an otherwise empty lua interpreter, so this is purely
// a syntax check.
//
FString ValidateLuaExpr(const FString &Code);
// Invoke some lua code.
//
void InvokeLuaExpr(const FString &Code);
// Get the Asset Lookup table. // Get the Asset Lookup table.
const UlxAssetLookup *GetAssetLookup() const { return AssetLookup; } const UlxAssetLookup *GetAssetLookup() const { return AssetLookup; }
@@ -164,8 +138,13 @@ public:
// The Luprex EngineWrapper, with a Mutex to protect it. // The Luprex EngineWrapper, with a Mutex to protect it.
// To access it, construct a FlxLockedWrapper. // To access it, construct a FlxLockedWrapper.
//
FlxLockableWrapper LockableWrapper; FlxLockableWrapper LockableWrapper;
// Get the LockableWrapper.
//
FlxLockableWrapper& GetLockableWrapper() { return LockableWrapper; }
// The Lua Call Assembly Buffer. // The Lua Call Assembly Buffer.
FlxStreamBuffer LuaCallBuffer; FlxStreamBuffer LuaCallBuffer;

View File

@@ -2,6 +2,7 @@
#include "UtilityLibrary.h" #include "UtilityLibrary.h"
#include "LuprexGameModeBase.h"
#include "GameFramework/PlayerController.h" #include "GameFramework/PlayerController.h"
#include "EnhancedInputSubsystems.h" #include "EnhancedInputSubsystems.h"
#include "Kismet/KismetSystemLibrary.h" #include "Kismet/KismetSystemLibrary.h"
@@ -251,3 +252,11 @@ FVector UlxUtilityLibrary::GetActorForwardVelocity(const AActor *Actor, double S
} }
return Forward * Speed; return Forward * Speed;
} }
void UlxUtilityLibrary::ValidateLuaExpr(
ElxLuaSyntaxCheck &Status, FString &ErrorMessage, UObject *context, const FString &Code)
{
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
FlxLockedWrapper w(mode->GetLockableWrapper());
Status = w.ValidateLuaExpr(Code, ErrorMessage);
}

View File

@@ -165,4 +165,11 @@ public:
// //
UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex|Utility", meta = (DefaultToSelf = "Actor")) UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex|Utility", meta = (DefaultToSelf = "Actor"))
static FVector GetActorForwardVelocity(const AActor *Actor, double Speed = 1.0, bool bSnapToXY = false); static FVector GetActorForwardVelocity(const AActor *Actor, double Speed = 1.0, bool bSnapToXY = false);
// Syntactically validate lua code. Parses the
// code and returns an error message. If the code
// is error-free, the error message is empty.
//
UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Utility")
static void ValidateLuaExpr(ElxLuaSyntaxCheck &Status, FString &ErrorMessage, UObject *context, const FString &Code);
}; };

View File

@@ -24,7 +24,7 @@ end
function engio.move(action, xyz, facing) function engio.move(action, xyz, facing)
-- todo: sanity check the parameters. -- todo: sanity check the parameters.
-- dprint("engio.move ", action, " ", xyz[1], " ", xyz[2], " ", xyz[3]) dprint("engio.move ", action, " ", xyz[1], " ", xyz[2], " ", xyz[3])
tangible.animate{tan=actor, anim={action=action, interactive=true, xyz=xyz, facing=facing}} tangible.animate{tan=actor, anim={action=action, interactive=true, xyz=xyz, facing=facing}}
end end