SkyPortal-plugin/Source/SkyPortal/Public/SkyPortalSubsystem.h
2024-09-23 18:19:18 +02:00

230 lines
6.1 KiB
C++

#pragma once
#include "CoreMinimal.h"
#include "Subsystems/EngineSubsystem.h"
#include "hidapi.h"
#include "HAL/Runnable.h"
#include "SkyPortalSubsystem.generated.h"
#pragma region Definitions
UENUM(BlueprintType)
enum EPortalSide {
LEFT UMETA(DisplayName = "Left side"),
RIGHT UMETA(DisplayName = "Right side"),
BOTH UMETA(DisplayName = "Both left and right"),
TRAP UMETA(DisplayName = "Trap")
};
UENUM(BlueprintType)
enum EPortalCommand {
A UMETA(DisplayName = "Activate"),
C UMETA(DisplayName = "Color"),
J UMETA(DisplayName = "Advanced color"),
L UMETA(DisplayName = "Trap color"),
M UMETA(DisplayName = "Music"),
Q UMETA(DisplayName = "Query"),
R UMETA(DisplayName = "Ready"),
S UMETA(DisplayName = "Status")
};
UENUM(BlueprintType)
enum class EFigureStatus : uint8
{
NOT_PRESENT = 0b00 UMETA(DisplayName = "Not Present"),
PRESENT = 0b01 UMETA(DisplayName = "Present"),
ADDED = 0b11 UMETA(DisplayName = "Added"),
REMOVED = 0b10 UMETA(DisplayName = "Removed")
};
USTRUCT(BlueprintType)
struct FPortalStatusData
{
GENERATED_USTRUCT_BODY()
// Array of statuses
UPROPERTY(BlueprintReadOnly, Category = "SkyPortal|Figure")
TArray<EFigureStatus> StatusArray;
// timestamp.
//only one byte long. This means that after the value 0xFF, it overflows back to 0x00.
// Since these are so far apart, it can be assumed that 0x00 is newer than anything in the range 0xF0 - 0xFF.
UPROPERTY(BlueprintReadOnly, Category = "SkyPortal|Figure")
uint8 Counter;
// Should always be true
UPROPERTY(BlueprintReadOnly, Category = "SkyPortal|Figure")
bool bIsReady;
};
/* Macro constants Definitions */
#define rw_buf_size 0x21
#define TIMEOUT 30000
#define DEBUG true
typedef struct {
unsigned char buf[rw_buf_size]; int BytesTransferred;
} RWBlock;
DECLARE_LOG_CATEGORY_EXTERN(LogHIDApi, Log, All);
DECLARE_LOG_CATEGORY_EXTERN(LogSkyportalIO, Log, All);
//// Delegates
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSkylanderAddedDelegate, int32, SkylanderID, int32, Index);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSkylanderRemovedDelegate, int32, SkylanderID, int32, Index);
#pragma endregion
class FPortalStatusChecker : public FRunnable {
public:
// Constructor: pass the subsystem and desired check interval (in seconds)
FPortalStatusChecker(USkyPortalSubsystem* InSubsystem, float InCheckInterval);
// FRunnable interface
virtual bool Init() override;
virtual uint32 Run() override;
virtual void Stop() override;
virtual void Exit() override;
private:
// Pointer to the subsystem that contains PortalStatus()
USkyPortalSubsystem* SkyPortalSubsystem;
// Time interval (in seconds) for status checking
float CheckInterval;
// Thread control variables
FThreadSafeBool bShouldRun;
// Helper function to check portal status
void CheckPortalStatus();
};
/* Handle all the portal I/O
*
*
*
*/
UCLASS(MinimalAPI)
class USkyPortalSubsystem : public UEngineSubsystem
{
GENERATED_BODY()
//Portal ref used in the subsystem
hid_device* PortalDevice;
public:
// Override initialization and deinitialization methods
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
/********* Portal Actions *************/
/* The first function to run, before anything else.
* It will re-init and prepare the portal for receiving/sending inputs
*
* return false if portal is not found
*/
UFUNCTION(BlueprintCallable, CallInEditor, meta = (Category = "SkyPortal"))
SKYPORTAL_API bool ConnectPortal();
/*Send a **Status** command, to see if the portal is ready to receive new commands*/
UFUNCTION(BlueprintCallable, BlueprintPure, meta = (Category = "SkyPortal|NOT FUNCTIONING"))
SKYPORTAL_API bool bIsPortalReady();
UFUNCTION(BlueprintCallable, meta = (Category = "SkyPortal|NOT FUNCTIONING"))
SKYPORTAL_API void SendPortalCommand(EPortalCommand Command);
UFUNCTION(BlueprintCallable, meta = (Category = "SkyPortal|NOT FUNCTIONING"))
SKYPORTAL_API void SendPortalSound(USoundWave* Sound);
/* Change portal color, ideally should be called just at the start.For gameplay usage, use ChangePortalColorside()*/
UFUNCTION(BlueprintCallable, CallInEditor, meta = (AutoCreateRefTerm = "Color", Category = "SkyPortal|Cosmetic"))
SKYPORTAL_API void ChangePortalColor(const FLinearColor& Color = FLinearColor::Green);
/**
* Change the color of the portal, can separate side and even trap ligth
* @param NextColor New color
* @param PortalSide The actors to record
* @param BlendTime Blend between current color and NextColor, in milliseconds
*/
UFUNCTION(BlueprintCallable, CallInEditor, meta = (AutoCreateRefTerm = "NextColor", Category = "SkyPortal|Cosmetic"))
SKYPORTAL_API void ChangePortalColorside(const FLinearColor& NextColor = FLinearColor::Green, const EPortalSide PortalSide = EPortalSide::BOTH, const float BlendTime = 500.0f);
// Blueprint-assignable event property
UPROPERTY(BlueprintAssignable, Category = "SkyPortal|Skylander")
FOnSkylanderAddedDelegate OnSkylanderAdded;
// Blueprint-assignable event property
UPROPERTY(BlueprintAssignable, Category = "SkyPortal|Skylander")
FOnSkylanderRemovedDelegate OnSkylanderRemoved;
UPROPERTY(BlueprintReadOnly)
FPortalStatusData CurrentStatusData;
EPortalCommand GetPortalCommandFromChar(unsigned char Char);
UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
bool bPortalConnected = false;
FString HidError;
bool ReadBlock(unsigned int block, unsigned char data[0x10], int skylander);
bool WriteBlock(unsigned int, unsigned char[0x10], int);
bool CheckResponse(RWBlock*, char);
void CheckComplexResponse();
private:
FPortalStatusData ParsePortalStatus(const RWBlock& ResponseBlock);
static void Sleep(int sleepMs);
void ActivatePortal(int active);
void RestartPortal();
// Block/byte related data write/read functions
bool OpenPortalHandle();
void Write(RWBlock*);
// Pointer to the status checker thread
FPortalStatusChecker* StatusChecker;
FRunnableThread* StatusCheckerThread;
protected:
//Constants
const int VendorIds[4] = { 0x12ba, 0x54c, 0x1430, 0x1430 };
const int ProductIds[4] = { 0x150, 0x967, 0x1f17 };
/////Defaults values, should not be used
//const int VendorId = 5168;
//const int ProductId = 336;
};