Still working on event handling for hotkey widgets
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
[/Script/Engine.Engine]
|
||||||
|
+ActiveClassRedirects=(OldClassName="/Script/Integration.lxLookAtWidget",NewClassName="/Script/Integration.lxLuaWidget")
|
||||||
|
|
||||||
|
|
||||||
[/Script/EngineSettings.GameMapsSettings]
|
[/Script/EngineSettings.GameMapsSettings]
|
||||||
|
|||||||
BIN
Content/LpxLevel.umap
LFS
BIN
Content/LpxLevel.umap
LFS
Binary file not shown.
Binary file not shown.
BIN
Content/Luprex/lxGameMode.uasset
LFS
BIN
Content/Luprex/lxGameMode.uasset
LFS
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Content/Widgets/TX_Crosshair_Image.uasset
LFS
Normal file
BIN
Content/Widgets/TX_Crosshair_Image.uasset
LFS
Normal file
Binary file not shown.
BIN
Content/Widgets/TX_Hotkey_Label.uasset
LFS
Normal file
BIN
Content/Widgets/TX_Hotkey_Label.uasset
LFS
Normal file
Binary file not shown.
Binary file not shown.
BIN
Content/Widgets/WB_Hotkey_Action.uasset
LFS
Normal file
BIN
Content/Widgets/WB_Hotkey_Action.uasset
LFS
Normal file
Binary file not shown.
BIN
Content/Widgets/WB_Hotkey_Image.uasset
LFS
Normal file
BIN
Content/Widgets/WB_Hotkey_Image.uasset
LFS
Normal file
Binary file not shown.
Binary file not shown.
BIN
Content/Widgets/WB_Root.uasset
LFS
Normal file
BIN
Content/Widgets/WB_Root.uasset
LFS
Normal file
Binary file not shown.
Binary file not shown.
@@ -22,6 +22,10 @@
|
|||||||
"TargetAllowList": [
|
"TargetAllowList": [
|
||||||
"Editor"
|
"Editor"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Name": "CommonUI",
|
||||||
|
"Enabled": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ void UlxAssetLookup::LogMaybeError(bool Error, const TCHAR *Message, const TCHAR
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UStaticMesh *UlxAssetLookup::GetStaticMeshByName(const UObject *Context, const FString &Name, bool ErrorIfNotFound)
|
UStaticMesh *UlxAssetLookup::LoadStaticMeshAsset(const UObject *Context, const FString &Name, bool ErrorIfNotFound)
|
||||||
{
|
{
|
||||||
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(Context);
|
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(Context);
|
||||||
FString Path = mode->GetAssetLookup()->StaticMeshLoadPath(FName(FString("SM_") + Name));
|
FString Path = mode->GetAssetLookup()->StaticMeshLoadPath(FName(FString("SM_") + Name));
|
||||||
@@ -124,7 +124,7 @@ UStaticMesh *UlxAssetLookup::GetStaticMeshByName(const UObject *Context, const F
|
|||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
TSubclassOf<AActor> UlxAssetLookup::GetTangibleClassByName(const UObject *Context, const FString &Name, bool ErrorIfNotFound) {
|
TSubclassOf<AActor> UlxAssetLookup::LoadTangibleBlueprintAsset(const UObject *Context, const FString &Name, bool ErrorIfNotFound) {
|
||||||
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(Context);
|
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(Context);
|
||||||
FString Path = mode->GetAssetLookup()->TangibleLoadPath(FName(FString("TAN_") + Name));
|
FString Path = mode->GetAssetLookup()->TangibleLoadPath(FName(FString("TAN_") + Name));
|
||||||
if (Path.IsEmpty())
|
if (Path.IsEmpty())
|
||||||
@@ -152,7 +152,7 @@ TSubclassOf<AActor> UlxAssetLookup::GetTangibleClassByName(const UObject *Contex
|
|||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
TSubclassOf<UUserWidget> UlxAssetLookup::GetWidgetByName(const UObject *Context, const FString &Name, bool ErrorIfNotFound) {
|
TSubclassOf<UUserWidget> UlxAssetLookup::LoadUserWidgetAsset(const UObject *Context, const FString &Name, bool ErrorIfNotFound) {
|
||||||
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(Context);
|
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(Context);
|
||||||
FString Path = mode->GetAssetLookup()->WidgetLoadPath(FName(FString("WB_") + Name));
|
FString Path = mode->GetAssetLookup()->WidgetLoadPath(FName(FString("WB_") + Name));
|
||||||
if (Path.IsEmpty())
|
if (Path.IsEmpty())
|
||||||
@@ -168,13 +168,13 @@ TSubclassOf<UUserWidget> UlxAssetLookup::GetWidgetByName(const UObject *Context,
|
|||||||
}
|
}
|
||||||
if (!Result->IsChildOf(UUserWidget::StaticClass()))
|
if (!Result->IsChildOf(UUserWidget::StaticClass()))
|
||||||
{
|
{
|
||||||
LogMaybeError(ErrorIfNotFound, TEXT("Blueprint is not a Widget Blueprint"), *Path);
|
LogMaybeError(ErrorIfNotFound, TEXT("Blueprint does not derive from UUserWidget"), *Path);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
TSubclassOf<UlxLookAtWidget> UlxAssetLookup::GetLookAtWidgetByName(const UObject *Context, const FString &Name, bool ErrorIfNotFound) {
|
TSubclassOf<UlxLuaWidget> UlxAssetLookup::LoadLuaWidgetAsset(const UObject *Context, const FString &Name, bool ErrorIfNotFound) {
|
||||||
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(Context);
|
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(Context);
|
||||||
FString Path = mode->GetAssetLookup()->WidgetLoadPath(FName(FString("WB_") + Name));
|
FString Path = mode->GetAssetLookup()->WidgetLoadPath(FName(FString("WB_") + Name));
|
||||||
if (Path.IsEmpty())
|
if (Path.IsEmpty())
|
||||||
@@ -188,9 +188,9 @@ TSubclassOf<UlxLookAtWidget> UlxAssetLookup::GetLookAtWidgetByName(const UObject
|
|||||||
LogMaybeError(ErrorIfNotFound, TEXT("Cannot load widget blueprint"), *Path);
|
LogMaybeError(ErrorIfNotFound, TEXT("Cannot load widget blueprint"), *Path);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
if (!Result->IsChildOf(UlxLookAtWidget::StaticClass()))
|
if (!Result->IsChildOf(UlxLuaWidget::StaticClass()))
|
||||||
{
|
{
|
||||||
LogMaybeError(ErrorIfNotFound, TEXT("Blueprint is not a Luprex Look-At Widget"), *Path);
|
LogMaybeError(ErrorIfNotFound, TEXT("Blueprint does not derive from UlxLuaWidget"), *Path);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
return Result;
|
return Result;
|
||||||
|
|||||||
@@ -49,18 +49,18 @@ public:
|
|||||||
FString WidgetLoadPath(const FName &AssetName) const;
|
FString WidgetLoadPath(const FName &AssetName) const;
|
||||||
|
|
||||||
// Get a static mesh by name
|
// Get a static mesh by name
|
||||||
UFUNCTION(BlueprintPure, meta = (WorldContext = "Context"), Category = "Luprex|Miscellaneous")
|
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context"), Category = "Luprex|Asset Loading")
|
||||||
static UStaticMesh *GetStaticMeshByName(const UObject *Context, const FString &Name, bool ErrorIfNotFound = false);
|
static UStaticMesh *LoadStaticMeshAsset(const UObject *Context, const FString &Name, bool ErrorIfNotFound = false);
|
||||||
|
|
||||||
// Get a tangible class by name
|
// Get a tangible class by name
|
||||||
UFUNCTION(BlueprintPure, meta = (WorldContext = "Context"), Category = "Luprex|Miscellaneous")
|
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context"), Category = "Luprex|Asset Loading")
|
||||||
static TSubclassOf<AActor> GetTangibleClassByName(const UObject *Context, const FString &Name, bool ErrorIfNotFound = false);
|
static TSubclassOf<AActor> LoadTangibleBlueprintAsset(const UObject *Context, const FString &Name, bool ErrorIfNotFound = false);
|
||||||
|
|
||||||
// Get a widget blueprint by name
|
// Get a widget blueprint by name
|
||||||
UFUNCTION(BlueprintPure, meta = (WorldContext = "Context"), Category = "Luprex|Miscellaneous")
|
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context"), Category = "Luprex|Asset Loading")
|
||||||
static TSubclassOf<UUserWidget> GetWidgetByName(const UObject *Context, const FString &Name, bool ErrorIfNotFound = false);
|
static TSubclassOf<UUserWidget> LoadUserWidgetAsset(const UObject *Context, const FString &Name, bool ErrorIfNotFound = false);
|
||||||
|
|
||||||
// Get a look-at widget blueprint by name
|
// Get a look-at widget blueprint by name
|
||||||
UFUNCTION(BlueprintPure, meta = (WorldContext = "Context"), Category = "Luprex|Miscellaneous")
|
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context"), Category = "Luprex|Asset Loading")
|
||||||
static TSubclassOf<UlxLookAtWidget> GetLookAtWidgetByName(const UObject *Context, const FString &Name, bool ErrorIfNotFound = false);
|
static TSubclassOf<UlxLuaWidget> LoadLuaWidgetAsset(const UObject *Context, const FString &Name, bool ErrorIfNotFound = false);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ public class Integration : ModuleRules
|
|||||||
"Sockets",
|
"Sockets",
|
||||||
"Networking",
|
"Networking",
|
||||||
"EnhancedInput",
|
"EnhancedInput",
|
||||||
|
"UMG",
|
||||||
|
"CommonUI"
|
||||||
});
|
});
|
||||||
|
|
||||||
PrivateDependencyModuleNames.AddRange(new string[] {
|
PrivateDependencyModuleNames.AddRange(new string[] {
|
||||||
|
|||||||
@@ -233,12 +233,12 @@ bool UlxLuaCallLibrary::LuaCallProbe(UObject *context, AActor *place, UlxLuaValu
|
|||||||
ReturnArray = nullptr;
|
ReturnArray = nullptr;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ElxSuccessOrError Status;
|
ElxSuccessOrWrongType Status;
|
||||||
FString ErrorMessage;
|
FString ErrorMessage;
|
||||||
ReturnArray->ReadString(Status, ErrorMessage);
|
ReturnArray->ReadString(Status, ErrorMessage, false);
|
||||||
if (Status != ElxSuccessOrError::Success)
|
if (Status != ElxSuccessOrWrongType::Success)
|
||||||
{
|
{
|
||||||
UE_LOG(LogLuprexIntegration, Error, TEXT("corruption in lua_probe"));
|
UE_LOG(LogLuprexIntegration, Error, TEXT("lua probe should always return an error message (possibly empty) as the first parameter"));
|
||||||
ReturnArray = nullptr;
|
ReturnArray = nullptr;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -408,16 +408,19 @@ FString UlxLuaValues::DebugString() const
|
|||||||
return Output.ToString();
|
return Output.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
ElxSuccessOrError UlxLuaValues::CheckType(ElxLuaValueType Type, ElxLuaValueType Desired)
|
ElxSuccessOrWrongType UlxLuaValues::CheckType(bool LogErrorOnWrongType, ElxLuaValueType Type, ElxLuaValueType Desired)
|
||||||
{
|
{
|
||||||
if (Type != Desired)
|
if (Type != Desired)
|
||||||
{
|
{
|
||||||
FString TypeName = StaticEnum<ElxLuaValueType>()->GetDisplayNameTextByValue(int64(Type)).ToString();
|
if (LogErrorOnWrongType)
|
||||||
FString DesiredName = StaticEnum<ElxLuaValueType>()->GetDisplayNameTextByValue(int64(Desired)).ToString();
|
{
|
||||||
UE_LOG(LogBlueprint, Error, TEXT("Expected a value of type %s, but found %s instead."), *DesiredName, *TypeName);
|
FString TypeName = StaticEnum<ElxLuaValueType>()->GetDisplayNameTextByValue(int64(Type)).ToString();
|
||||||
return ElxSuccessOrError::Error;
|
FString DesiredName = StaticEnum<ElxLuaValueType>()->GetDisplayNameTextByValue(int64(Desired)).ToString();
|
||||||
|
UE_LOG(LogBlueprint, Error, TEXT("Expected a value of type %s, but found %s instead."), *DesiredName, *TypeName);
|
||||||
|
}
|
||||||
|
return ElxSuccessOrWrongType::WrongType;
|
||||||
}
|
}
|
||||||
return ElxSuccessOrError::Success;
|
return ElxSuccessOrWrongType::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UlxLuaValues::DiscardBeforeCursor()
|
void UlxLuaValues::DiscardBeforeCursor()
|
||||||
@@ -437,40 +440,40 @@ ElxLuaValueType UlxLuaValues::NextType() const
|
|||||||
return Types[Cursor];
|
return Types[Cursor];
|
||||||
}
|
}
|
||||||
|
|
||||||
void UlxLuaValues::ReadString(ElxSuccessOrError &Status, FString &Result)
|
void UlxLuaValues::ReadString(ElxSuccessOrWrongType &Status, FString &Result, bool LogErrorOnMismatch)
|
||||||
{
|
{
|
||||||
Status = CheckType(NextType(), ElxLuaValueType::String);
|
Status = CheckType(LogErrorOnMismatch, NextType(), ElxLuaValueType::String);
|
||||||
if (Status == ElxSuccessOrError::Error)
|
if (Status == ElxSuccessOrWrongType::WrongType)
|
||||||
{
|
{
|
||||||
Result.Empty(); return;
|
Result.Empty(); return;
|
||||||
}
|
}
|
||||||
Result = FlxStreamBuffer(Data[Cursor++]).read_fstring();
|
Result = FlxStreamBuffer(Data[Cursor++]).read_fstring();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UlxLuaValues::ReadName(ElxSuccessOrError &Status, FName &Result)
|
void UlxLuaValues::ReadName(ElxSuccessOrWrongType &Status, FName &Result, bool LogErrorOnMismatch)
|
||||||
{
|
{
|
||||||
Status = CheckType(NextType(), ElxLuaValueType::Name);
|
Status = CheckType(LogErrorOnMismatch, NextType(), ElxLuaValueType::Name);
|
||||||
if (Status == ElxSuccessOrError::Error)
|
if (Status == ElxSuccessOrWrongType::WrongType)
|
||||||
{
|
{
|
||||||
Result = FName(); return;
|
Result = FName(); return;
|
||||||
}
|
}
|
||||||
Result = FlxStreamBuffer(Data[Cursor++]).read_fname();
|
Result = FlxStreamBuffer(Data[Cursor++]).read_fname();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UlxLuaValues::ReadFloat(ElxSuccessOrError &Status, double &Result)
|
void UlxLuaValues::ReadFloat(ElxSuccessOrWrongType &Status, double &Result, bool LogErrorOnMismatch)
|
||||||
{
|
{
|
||||||
Status = CheckType(NextType(), ElxLuaValueType::Float);
|
Status = CheckType(LogErrorOnMismatch, NextType(), ElxLuaValueType::Float);
|
||||||
if (Status == ElxSuccessOrError::Error)
|
if (Status == ElxSuccessOrWrongType::WrongType)
|
||||||
{
|
{
|
||||||
Result = 0.0; return;
|
Result = 0.0; return;
|
||||||
}
|
}
|
||||||
Result = FlxStreamBuffer(Data[Cursor++]).read_double();
|
Result = FlxStreamBuffer(Data[Cursor++]).read_double();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UlxLuaValues::ReadInt(ElxSuccessOrError &Status, int &Result)
|
void UlxLuaValues::ReadInt(ElxSuccessOrWrongType &Status, int &Result, bool LogErrorOnMismatch)
|
||||||
{
|
{
|
||||||
Status = CheckType(NextType(), ElxLuaValueType::Float);
|
Status = CheckType(LogErrorOnMismatch, NextType(), ElxLuaValueType::Float);
|
||||||
if (Status == ElxSuccessOrError::Error)
|
if (Status == ElxSuccessOrWrongType::WrongType)
|
||||||
{
|
{
|
||||||
Result = 0.0; return;
|
Result = 0.0; return;
|
||||||
}
|
}
|
||||||
@@ -478,24 +481,24 @@ void UlxLuaValues::ReadInt(ElxSuccessOrError &Status, int &Result)
|
|||||||
Result = int(dvalue);
|
Result = int(dvalue);
|
||||||
if (double(Result) != dvalue)
|
if (double(Result) != dvalue)
|
||||||
{
|
{
|
||||||
Result = 0; Status = ElxSuccessOrError::Error; return;
|
Result = 0; Status = ElxSuccessOrWrongType::WrongType; return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UlxLuaValues::ReadVector(ElxSuccessOrError &Status, FVector &Result)
|
void UlxLuaValues::ReadVector(ElxSuccessOrWrongType &Status, FVector &Result, bool LogErrorOnMismatch)
|
||||||
{
|
{
|
||||||
Status = CheckType(NextType(), ElxLuaValueType::Vector);
|
Status = CheckType(LogErrorOnMismatch, NextType(), ElxLuaValueType::Vector);
|
||||||
if (Status == ElxSuccessOrError::Error)
|
if (Status == ElxSuccessOrWrongType::WrongType)
|
||||||
{
|
{
|
||||||
Result = FVector(); return;
|
Result = FVector(); return;
|
||||||
}
|
}
|
||||||
Result = FlxStreamBuffer(Data[Cursor++]).read_fvector();
|
Result = FlxStreamBuffer(Data[Cursor++]).read_fvector();
|
||||||
}
|
}
|
||||||
|
|
||||||
void UlxLuaValues::ReadVector2D(ElxSuccessOrError &Status, FVector2D &Result)
|
void UlxLuaValues::ReadVector2D(ElxSuccessOrWrongType &Status, FVector2D &Result, bool LogErrorOnMismatch)
|
||||||
{
|
{
|
||||||
Status = CheckType(NextType(), ElxLuaValueType::Vector);
|
Status = CheckType(LogErrorOnMismatch, NextType(), ElxLuaValueType::Vector);
|
||||||
if (Status == ElxSuccessOrError::Error)
|
if (Status == ElxSuccessOrWrongType::WrongType)
|
||||||
{
|
{
|
||||||
Result = FVector2D(); return;
|
Result = FVector2D(); return;
|
||||||
}
|
}
|
||||||
@@ -503,10 +506,10 @@ void UlxLuaValues::ReadVector2D(ElxSuccessOrError &Status, FVector2D &Result)
|
|||||||
Result = FVector2D(VValue.X, VValue.Y);
|
Result = FVector2D(VValue.X, VValue.Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UlxLuaValues::ReadBoolean(ElxSuccessOrError &Status, bool &Result)
|
void UlxLuaValues::ReadBoolean(ElxSuccessOrWrongType &Status, bool &Result, bool LogErrorOnMismatch)
|
||||||
{
|
{
|
||||||
Status = CheckType(NextType(), ElxLuaValueType::Boolean);
|
Status = CheckType(LogErrorOnMismatch, NextType(), ElxLuaValueType::Boolean);
|
||||||
if (Status == ElxSuccessOrError::Error)
|
if (Status == ElxSuccessOrWrongType::WrongType)
|
||||||
{
|
{
|
||||||
Result = false; return;
|
Result = false; return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,12 @@ enum class ElxFoundOrNotFound : uint8 {
|
|||||||
NotFound,
|
NotFound,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
UENUM(BlueprintType)
|
||||||
|
enum class ElxSuccessOrWrongType : uint8 {
|
||||||
|
Success,
|
||||||
|
WrongType,
|
||||||
|
};
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// This is a little parser that parses Lua function 'prototypes'.
|
// This is a little parser that parses Lua function 'prototypes'.
|
||||||
@@ -219,9 +225,9 @@ private:
|
|||||||
//
|
//
|
||||||
void Empty();
|
void Empty();
|
||||||
|
|
||||||
// Compare two types. If they aren't equal, log an error and return false.
|
// Compare two types. If they aren't equal, possibly log an error, and return a status code.
|
||||||
//
|
//
|
||||||
static ElxSuccessOrError CheckType(ElxLuaValueType Type, ElxLuaValueType Desired);
|
static ElxSuccessOrWrongType CheckType(bool LogErrorOnWrongType, ElxLuaValueType Type, ElxLuaValueType Desired);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UlxLuaValues() { Cursor = 0; }
|
UlxLuaValues() { Cursor = 0; }
|
||||||
@@ -256,23 +262,23 @@ public:
|
|||||||
ElxLuaValueType SwitchNextType() { return NextType(); }
|
ElxLuaValueType SwitchNextType() { return NextType(); }
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
|
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
|
||||||
void ReadString(ElxSuccessOrError &Status, FString &Result);
|
void ReadString(ElxSuccessOrWrongType &Status, FString &Result, bool LogErrorOnWrongType = false);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
|
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
|
||||||
void ReadName(ElxSuccessOrError &Status, FName &Result);
|
void ReadName(ElxSuccessOrWrongType &Status, FName &Result, bool LogErrorOnWrongType = false);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
|
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
|
||||||
void ReadFloat(ElxSuccessOrError &Status, double &Result);
|
void ReadFloat(ElxSuccessOrWrongType &Status, double &Result, bool LogErrorOnWrongType = false);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
|
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
|
||||||
void ReadInt(ElxSuccessOrError &Status, int &Result);
|
void ReadInt(ElxSuccessOrWrongType &Status, int &Result, bool LogErrorOnWrongType = false);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
|
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
|
||||||
void ReadVector(ElxSuccessOrError &Status, FVector &Result);
|
void ReadVector(ElxSuccessOrWrongType &Status, FVector &Result, bool LogErrorOnWrongType = false);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
|
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
|
||||||
void ReadVector2D(ElxSuccessOrError &Status, FVector2D &Result);
|
void ReadVector2D(ElxSuccessOrWrongType &Status, FVector2D &Result, bool LogErrorOnWrongType = false);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
|
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Status"), Category = "Luprex|Lua Value Array")
|
||||||
void ReadBoolean(ElxSuccessOrError &Status, bool &Result);
|
void ReadBoolean(ElxSuccessOrWrongType &Status, bool &Result, bool LogErrorOnWrongType = false);
|
||||||
};
|
};
|
||||||
@@ -426,7 +426,7 @@ void UK2Node_LuaInvoke::ExpandNode(class FKismetCompilerContext& CompilerContext
|
|||||||
}
|
}
|
||||||
UK2Node_CallFunction *UnpackNode = MakeCallFunctionNode(UnpackingFunc);
|
UK2Node_CallFunction *UnpackNode = MakeCallFunctionNode(UnpackingFunc);
|
||||||
ReturnArrayPin->MakeLinkTo(UnpackNode->FindPinChecked(UEdGraphSchema_K2::PN_Self));
|
ReturnArrayPin->MakeLinkTo(UnpackNode->FindPinChecked(UEdGraphSchema_K2::PN_Self));
|
||||||
CompilerContext.CopyPinLinksToIntermediate(*FindPinChecked(ErrorPinName), *UnpackNode->FindPinChecked(TEXT("Error")));
|
CompilerContext.CopyPinLinksToIntermediate(*FindPinChecked(ErrorPinName), *UnpackNode->FindPinChecked(TEXT("WrongType")));
|
||||||
CompilerContext.MovePinLinksToIntermediate(*Pin, *UnpackNode->FindPinChecked(TEXT("Result")));
|
CompilerContext.MovePinLinksToIntermediate(*Pin, *UnpackNode->FindPinChecked(TEXT("Result")));
|
||||||
ThenPin->MakeLinkTo(UnpackNode->GetExecPin());
|
ThenPin->MakeLinkTo(UnpackNode->GetExecPin());
|
||||||
ThenPin = UnpackNode->FindPinChecked(TEXT("Success"));
|
ThenPin = UnpackNode->FindPinChecked(TEXT("Success"));
|
||||||
|
|||||||
@@ -349,48 +349,6 @@ ALuprexGameModeBase *ALuprexGameModeBase::FromContext(const UObject *context) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ALuprexGameModeBase::ClearLookAtWidget(const UObject *Context)
|
|
||||||
{
|
|
||||||
ALuprexGameModeBase *mode = FromContext(Context);
|
|
||||||
if (mode->LookAtWidget != nullptr)
|
|
||||||
{
|
|
||||||
mode->LookAtWidget->RemoveFromParent();
|
|
||||||
mode->LookAtWidget = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ALuprexGameModeBase::SetLookAtWidget(const UObject *Context, UlxLookAtWidget *Widget)
|
|
||||||
{
|
|
||||||
ALuprexGameModeBase *Mode = FromContext(Context);
|
|
||||||
if (Mode->LookAtWidget != Widget)
|
|
||||||
{
|
|
||||||
ClearLookAtWidget(Context);
|
|
||||||
}
|
|
||||||
Mode->LookAtWidget = Widget;
|
|
||||||
}
|
|
||||||
|
|
||||||
UlxLookAtWidget *ALuprexGameModeBase::CreateLookAtWidgetByName(UObject *Context, const FString &BlueprintName,
|
|
||||||
ElxFoundOrNotFound &Result, bool ErrorIfNotFound, bool AddToViewport, bool SetLookAtWidget)
|
|
||||||
{
|
|
||||||
ALuprexGameModeBase *Mode = FromContext(Context);
|
|
||||||
Result = ElxFoundOrNotFound::NotFound;
|
|
||||||
auto Blueprint = UlxAssetLookup::GetLookAtWidgetByName(Context, BlueprintName, ErrorIfNotFound);
|
|
||||||
if (Blueprint == nullptr) return nullptr;
|
|
||||||
APlayerController *pc = Context->GetWorld()->GetFirstPlayerController();
|
|
||||||
UlxLookAtWidget *Widget = Cast<UlxLookAtWidget>(UWidgetBlueprintLibrary::Create(Context, Blueprint, pc));
|
|
||||||
check(Widget != nullptr);
|
|
||||||
if (AddToViewport)
|
|
||||||
{
|
|
||||||
Widget->AddToViewport(100);
|
|
||||||
}
|
|
||||||
if (SetLookAtWidget)
|
|
||||||
{
|
|
||||||
Mode->SetLookAtWidget(Context, Widget);
|
|
||||||
}
|
|
||||||
Result = ElxFoundOrNotFound::Found;
|
|
||||||
return Widget;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void ALuprexGameModeBase::SetLookAt(const UObject *Context, const FHitResult &HitResult)
|
void ALuprexGameModeBase::SetLookAt(const UObject *Context, const FHitResult &HitResult)
|
||||||
{
|
{
|
||||||
@@ -402,7 +360,6 @@ void ALuprexGameModeBase::SetLookAt(const UObject *Context, const FHitResult &Hi
|
|||||||
Mode->CurrentLookAt = HitResult;
|
Mode->CurrentLookAt = HitResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
FVector2D ALuprexGameModeBase::GetLookAtPixel(const UObject *Context)
|
FVector2D ALuprexGameModeBase::GetLookAtPixel(const UObject *Context)
|
||||||
{
|
{
|
||||||
ALuprexGameModeBase *Mode = FromContext(Context);
|
ALuprexGameModeBase *Mode = FromContext(Context);
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
#include "TriggeredTask.h"
|
#include "TriggeredTask.h"
|
||||||
#include "BlueprintErrors.h"
|
#include "BlueprintErrors.h"
|
||||||
#include "Blueprint/UserWidget.h"
|
#include "Blueprint/UserWidget.h"
|
||||||
|
#include "Widgets/CommonActivatableWidgetContainer.h"
|
||||||
|
#include "CommonActivatableWidget.h"
|
||||||
#include "LuprexGameModeBase.generated.h"
|
#include "LuprexGameModeBase.generated.h"
|
||||||
|
|
||||||
// Messages that come from inside the Luprex Core.
|
// Messages that come from inside the Luprex Core.
|
||||||
@@ -25,12 +27,12 @@ class UlxLuaValues;
|
|||||||
|
|
||||||
|
|
||||||
UCLASS(BlueprintType)
|
UCLASS(BlueprintType)
|
||||||
class INTEGRATION_API UlxLookAtWidget : public UUserWidget
|
class INTEGRATION_API UlxLuaWidget : public UCommonActivatableWidget
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UFUNCTION(BlueprintImplementableEvent, BlueprintCallable, Category = "Luprex|Look-At Detection")
|
UFUNCTION(BlueprintImplementableEvent, BlueprintCallable, Category = "Luprex|Miscellaneous")
|
||||||
void ReadLuaConfiguration(UlxLuaValues *Config);
|
void ReadLuaConfiguration(UlxLuaValues *Config);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -79,36 +81,6 @@ public:
|
|||||||
UFUNCTION(BlueprintPure, meta = (WorldContext = "Context"),Category = "Luprex|Look-At Detection")
|
UFUNCTION(BlueprintPure, meta = (WorldContext = "Context"),Category = "Luprex|Look-At Detection")
|
||||||
static FVector2D GetLookAtPixel(const UObject *Context);
|
static FVector2D GetLookAtPixel(const UObject *Context);
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context"), Category = "Luprex|Look-At Detection")
|
|
||||||
static void SetLookAtWidget(const UObject *Context, UlxLookAtWidget *Widget);
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context"), Category = "Luprex|Look-At Detection")
|
|
||||||
static UlxLookAtWidget *GetLookAtWidget(const UObject *Context) { return FromContext(Context)->LookAtWidget; }
|
|
||||||
|
|
||||||
// Create a new Look-At Widget, given the blueprint's name.
|
|
||||||
//
|
|
||||||
// The prefix WB_ is added to the blueprint name, and the blueprint is
|
|
||||||
// searched for in the folder "Widgets".
|
|
||||||
//
|
|
||||||
// * If the specified blueprint is not found, execution continues
|
|
||||||
// on the "Not Found" pin.
|
|
||||||
//
|
|
||||||
// * If the flag "Error if Not Found" is true, and the widget blueprint
|
|
||||||
// is not found, logs an error.
|
|
||||||
//
|
|
||||||
// * If the flag "Add to Viewport" is true, the new widget is added
|
|
||||||
// to the viewport.
|
|
||||||
//
|
|
||||||
// * If the flag "Set Look at Widget" is true, the new widget is stored
|
|
||||||
// as the current look-at widget.
|
|
||||||
//
|
|
||||||
UFUNCTION(BlueprintCallable, meta = (ExpandEnumAsExecs = "Result", WorldContext = "Context"), Category = "Luprex|Look-At Detection")
|
|
||||||
static UlxLookAtWidget *CreateLookAtWidgetByName(UObject *Context, const FString &BlueprintName,
|
|
||||||
ElxFoundOrNotFound &Result, bool ErrorIfNotFound = true, bool AddToViewport = true, bool SetLookAtWidget = true);
|
|
||||||
|
|
||||||
UFUNCTION(BlueprintCallable, meta = (WorldContext = "Context"), Category = "Luprex|Look-At Detection")
|
|
||||||
static void ClearLookAtWidget(const UObject *Context);
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Look-At Related Events
|
// Look-At Related Events
|
||||||
//
|
//
|
||||||
@@ -183,9 +155,6 @@ public:
|
|||||||
|
|
||||||
bool MustCallLookAtChanged;
|
bool MustCallLookAtChanged;
|
||||||
|
|
||||||
UPROPERTY()
|
|
||||||
UlxLookAtWidget *LookAtWidget;
|
|
||||||
|
|
||||||
// The sensitivity level at which a log message triggers a debugger breakpoint.
|
// The sensitivity level at which a log message triggers a debugger breakpoint.
|
||||||
UPROPERTY(EditAnywhere, Category="Debugging Tools")
|
UPROPERTY(EditAnywhere, Category="Debugging Tools")
|
||||||
ElxLogVerbosity BreakToDebuggerLogVerbosity;
|
ElxLogVerbosity BreakToDebuggerLogVerbosity;
|
||||||
|
|||||||
56
Source/Integration/StoreFNameInputModifier.cpp
Normal file
56
Source/Integration/StoreFNameInputModifier.cpp
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#include "StoreFNameInputModifier.h"
|
||||||
|
#include "EnhancedPlayerInput.h"
|
||||||
|
#include "LuprexGameModeBase.h"
|
||||||
|
|
||||||
|
|
||||||
|
static const int ENCODE_LIMIT = 0x00FFFFFF;
|
||||||
|
|
||||||
|
FInputActionValue UPassFNameAsAxis3D::ModifyRaw_Implementation(const UEnhancedPlayerInput* PlayerInput, FInputActionValue CurrentValue, float DeltaTime)
|
||||||
|
{
|
||||||
|
// Internally, an FName is stored as three integers:
|
||||||
|
// ComparisonIndex, DisplayIndex, and Number. These numbers
|
||||||
|
// are only valid within a single unreal process, but that's OK.
|
||||||
|
// We copy these three numbers into an Axis3D.
|
||||||
|
//
|
||||||
|
// Yes, that's ugly. It would be nicer if FName was one
|
||||||
|
// of the types explicitly allowed in an FInputActionValue.
|
||||||
|
// Maybe some day!
|
||||||
|
//
|
||||||
|
// Integers larger than ENCODE_LIMIT cannot be losslessly
|
||||||
|
// converted to float. Such numbers should never occur in
|
||||||
|
// practice: the string table should not contain that many
|
||||||
|
// strings!
|
||||||
|
//
|
||||||
|
uint32 cidx = FNameToStore.GetComparisonIndex().ToUnstableInt();
|
||||||
|
uint32 didx = FNameToStore.GetDisplayIndex().ToUnstableInt();
|
||||||
|
uint32 nidx = FNameToStore.GetNumber();
|
||||||
|
|
||||||
|
// Make sure the three integers will fit into three floats without rounding or overflow.
|
||||||
|
if ((cidx > ENCODE_LIMIT) || (didx > ENCODE_LIMIT) || (nidx > ENCODE_LIMIT))
|
||||||
|
{
|
||||||
|
UE_LOG(LogLuprexIntegration, Error, TEXT("Name cannot be converted to FInputActionValue: %s"), *FNameToStore.ToString());
|
||||||
|
return FInputActionValue(FVector());
|
||||||
|
}
|
||||||
|
|
||||||
|
return FInputActionValue(FVector(cidx, didx, nidx));
|
||||||
|
}
|
||||||
|
|
||||||
|
FName UPassFNameAsAxis3D::DecodeFNameFromAxis3D(const FVector &Vec)
|
||||||
|
{
|
||||||
|
uint32 cidx = static_cast<uint32>(Vec.X);
|
||||||
|
uint32 didx = static_cast<uint32>(Vec.Y);
|
||||||
|
uint32 nidx = static_cast<uint32>(Vec.Z);
|
||||||
|
FName Result(FNameEntryId::FromUnstableInt(cidx), FNameEntryId::FromUnstableInt(didx), nidx);
|
||||||
|
FName Inverse(FNameEntryId::FromUnstableInt(didx), FNameEntryId::FromUnstableInt(cidx), nidx);
|
||||||
|
|
||||||
|
if ((double(cidx) != Vec.X) || (double(didx) != Vec.Y) || (double(nidx) != Vec.Z) ||
|
||||||
|
(cidx > ENCODE_LIMIT) || (didx > ENCODE_LIMIT) || (nidx > ENCODE_LIMIT) ||
|
||||||
|
(!Result.IsValid()) || (!Inverse.IsValid()))
|
||||||
|
{
|
||||||
|
UE_LOG(LogLuprexIntegration, Error, TEXT("FVector is not an encoded FName."));
|
||||||
|
return NAME_None;
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
54
Source/Integration/StoreFNameInputModifier.h
Normal file
54
Source/Integration/StoreFNameInputModifier.h
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "InputModifiers.h"
|
||||||
|
#include "StoreFNameInputModifier.generated.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Allows you to pass an FName into an event handler.
|
||||||
|
*
|
||||||
|
* This modifier allows you to type an FName directly into an input mapping in
|
||||||
|
* the InputMappingContext editor, and have that FName passed through to an
|
||||||
|
* enhanced input handler.
|
||||||
|
*
|
||||||
|
* This modifier has an FName UPROPERTY which is editable in the InputMappingContext
|
||||||
|
* editor. At runtime, it encodes the FName as an Axis3D. In the event handler,
|
||||||
|
* the Axis3D can be decoded back into an FName using DecodeFNameFromAxis3D.
|
||||||
|
*
|
||||||
|
* One use for this modifier is when you want to know exactly which
|
||||||
|
* keyboard key triggered an EnhancedInput event. You can put the key's FName
|
||||||
|
* into the InputMappingContext, and it will get passed through to the event
|
||||||
|
* handler.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
UCLASS()
|
||||||
|
class INTEGRATION_API UPassFNameAsAxis3D : public UInputModifier
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief The FName to be encoded into the FInputActionValue.
|
||||||
|
*
|
||||||
|
* This property can be configured in the Input Mapping Context editor
|
||||||
|
* for each specific mapping.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Modifier Settings")
|
||||||
|
FName FNameToStore;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual FInputActionValue ModifyRaw_Implementation(const UEnhancedPlayerInput* PlayerInput, FInputActionValue CurrentValue, float DeltaTime) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Decodes an FName that was passed by the PassFnameAsAxis3D modifier.
|
||||||
|
*
|
||||||
|
* This function is used in an enhanced input event, where the PassFNameAsAxis3D
|
||||||
|
* modifier was used to pass an FName into the event. The value shows up as
|
||||||
|
* an Axis3D, which much be decoded back into an FName.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
UFUNCTION(BlueprintPure, Category = "Input|Enhanced")
|
||||||
|
static FName DecodeFNameFromAxis3D(const FVector& Encoded);
|
||||||
|
|
||||||
|
};
|
||||||
@@ -33,10 +33,10 @@ void UlxTangible::SetActorBlueprint(const FString &XName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the blueprint.
|
// Get the blueprint.
|
||||||
UClass *blueprint = UlxAssetLookup::GetTangibleClassByName(this, Name);
|
UClass *blueprint = UlxAssetLookup::LoadTangibleBlueprintAsset(this, Name);
|
||||||
if (blueprint == nullptr)
|
if (blueprint == nullptr)
|
||||||
{
|
{
|
||||||
blueprint = UlxAssetLookup::GetTangibleClassByName(this, DEFAULT_BLUEPRINT);
|
blueprint = UlxAssetLookup::LoadTangibleBlueprintAsset(this, DEFAULT_BLUEPRINT);
|
||||||
check(blueprint != nullptr);
|
check(blueprint != nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
#include "Kismet/GameplayStatics.h"
|
#include "Kismet/GameplayStatics.h"
|
||||||
#include "Blueprint/UserWidget.h"
|
#include "Blueprint/UserWidget.h"
|
||||||
#include "Components/GridPanel.h"
|
#include "Components/GridPanel.h"
|
||||||
|
#include "InputMappingContext.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#define LOCTEXT_NAMESPACE "Luprex Utility"
|
#define LOCTEXT_NAMESPACE "Luprex Utility"
|
||||||
@@ -209,3 +211,21 @@ void UlxUtilityLibrary::GetPositionOfGridPanelMiddleCell(UGridPanel *GridPanel,
|
|||||||
LowerRightXY.Y = (Row[0] + Row[1]) / TotalY;
|
LowerRightXY.Y = (Row[0] + Row[1]) / TotalY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UlxUtilityLibrary::MapAllKeyboardKeysToOneInputAction(UInputMappingContext *IMC, UInputAction *Action)
|
||||||
|
{
|
||||||
|
TArray<FKey> AllKeys;
|
||||||
|
EKeys::GetAllKeys(AllKeys);
|
||||||
|
|
||||||
|
// Map every keyboard key to the provided LuaAction
|
||||||
|
for (const FKey& Key : AllKeys)
|
||||||
|
{
|
||||||
|
if ((Key.IsValid()) &&
|
||||||
|
(Key.IsBindableInBlueprints()) &&
|
||||||
|
(Key.GetMenuCategory() == EKeys::NAME_KeyboardCategory) &&
|
||||||
|
(!Key.IsModifierKey()))
|
||||||
|
{
|
||||||
|
IMC->MapKey(Action, Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -132,4 +132,13 @@ public:
|
|||||||
UFUNCTION(BlueprintPure, Category="Widget")
|
UFUNCTION(BlueprintPure, Category="Widget")
|
||||||
static void GetPositionOfGridPanelMiddleCell(UGridPanel *GridPanel, FVector2D &UpperLeftXY, FVector2D &LowerRightXY);
|
static void GetPositionOfGridPanelMiddleCell(UGridPanel *GridPanel, FVector2D &UpperLeftXY, FVector2D &LowerRightXY);
|
||||||
|
|
||||||
|
// Create a mapping context that maps all keyboard keys to a single input action.
|
||||||
|
//
|
||||||
|
// This mapping context is usually meant to be used in conjunction with a
|
||||||
|
// hand-crafted mapping context, to act as a catch-all that catches all
|
||||||
|
// keys that aren't mapped in the hand-crafted mapping.
|
||||||
|
//
|
||||||
|
UFUNCTION(BlueprintCallable, Category="Input", meta=(WorldContext="WorldContextObject"))
|
||||||
|
static void MapAllKeyboardKeysToOneInputAction(UInputMappingContext *IMC, UInputAction *Action);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,11 +21,13 @@ end
|
|||||||
function cube.lookhotkeys(keys)
|
function cube.lookhotkeys(keys)
|
||||||
keys:add("X", "Cube Hi", function () dprint("Cube Hi") end)
|
keys:add("X", "Cube Hi", function () dprint("Cube Hi") end)
|
||||||
keys:add("A", "Cube Bye", function () dprint("Cube Bye") end)
|
keys:add("A", "Cube Bye", function () dprint("Cube Bye") end)
|
||||||
|
keys:add("Y", "Cube Yo", function () dprint("Cube Yo") end)
|
||||||
end
|
end
|
||||||
|
|
||||||
function sphere.lookhotkeys(keys)
|
function sphere.lookhotkeys(keys)
|
||||||
keys:add("X", "Sphere Hi", function () dprint("Sphere Hi") end)
|
keys:add("X", "Sphere Hi", function () dprint("Sphere Hi") end)
|
||||||
keys:add("A", "Sphere Bye", function () dprint("Sphere Bye") end)
|
keys:add("A", "Sphere Bye", function () dprint("Sphere Bye") end)
|
||||||
|
keys:add("Y", "Sphere Yo", function () dprint("Sphere Yo") end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user