#pragma once #include "Subsystems/EngineSubsystem.h" #include "hidapi.h" #include "HAL/Runnable.h" #include "SkyPortalFigure.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, EditFixedSize, Category = "SkyPortal|Figure", meta = (EditFixedOrder)) TArray 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; explicit FPortalStatusData(uint8 ArraySize = 16, EFigureStatus DefaultStatus = EFigureStatus::NOT_PRESENT) : Counter(0), // Utilisation correcte de l'initialisation directe bIsReady(true) // Utilisation correcte de l'initialisation directe { // Initialisation du tableau StatusArray avec 16 éléments par défaut StatusArray.Init(DefaultStatus, ArraySize); } // Overload the == operator bool operator==(const FPortalStatusData& Other) const { if (bIsReady == Other.bIsReady && Counter == Other.Counter) { if (StatusArray == Other.StatusArray) { return true; } } return false; } // Overload the != operator bool operator!=(const FPortalStatusData& Other) const { return !(*this == Other); } }; /* 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; //// 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", HideAlphaChannel)) 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; UPROPERTY(BlueprintReadOnly) FPortalStatusData OldStatusData; 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(); UFUNCTION() TArray QueryBlock(uint8 characterIndex, uint8 block = 0x00); 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*); FigureDataBlock ReadFigureBlocks(uint8 FigureIndex); bool FalsePositive() const; // 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; };