├── .gitattributes ├── .github └── workflows │ └── multi-platform.yml ├── .gitignore ├── CMakeLists.txt ├── CMakeSettings.json ├── README.md ├── about.md ├── include ├── buttplugCpp.h ├── buttplugclient.h ├── helperClasses.h ├── log.h ├── messageHandler.h └── messages.h ├── logo.png ├── mod.json └── src ├── buttplugclient.cpp ├── log.cpp ├── main.cpp ├── messageHandler.cpp └── messages.cpp /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/workflows/multi-platform.yml: -------------------------------------------------------------------------------- 1 | name: Build Geode Mod 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - "**" 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | config: 15 | - name: Windows 16 | os: windows-latest 17 | 18 | - name: macOS 19 | os: macos-latest 20 | 21 | - name: Android32 22 | os: ubuntu-latest 23 | target: Android32 24 | 25 | - name: Android64 26 | os: ubuntu-latest 27 | target: Android64 28 | 29 | name: ${{ matrix.config.name }} 30 | runs-on: ${{ matrix.config.os }} 31 | 32 | steps: 33 | - uses: actions/checkout@v4 34 | 35 | - name: Build the mod 36 | uses: geode-sdk/build-geode-mod@main 37 | with: 38 | bindings: geode-sdk/bindings 39 | bindings-ref: main 40 | combine: true 41 | sdk: nightly 42 | build-config: Release 43 | target: ${{ matrix.config.target }} 44 | 45 | package: 46 | name: Package builds 47 | runs-on: ubuntu-latest 48 | needs: ["build"] 49 | 50 | steps: 51 | - uses: geode-sdk/build-geode-mod/combine@main 52 | id: build 53 | 54 | - uses: actions/upload-artifact@v4 55 | with: 56 | name: Build Output 57 | path: ${{ steps.build.outputs.build-output }} 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # Macos be like 35 | **/.DS_Store 36 | 37 | # Cache files for Sublime Text 38 | *.tmlanguage.cache 39 | *.tmPreferences.cache 40 | *.stTheme.cache 41 | 42 | # Ignore build folders 43 | **/build 44 | # Ignore platform specific build folders 45 | build-*/ 46 | 47 | # Workspace files are user-specific 48 | *.sublime-workspace 49 | 50 | # ILY vscode 51 | **/.vscode 52 | .idea/ 53 | 54 | # clangd 55 | .cache/ 56 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | set(CMAKE_CXX_STANDARD 20) 3 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 4 | set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64") 5 | set(CMAKE_CXX_VISIBILITY_PRESET hidden) 6 | set(GEODE_DISABLE_PRECOMPILED_HEADERS ON) 7 | set(USE_ZLIB OFF) 8 | 9 | project(GeometryPlug VERSION 2.2.0) 10 | 11 | # Set up the mod binary 12 | add_library(${PROJECT_NAME} SHARED 13 | src/main.cpp 14 | # Add your cpp files here 15 | ) 16 | 17 | file(GLOB SRC_FILES 18 | "src/*.cpp" 19 | "include/*.h" 20 | ) 21 | 22 | target_sources(GeometryPlug PUBLIC 23 | ${SRC_FILES} 24 | ) 25 | 26 | if (NOT DEFINED ENV{GEODE_SDK}) 27 | message(FATAL_ERROR "Unable to find Geode SDK! Please define GEODE_SDK environment variable to point to Geode") 28 | else() 29 | message(STATUS "Found Geode: $ENV{GEODE_SDK}") 30 | endif() 31 | 32 | add_subdirectory($ENV{GEODE_SDK} ${CMAKE_CURRENT_BINARY_DIR}/geode) 33 | 34 | # Set up dependencies, resources, link Geode 35 | setup_geode_mod(${PROJECT_NAME}) 36 | 37 | find_package(Threads REQUIRED) 38 | CPMAddPackage("gh:machinezone/IXWebSocket#e03c0be") 39 | CPMAddPackage( 40 | NAME nlohmann_json 41 | GITHUB_REPOSITORY nlohmann/json 42 | VERSION 3.11.2) 43 | target_link_libraries(${PROJECT_NAME} ixwebsocket::ixwebsocket nlohmann_json::nlohmann_json ) 44 | 45 | if (CMAKE_VERSION VERSION_GREATER 3.12) 46 | set_property(TARGET GeometryPlug PROPERTY CXX_STANDARD 11) 47 | endif() 48 | -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "x64-Release", 5 | "generator": "Ninja", 6 | "configurationType": "RelWithDebInfo", 7 | "buildRoot": "${projectDir}\\out\\build\\${name}", 8 | "installRoot": "${projectDir}\\out\\install\\${name}", 9 | "cmakeCommandArgs": "", 10 | "buildCommandArgs": "", 11 | "ctestCommandArgs": "", 12 | "inheritEnvironments": [ "msvc_x86" ], 13 | "variables": [] 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GeometryPlug 2 | 3 | ## Now compatible with 2.2074 4 | 5 | ### Windows, MacOS, and Android compatible! 6 | 7 | Geometry Plug lets you connect your sex toys to Geometry Dash. 8 | 9 | This mod is NOT standalone and requires external software and hardware which can be found here: https://buttplug.io/ (or download the Intiface Central app off the Google Play Store) 10 | 11 | This mod requires the Geode mod launcher: https://geode-sdk.org/ 12 | 13 | If you have questions or comments about the mod dm me @zelfmonco on Discord 14 | 15 | ## How to install 16 | 17 | ### Windows/Mac: 18 | 19 | 1. Download and install the Geode mod launcher 20 | 21 | 2. Download Geometry Plug from the releases tab on the right side of your screen or by clicking [this link](https://github.com/Zelfmonco/Geometry-Plug/releases/download/update-6/zelfmonco.geometryplug.geode) 22 | 23 | 3. Open the Geode mods folder from the in-game button in the Geode menu and move `zelfmonco.geometryplug.geode` into the mods folder 24 | 25 | 4. Restart Geometry Dash and you should see the mod in the Geode menu 26 | 27 | 28 | ### Android: 29 | 30 | 1. Download and install the Geode mod launcher for Android from [https://geode.sdk.org](https://github.com/geode-sdk/android-launcher/releases/download/1.4.1/geode-launcher-v1.4.1.apk) (this link will download a file for you) 31 | 32 | 2. Open Geometry Dash after installing Geode through the Geode Launcher 33 | 34 | 3. Download Geometry Plug from the releases tab on the right side of your screen or by clicking [this link](https://github.com/Zelfmonco/Geometry-Plug/releases/download/update-5/zelfmonco.geometryplug.geode) 35 | 36 | 4. Move `zelfmonco.geometryplug.geode` into the Geode mods folder, This is usually stored under: Internal Storage > android > media > com.geode.launcher > game > geode 37 | 38 | 5. Open Geometry Dash and you will see the mod when you open up the Geode Menu! 39 | -------------------------------------------------------------------------------- /about.md: -------------------------------------------------------------------------------- 1 | # GeometryPlug 2 | 3 | ## Now compatible with 2.2074 4 | 5 | Windows, MacOS, and Android compatible! 6 | 7 | Geometry Plug lets you connect your sex toys to Geometry Dash. 8 | 9 | This mod is NOT standalone and requires external software and hardware which can be found here: https://buttplug.io/ (or download the Intiface Central app off the Google Play Store) 10 | 11 | This mod requires the Geode mod launcher: https://geode-sdk.org/ 12 | 13 | If you have questions or comments about the mod dm me @zelfmonco on Discord 14 | -------------------------------------------------------------------------------- /include/buttplugCpp.h: -------------------------------------------------------------------------------- 1 | // buttplugCpp.h : Include file for standard system include files, 2 | // or project specific include files. 3 | 4 | #pragma once 5 | #include "buttplugclient.h" 6 | 7 | // TODO: Reference additional headers your program requires here. 8 | -------------------------------------------------------------------------------- /include/buttplugclient.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "ixwebsocket/IXWebSocket.h" 6 | #include "messageHandler.h" 7 | #include "log.h" 8 | 9 | typedef msg::SensorReading SensorClass; 10 | 11 | // Helper class to store devices and access them outside of the library. 12 | class DeviceClass { 13 | public: 14 | std::string deviceName; 15 | std::string displayName; 16 | std::vector commandTypes; 17 | std::vector sensorTypes; 18 | unsigned int deviceID; 19 | }; 20 | 21 | // Main client class 22 | class Client { 23 | public: 24 | // Constructor which initialized websockets for Windows. Add an IFDEF depending on compilation OS for portability. 25 | Client(std::string url, unsigned int port) { 26 | lUrl = url; 27 | lPort = port; 28 | } 29 | Client(std::string url, unsigned int port, std::string logfile) { 30 | lUrl = url; 31 | lPort = port; 32 | logging = 1; 33 | if (!logfile.empty()) logInfo.init(logfile); 34 | else logInfo.init("log.txt"); 35 | } 36 | ~Client() { 37 | 38 | } 39 | 40 | void connect(void (*callFunc)(const mhl::Messages)); 41 | void disconnect(); 42 | 43 | // Atomic variables to store connection status. Can be accessed outside library too since atomic. 44 | std::atomic wsConnected{false}; 45 | std::atomic isConnecting{false}; 46 | std::atomic clientConnected{false}; 47 | // Condition variables for the atomics, we want C++11 support 48 | std::condition_variable condClient; 49 | std::condition_variable condWs; 50 | 51 | // Public functions that send requests to server. 52 | void startScan(); 53 | void stopScan(); 54 | void requestDeviceList(); 55 | void stopDevice(DeviceClass dev); 56 | void stopAllDevices(); 57 | void sendScalar(DeviceClass dev, double str); 58 | void sensorRead(DeviceClass dev, int senIndex); 59 | void sensorSubscribe(DeviceClass dev, int senIndex); 60 | void sensorUnsubscribe(DeviceClass dev, int senIndex); 61 | 62 | // Setters 63 | void setUrl(std::string url) { lUrl = url; } 64 | void setPort(unsigned int port) { lPort = port; } 65 | 66 | // Mutex blocked function which grabs the currently connected devices and sensor reads. 67 | std::vector getDevices(); 68 | SensorClass getSensors(); 69 | private: 70 | // URL variables for the websocket. 71 | std::string lUrl; 72 | unsigned int lPort; 73 | 74 | int logging = 0; 75 | Logger logInfo; 76 | 77 | ix::WebSocket webSocket; 78 | 79 | // Message handler class, which takes messages, parses them and makes them to classes. 80 | mhl::Messages messageHandler; 81 | 82 | // Queue variable for passing received messages from server. 83 | std::queue q; 84 | // Condition variabel to wait for received messages in the queue. 85 | std::condition_variable cond; 86 | // Mutex to ensure no race conditions. 87 | std::mutex msgMx; 88 | // Callback function for when a message is received and handled. 89 | std::function messageCallback; 90 | 91 | // Device and sensor class vector which is grabbed outside of the library. 92 | std::vector devices; 93 | SensorClass sensorData; 94 | 95 | void connectServer(); 96 | void callbackFunction(const ix::WebSocketMessagePtr& msg); 97 | void messageHandling(); 98 | void sendMessage(json msg, mhl::MessageTypes mType); 99 | void updateDevices(); 100 | int findDevice(DeviceClass dev); 101 | }; 102 | -------------------------------------------------------------------------------- /include/helperClasses.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | class DeviceCmdAttr { 5 | public: 6 | std::string FeatureDescriptor = ""; 7 | unsigned int StepCount = 0; 8 | std::string ActuatorType = ""; 9 | std::string SensorType = ""; 10 | std::vector SensorRange; // every two steps 11 | //std::vector Endpoints; 12 | }; 13 | 14 | class DeviceCmd { 15 | public: 16 | std::string CmdType = ""; 17 | std::string StopDeviceCmd = ""; 18 | std::vector DeviceCmdAttributes; 19 | }; 20 | 21 | class Device { 22 | public: 23 | std::string DeviceName; 24 | unsigned int DeviceIndex; 25 | unsigned int DeviceMessageTimingGap = 0; 26 | std::string DeviceDisplayName = ""; 27 | std::vector DeviceMessages; 28 | }; 29 | 30 | class Scalar { 31 | public: 32 | unsigned int Index; 33 | double ScalarVal; 34 | std::string ActuatorType; 35 | }; 36 | -------------------------------------------------------------------------------- /include/log.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | class RequestQueue { 6 | public: 7 | unsigned int id = 1; 8 | std::string requestType; 9 | }; 10 | 11 | class Logger { 12 | public: 13 | Logger() { 14 | start = std::chrono::system_clock::now(); 15 | } 16 | 17 | ~Logger() { 18 | logFile.close(); 19 | } 20 | 21 | void init(std::string filename); 22 | void logSentMessage(std::string rqType, unsigned int id); 23 | void logReceivedMessage(std::string repType, unsigned int id); 24 | 25 | private: 26 | std::fstream logFile; 27 | std::vector rQueue; 28 | 29 | // Using time point and system_clock 30 | std::chrono::time_point start, end; 31 | }; -------------------------------------------------------------------------------- /include/messageHandler.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "messages.h" 5 | 6 | namespace mhl { 7 | // Enum class for message types for convenience. 8 | enum class MessageTypes { 9 | Ok, 10 | Error, 11 | Ping, 12 | RequestServerInfo, 13 | ServerInfo, 14 | StartScanning, 15 | StopScanning, 16 | ScanningFinished, 17 | RequestDeviceList, 18 | DeviceList, 19 | DeviceAdded, 20 | DeviceRemoved, 21 | StopDeviceCmd, 22 | StopAllDevices, 23 | ScalarCmd, 24 | LinearCmd, 25 | RotateCmd, 26 | SensorReadCmd, 27 | SensorReading, 28 | SensorSubscribeCmd, 29 | SensorUnsubscribeCmd 30 | }; 31 | 32 | typedef std::map MessageMap_t; 33 | 34 | // Class for request messages. 35 | class Requests { 36 | public: 37 | msg::RequestServerInfo requestServerInfo; 38 | msg::StartScanning startScanning; 39 | msg::StopScanning stopScanning; 40 | msg::ScanningFinished scanningFinished; 41 | msg::RequestDeviceList requestDeviceList; 42 | msg::DeviceRemoved deviceRemoved; 43 | msg::StopDeviceCmd stopDeviceCmd; 44 | msg::StopAllDevices stopAllDevices; 45 | msg::ScalarCmd scalarCmd; 46 | msg::SensorReadCmd sensorReadCmd; 47 | msg::SensorSubscribeCmd sensorSubscribeCmd; 48 | msg::SensorUnsubscribeCmd sensorUnsubscribeCmd; 49 | }; 50 | 51 | // Class for messages received and for handling all types of messages. 52 | class Messages { 53 | public: 54 | MessageTypes messageType = MessageTypes::Ok; 55 | unsigned int Id; 56 | 57 | // A map for message strings corresponding to enum. This is in this class since it is more convenient. 58 | MessageMap_t messageMap = { 59 | {MessageTypes::Ok, "Ok"}, 60 | {MessageTypes::Error, "Error"}, 61 | {MessageTypes::Ping, "Ping"}, 62 | {MessageTypes::RequestServerInfo, "RequestServerInfo"}, 63 | {MessageTypes::ServerInfo, "ServerInfo"}, 64 | {MessageTypes::StartScanning, "StartScanning"}, 65 | {MessageTypes::StopScanning, "StopScanning"}, 66 | {MessageTypes::ScanningFinished, "ScanningFinished"}, 67 | {MessageTypes::RequestDeviceList, "RequestDeviceList"}, 68 | {MessageTypes::DeviceList, "DeviceList"}, 69 | {MessageTypes::DeviceAdded, "DeviceAdded"}, 70 | {MessageTypes::DeviceRemoved, "DeviceRemoved"}, 71 | {MessageTypes::StopDeviceCmd, "StopDeviceCmd"}, 72 | {MessageTypes::StopAllDevices, "StopAllDevices"}, 73 | {MessageTypes::ScalarCmd, "ScalarCmd"}, 74 | {MessageTypes::LinearCmd, "LinearCmd"}, 75 | {MessageTypes::RotateCmd, "RotateCmd"}, 76 | {MessageTypes::SensorReadCmd, "SensorReadCmd"}, 77 | {MessageTypes::SensorReading, "SensorReading"}, 78 | {MessageTypes::SensorSubscribeCmd, "SensorSubscribeCmd"}, 79 | {MessageTypes::SensorUnsubscribeCmd, "SensorUnsubscribeCmd"} 80 | }; 81 | 82 | msg::Ok ok; 83 | msg::Error error; 84 | msg::ServerInfo serverInfo; 85 | msg::DeviceList deviceList; 86 | msg::DeviceAdded deviceAdded; 87 | msg::DeviceRemoved deviceRemoved; 88 | msg::SensorReading sensorReading; 89 | 90 | // Both server message and requests are handled in this class. 91 | void handleServerMessage(json& msg); 92 | json handleClientRequest(Requests req); 93 | private: 94 | }; 95 | } -------------------------------------------------------------------------------- /include/messages.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "helperClasses.h" 3 | #include 4 | #include 5 | 6 | using json = nlohmann::json; 7 | 8 | // Classes for the various message types and functions to convert to/from json. 9 | namespace msg { 10 | class Ok { 11 | public: 12 | unsigned int Id = 1; 13 | //NLOHMANN_DEFINE_TYPE_INTRUSIVE(Ok, Id); 14 | }; 15 | 16 | class Error { 17 | public: 18 | unsigned int Id = 1; 19 | std::string ErrorMessage = ""; 20 | int ErrorCode = 0; 21 | 22 | //NLOHMANN_DEFINE_TYPE_INTRUSIVE(Error, Id, ErrorMessage, ErrorCode); 23 | }; 24 | 25 | class RequestServerInfo { 26 | public: 27 | unsigned int Id = 1; 28 | std::string ClientName = "Default Client"; 29 | unsigned int MessageVersion = 3; 30 | }; 31 | 32 | class ServerInfo { 33 | public: 34 | unsigned int Id; 35 | std::string ServerName; 36 | unsigned int MessageVersion; 37 | unsigned int MaxPingTime; 38 | 39 | //NLOHMANN_DEFINE_TYPE_INTRUSIVE(ServerInfo, Id, ServerName, MessageVersion, MaxPingTime); 40 | }; 41 | 42 | class StartScanning { 43 | public: 44 | unsigned int Id = 1; 45 | }; 46 | 47 | class StopScanning { 48 | public: 49 | unsigned int Id = 1; 50 | }; 51 | 52 | class ScanningFinished { 53 | public: 54 | unsigned int Id = 1; 55 | //NLOHMANN_DEFINE_TYPE_INTRUSIVE(ScanningFinished, Id); 56 | }; 57 | 58 | class RequestDeviceList { 59 | public: 60 | unsigned int Id = 1; 61 | }; 62 | 63 | class DeviceList { 64 | public: 65 | unsigned int Id = 1; 66 | std::vector Devices; 67 | }; 68 | 69 | class DeviceAdded { 70 | public: 71 | unsigned int Id = 1; 72 | Device device; 73 | }; 74 | 75 | class DeviceRemoved { 76 | public: 77 | unsigned int Id = 1; 78 | unsigned int DeviceIndex; 79 | }; 80 | 81 | class StopDeviceCmd { 82 | public: 83 | unsigned int Id = 1; 84 | unsigned int DeviceIndex; 85 | }; 86 | 87 | class StopAllDevices { 88 | public: 89 | unsigned int Id = 1; 90 | }; 91 | 92 | class ScalarCmd { 93 | public: 94 | unsigned int Id = 1; 95 | unsigned int DeviceIndex; 96 | std::vector Scalars; 97 | }; 98 | 99 | class SensorReadCmd { 100 | public: 101 | unsigned int Id = 1; 102 | unsigned int DeviceIndex; 103 | unsigned int SensorIndex; 104 | std::string SensorType; 105 | }; 106 | 107 | class SensorReading { 108 | public: 109 | unsigned int Id = 1; 110 | unsigned int DeviceIndex; 111 | unsigned int SensorIndex; 112 | std::string SensorType; 113 | std::vector Data; 114 | }; 115 | 116 | class SensorSubscribeCmd { 117 | public: 118 | unsigned int Id = 1; 119 | unsigned int DeviceIndex; 120 | unsigned int SensorIndex; 121 | std::string SensorType; 122 | }; 123 | 124 | class SensorUnsubscribeCmd { 125 | public: 126 | unsigned int Id = 1; 127 | unsigned int DeviceIndex; 128 | unsigned int SensorIndex; 129 | std::string SensorType; 130 | }; 131 | 132 | extern void to_json(json& j, const RequestServerInfo& k); 133 | extern void to_json(json& j, const StartScanning& k); 134 | extern void to_json(json& j, const StopScanning& k); 135 | extern void to_json(json& j, const RequestDeviceList& k); 136 | extern void to_json(json& j, const StopDeviceCmd& k); 137 | extern void to_json(json& j, const StopAllDevices& k); 138 | extern void to_json(json& j, const ScalarCmd& k); 139 | extern void to_json(json& j, const SensorReadCmd& k); 140 | extern void to_json(json& j, const SensorSubscribeCmd& k); 141 | extern void to_json(json& j, const SensorUnsubscribeCmd& k); 142 | extern void from_json(const json& j, Ok& k); 143 | extern void from_json(const json& j, Error& k); 144 | extern void from_json(const json& j, ServerInfo& k); 145 | extern void from_json(const json& j, DeviceList& k); 146 | extern void from_json(const json& j, DeviceAdded& k); 147 | extern void from_json(const json& j, DeviceRemoved& k); 148 | extern void from_json(const json& j, SensorReading& k); 149 | } 150 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Zelfmonco/Geometry-Plug/f5054a1b4463fb5bec2694f124b7b2c1fa6f85a3/logo.png -------------------------------------------------------------------------------- /mod.json: -------------------------------------------------------------------------------- 1 | { 2 | "geode": "4.1.2", 3 | "gd": { 4 | "win": "2.2074", 5 | "android": "2.2074", 6 | "mac": "2.2074" 7 | }, 8 | "version": "v2.2.0", 9 | "id": "zelfmonco.geometryplug", 10 | "name": "GeometryPlug", 11 | "developer": "Zelfmonco", 12 | "description": "Connect your toys to Geometry Dash!", 13 | 14 | "settings": { 15 | "server-url": { 16 | "name": "Server URL", 17 | "description": "Change the server URL. Must start with ws:// or wss://", 18 | "type": "string", 19 | "default": "ws://127.0.0.1", 20 | "filter": "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.-_~:/?#[]@!$&'()*+,;=", 21 | "control": { 22 | "text": true 23 | } 24 | }, 25 | 26 | "server-port": { 27 | "name": "Server Port", 28 | "description": "Change the server port", 29 | "type": "int", 30 | "default": 12345, 31 | "min": 0, 32 | "max": 65535, 33 | "control": { 34 | "text": true, 35 | "slider": false 36 | } 37 | }, 38 | 39 | "death-vibration": { 40 | "name": "Death vibration toggle", 41 | "description": "Toggle if device will vibrate on death", 42 | "type": "bool", 43 | "default": true 44 | }, 45 | 46 | "death-vibration-with-level-percentage": { 47 | "name": "Death vibration with level percentage", 48 | "description": "Changes the death vibration strength based on the level percentage. Replaces the death vibration strength setting", 49 | "type": "bool", 50 | "default": false 51 | }, 52 | 53 | "invert-level-percentage": { 54 | "name": "Invert level percentage", 55 | "description": "Inverts the level percentage for death vibration (100% = 0%, 0% = 100%). Only applies if the above setting is enabled", 56 | "type": "bool", 57 | "default": false 58 | }, 59 | 60 | "death-vibration-strength": { 61 | "name": "Death vibration strength", 62 | "description": "Change the intestity of death vibrations", 63 | "type": "int", 64 | "default": 25, 65 | "min": 1, 66 | "max": 100, 67 | "control": { 68 | "slider": true 69 | } 70 | }, 71 | 72 | "death-vibration-length": { 73 | "name": "Death vibration length", 74 | "description": "Change the length of the vibration on death", 75 | "type": "float", 76 | "min": 0.01, 77 | "max": 4, 78 | "default": 0.2, 79 | "control": { 80 | "slider": true 81 | } 82 | }, 83 | 84 | "shake-vibration": { 85 | "name": "Shake vibration toggle", 86 | "description": "Toggle if shake triggers vibrate device", 87 | "type": "bool", 88 | "default": false 89 | }, 90 | 91 | "percentage-vibration": { 92 | "name": "Percentage vibration toggle", 93 | "description": "Toggle if device will vibrate as the level plays", 94 | "type": "bool", 95 | "default": false 96 | }, 97 | 98 | "complete-vibration": { 99 | "name": "Complete vibration toggle", 100 | "description": "Toggle if the device will vibrate on completetion of the level", 101 | "type": "bool", 102 | "default": true 103 | }, 104 | 105 | "complete-vibration-strength": { 106 | "name": "Complete vibration strength", 107 | "description": "Change the intestity of completion vibrations", 108 | "type": "int", 109 | "min": 1, 110 | "max": 100, 111 | "default": 25, 112 | "control": { 113 | "slider": true 114 | } 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/buttplugclient.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/buttplugclient.h" 2 | 3 | #include 4 | using namespace geode::prelude; 5 | 6 | /* 7 | TODO: Let the user access the devices in more details, that is, how many scalar cmds, 8 | how many sensor cmds and their types. Implement some kind of logging to track whether 9 | commands are successful. Implement Linear and Rotation cmds. Port to Linux. 10 | Investigate whether push back won't ruin sending scalar and sensor cmds, since the 11 | device list does not provide scalar or sensor indices (don't think it will). 12 | */ 13 | 14 | 15 | // Connection function with a function parameter which acts as a callback. 16 | void Client::connect(void (*callFunc)(const mhl::Messages)) { 17 | std::string FullUrl = lUrl + ":" + std::to_string(lPort); 18 | webSocket.setUrl(FullUrl); 19 | 20 | // Ping interval option. 21 | webSocket.setPingInterval(10); 22 | 23 | // Per message deflate connection is enabled by default. You can tweak its parameters or disable it 24 | webSocket.disablePerMessageDeflate(); 25 | 26 | // Set the callback function of the websocket 27 | std::function callback = std::bind(&Client::callbackFunction, this, std::placeholders::_1); 28 | messageCallback = callFunc; 29 | 30 | webSocket.setOnMessageCallback(callback); 31 | 32 | // Start websocket and indicate that it is connecting (non blocking function, other functions must wait for it to connect). 33 | webSocket.start(); 34 | isConnecting = 1; 35 | 36 | // Connect to server, specifically send a RequestServerInfo 37 | connectServer(); 38 | } 39 | 40 | void Client::disconnect() { 41 | std::lock_guard lock{msgMx}; 42 | 43 | // Set atomic variable that websocket is connected to false. 44 | wsConnected = false; 45 | condWs.notify_all(); 46 | 47 | // Set atomic variable that client is connected to false. 48 | clientConnected = false; 49 | condClient.notify_all(); 50 | 51 | // Set atomic variable that client is connected to false. 52 | isConnecting = false; 53 | cond.notify_all(); 54 | 55 | // Close websocket. 56 | webSocket.stop(); 57 | } 58 | 59 | // Websocket callback function. 60 | void Client::callbackFunction(const ix::WebSocketMessagePtr& msg) { 61 | // If a message is received to the websocket, pass it to the message handler and notify to stop waiting. 62 | if (msg->type == ix::WebSocketMessageType::Message) 63 | { 64 | // Mutex lock this scope. 65 | std::lock_guard lock{msgMx}; 66 | // Push message. 67 | q.push(msg->str); 68 | // Notify conditional variable to stop waiting. 69 | cond.notify_one(); 70 | } 71 | 72 | // Handle websocket errors. 73 | if (msg->type == ix::WebSocketMessageType::Error) 74 | { 75 | log::error("Error: {}", msg->errorInfo.reason); 76 | log::error("#retries: {}", msg->errorInfo.retries); 77 | log::error("Wait time(ms): {}", msg->errorInfo.wait_time); 78 | log::error("HTTP Status: {}", msg->errorInfo.http_status); 79 | } 80 | 81 | // Set atomic variable that websocket is connected once it is open. 82 | if (msg->type == ix::WebSocketMessageType::Open) { 83 | wsConnected = true; 84 | 85 | // Start a message handler thread and detach it. 86 | std::thread messageHandler(&Client::messageHandling, this); 87 | messageHandler.detach(); 88 | 89 | condWs.notify_all(); 90 | } 91 | 92 | // Set atomic variable that websocket is not connected if socket closes. 93 | if (msg->type == ix::WebSocketMessageType::Close) { 94 | wsConnected = false; 95 | } 96 | } 97 | 98 | // Function to start scanning in the server. 99 | void Client::startScan() { 100 | // Mutex lock scope. 101 | std::lock_guard lock{msgMx}; 102 | 103 | // Get a request class from message handling header. 104 | mhl::Requests req; 105 | // Set the ID of message according to the message enum in message handling header. 106 | req.startScanning.Id = static_cast(mhl::MessageTypes::StartScanning); 107 | // Set message type for the handler to recognize what message it is. 108 | messageHandler.messageType = mhl::MessageTypes::StartScanning; 109 | 110 | // Convert the returned handled request message class to json. 111 | json j = json::array({ messageHandler.handleClientRequest(req) }); 112 | log::debug("{}", j.dump()); 113 | 114 | // Start a thread that sends the message. 115 | std::thread sendHandler(&Client::sendMessage, this, j, messageHandler.messageType); 116 | sendHandler.detach(); 117 | } 118 | 119 | // Function to stop scanning, same as before but different type. 120 | void Client::stopScan() { 121 | std::lock_guard lock{msgMx}; 122 | 123 | mhl::Requests req; 124 | req.stopScanning.Id = static_cast(mhl::MessageTypes::StopScanning); 125 | messageHandler.messageType = mhl::MessageTypes::StopScanning; 126 | 127 | json j = json::array({ messageHandler.handleClientRequest(req) }); 128 | log::debug("{}", j.dump()); 129 | 130 | std::thread sendHandler(&Client::sendMessage, this, j, messageHandler.messageType); 131 | sendHandler.detach(); 132 | } 133 | 134 | // Function to get device list, same as before but different type. 135 | void Client::requestDeviceList() { 136 | std::lock_guard lock{msgMx}; 137 | 138 | mhl::Requests req; 139 | req.stopScanning.Id = static_cast(mhl::MessageTypes::RequestDeviceList); 140 | messageHandler.messageType = mhl::MessageTypes::RequestDeviceList; 141 | 142 | json j = json::array({ messageHandler.handleClientRequest(req) }); 143 | log::debug("{}", j.dump()); 144 | 145 | std::thread sendHandler(&Client::sendMessage, this, j, messageHandler.messageType); 146 | sendHandler.detach(); 147 | } 148 | 149 | // Function to send RequestServerInfo, same as before but different type. 150 | void Client::connectServer() { 151 | std::lock_guard lock{msgMx}; 152 | 153 | mhl::Requests req; 154 | req.requestServerInfo.Id = static_cast(mhl::MessageTypes::RequestServerInfo); 155 | req.requestServerInfo.ClientName = "Geometry Plug"; 156 | req.requestServerInfo.MessageVersion = 3; 157 | messageHandler.messageType = mhl::MessageTypes::RequestServerInfo; 158 | 159 | json j = json::array({ messageHandler.handleClientRequest(req) }); 160 | log::debug("{}", j.dump()); 161 | 162 | std::thread sendHandler(&Client::sendMessage, this, j, messageHandler.messageType); 163 | sendHandler.detach(); 164 | } 165 | 166 | // Function that actually sends the message. 167 | void Client::sendMessage(json msg, mhl::MessageTypes mType) { 168 | // First check whether a connection process is started. 169 | if (!isConnecting && !wsConnected) { 170 | log::error("Client is not connected and not started, start before sending a message"); 171 | return; 172 | } 173 | // If started, wait for the socket to connect first. 174 | if (!wsConnected && isConnecting) { 175 | std::unique_lock lock{msgMx}; 176 | log::debug("Waiting for socket to connect"); 177 | auto wsConnStatus = [this]() { return wsConnected == 1; }; 178 | condWs.wait(lock, wsConnStatus); 179 | log::info("Connected to socket"); 180 | //webSocket.send(msg.dump()); 181 | } 182 | // Once socket is connected, either wait for client to connect, or send a message if the message type 183 | // is a request server info, since this is our client connection message. 184 | if (!clientConnected && isConnecting) { 185 | log::info("Waiting for client to connect"); 186 | if (mType == mhl::MessageTypes::RequestServerInfo) { 187 | webSocket.send(msg.dump()); 188 | if (logging) logInfo.logSentMessage("RequestServerInfo", static_cast(mType)); 189 | log::info("Started connection to client"); 190 | return; 191 | } 192 | std::unique_lock lock{msgMx}; 193 | auto clientConnStatus = [this]() {return clientConnected == 1; }; 194 | condClient.wait(lock, clientConnStatus); 195 | log::info("Connected to client"); 196 | webSocket.send(msg.dump()); 197 | } 198 | // If everything is connected, simply send message and log request if enabled. 199 | else if (wsConnected && clientConnected) webSocket.send(msg.dump()); 200 | if (logging) { 201 | auto result = std::find_if( 202 | messageHandler.messageMap.begin(), 203 | messageHandler.messageMap.end(), 204 | [mType](std::pair > mo) {return mo.first == mType; }); 205 | std::string msgTypeText = result->second; 206 | logInfo.logSentMessage(msgTypeText, static_cast(mType)); 207 | } 208 | } 209 | 210 | // This takes the device data from message handler and puts it in to main device class 211 | // for user access. 212 | void Client::updateDevices() { 213 | std::vector tempDeviceVec; 214 | // Iterate through available devices. 215 | for (auto& el : messageHandler.deviceList.Devices) { 216 | DeviceClass tempDevice; 217 | // Set the appropriate class variables. 218 | tempDevice.deviceID = el.DeviceIndex; 219 | tempDevice.deviceName = el.DeviceName; 220 | tempDevice.displayName = el.DeviceDisplayName; 221 | if (el.DeviceMessages.size() > 0) { 222 | for (auto& el2 : el.DeviceMessages) tempDevice.commandTypes.push_back(el2.CmdType); 223 | } 224 | // Push back the device in vector. 225 | tempDeviceVec.push_back(tempDevice); 226 | } 227 | devices = tempDeviceVec; 228 | } 229 | 230 | // Mutex locked function to provide the user with available devices. 231 | std::vector Client::getDevices() { 232 | std::lock_guard lock{msgMx}; 233 | return devices; 234 | } 235 | 236 | SensorClass Client::getSensors() { 237 | std::lock_guard lock{msgMx}; 238 | return sensorData; 239 | } 240 | 241 | int Client::findDevice(DeviceClass dev) { 242 | for (int i = 0; i < messageHandler.deviceList.Devices.size(); i++) 243 | if (messageHandler.deviceList.Devices[i].DeviceIndex == dev.deviceID) 244 | return i; 245 | return -1; 246 | } 247 | 248 | void Client::stopDevice(DeviceClass dev) { 249 | std::lock_guard lock{msgMx}; 250 | 251 | mhl::Requests req; 252 | req.stopDeviceCmd.Id = static_cast(mhl::MessageTypes::StopDeviceCmd); 253 | req.stopDeviceCmd.DeviceIndex = dev.deviceID; 254 | 255 | messageHandler.messageType = mhl::MessageTypes::StopDeviceCmd; 256 | 257 | json j = json::array({ messageHandler.handleClientRequest(req) }); 258 | log::debug("{}", j.dump()); 259 | 260 | std::thread sendHandler(&Client::sendMessage, this, j, messageHandler.messageType); 261 | sendHandler.detach(); 262 | } 263 | 264 | void Client::stopAllDevices() { 265 | std::lock_guard lock{msgMx}; 266 | 267 | mhl::Requests req; 268 | req.stopDeviceCmd.Id = static_cast(mhl::MessageTypes::StopAllDevices); 269 | 270 | messageHandler.messageType = mhl::MessageTypes::StopAllDevices; 271 | 272 | json j = json::array({ messageHandler.handleClientRequest(req) }); 273 | log::debug("{}", j.dump()); 274 | 275 | std::thread sendHandler(&Client::sendMessage, this, j, messageHandler.messageType); 276 | sendHandler.detach(); 277 | } 278 | 279 | void Client::sendScalar(DeviceClass dev, double str) { 280 | std::lock_guard lock{msgMx}; 281 | int idx = findDevice(dev); 282 | if (idx > -1) { 283 | mhl::Requests req; 284 | 285 | for (auto& el1 : messageHandler.deviceList.Devices[idx].DeviceMessages) { 286 | std::string testScalar = "ScalarCmd"; 287 | if (!el1.CmdType.compare(testScalar)) { 288 | req.scalarCmd.DeviceIndex = messageHandler.deviceList.Devices[idx].DeviceIndex; 289 | req.scalarCmd.Id = static_cast(mhl::MessageTypes::ScalarCmd); 290 | int i = 0; 291 | for (auto& el2: el1.DeviceCmdAttributes) { 292 | Scalar sc; 293 | sc.ActuatorType = el2.ActuatorType; 294 | sc.ScalarVal = str; 295 | sc.Index = i; 296 | req.scalarCmd.Scalars.push_back(sc); 297 | i++; 298 | } 299 | messageHandler.messageType = mhl::MessageTypes::ScalarCmd; 300 | 301 | json j = json::array({ messageHandler.handleClientRequest(req) }); 302 | log::debug("{}", j.dump()); 303 | 304 | std::thread sendHandler(&Client::sendMessage, this, j, messageHandler.messageType); 305 | sendHandler.detach(); 306 | } 307 | } 308 | } 309 | } 310 | 311 | void Client::sensorRead(DeviceClass dev, int senIndex) { 312 | std::lock_guard lock{msgMx}; 313 | int idx = findDevice(dev); 314 | if (idx > -1) { 315 | mhl::Requests req; 316 | 317 | for (auto& el1 : messageHandler.deviceList.Devices[idx].DeviceMessages) { 318 | std::string testSensor = "SensorReadCmd"; 319 | if (!el1.CmdType.compare(testSensor)) { 320 | req.sensorReadCmd.DeviceIndex = messageHandler.deviceList.Devices[idx].DeviceIndex; 321 | req.sensorReadCmd.Id = static_cast(mhl::MessageTypes::SensorReadCmd); 322 | req.sensorReadCmd.SensorIndex = senIndex; 323 | req.sensorReadCmd.SensorType = el1.DeviceCmdAttributes[senIndex].SensorType; 324 | int i = 0; 325 | messageHandler.messageType = mhl::MessageTypes::SensorReadCmd; 326 | 327 | json j = json::array({ messageHandler.handleClientRequest(req) }); 328 | log::debug("{}", j.dump()); 329 | 330 | std::thread sendHandler(&Client::sendMessage, this, j, messageHandler.messageType); 331 | sendHandler.detach(); 332 | } 333 | } 334 | } 335 | } 336 | 337 | void Client::sensorSubscribe(DeviceClass dev, int senIndex) { 338 | std::lock_guard lock{msgMx}; 339 | int idx = findDevice(dev); 340 | if (idx > -1) { 341 | mhl::Requests req; 342 | 343 | for (auto& el1 : messageHandler.deviceList.Devices[idx].DeviceMessages) { 344 | std::string testSensor = "SensorReadCmd"; 345 | if (!el1.CmdType.compare(testSensor)) { 346 | req.sensorSubscribeCmd.DeviceIndex = messageHandler.deviceList.Devices[idx].DeviceIndex; 347 | req.sensorSubscribeCmd.Id = static_cast(mhl::MessageTypes::SensorSubscribeCmd); 348 | req.sensorSubscribeCmd.SensorIndex = senIndex; 349 | req.sensorSubscribeCmd.SensorType = el1.DeviceCmdAttributes[senIndex].SensorType; 350 | int i = 0; 351 | messageHandler.messageType = mhl::MessageTypes::SensorSubscribeCmd; 352 | 353 | json j = json::array({ messageHandler.handleClientRequest(req) }); 354 | log::debug("{}", j.dump()); 355 | 356 | std::thread sendHandler(&Client::sendMessage, this, j, messageHandler.messageType); 357 | sendHandler.detach(); 358 | } 359 | } 360 | } 361 | } 362 | 363 | void Client::sensorUnsubscribe(DeviceClass dev, int senIndex) { 364 | std::lock_guard lock{msgMx}; 365 | int idx = findDevice(dev); 366 | if (idx > -1) { 367 | mhl::Requests req; 368 | 369 | for (auto& el1 : messageHandler.deviceList.Devices[idx].DeviceMessages) { 370 | std::string testSensor = "SensorReadCmd"; 371 | if (!el1.CmdType.compare(testSensor)) { 372 | req.sensorUnsubscribeCmd.DeviceIndex = messageHandler.deviceList.Devices[idx].DeviceIndex; 373 | req.sensorUnsubscribeCmd.Id = static_cast(mhl::MessageTypes::SensorUnsubscribeCmd); 374 | req.sensorUnsubscribeCmd.SensorIndex = senIndex; 375 | req.sensorUnsubscribeCmd.SensorType = el1.DeviceCmdAttributes[senIndex].SensorType; 376 | int i = 0; 377 | messageHandler.messageType = mhl::MessageTypes::SensorUnsubscribeCmd; 378 | 379 | json j = json::array({ messageHandler.handleClientRequest(req) }); 380 | log::debug("{}", j.dump()); 381 | 382 | std::thread sendHandler(&Client::sendMessage, this, j, messageHandler.messageType); 383 | sendHandler.detach(); 384 | } 385 | } 386 | } 387 | } 388 | 389 | // Message handling function. 390 | // TODO: add client disconnect which stops this thread too. 391 | void Client::messageHandling() { 392 | // Loop through messages in queue. 393 | while (wsConnected) { 394 | std::unique_lock lock{msgMx}; 395 | 396 | // A lambda that waits to receive messages in the queue. 397 | cond.wait( 398 | lock, 399 | [this] { 400 | return !q.empty() || !wsConnected; 401 | } 402 | ); 403 | 404 | // If disconnected, break out of loop. 405 | if (!wsConnected) break; 406 | 407 | // If received, grab the message and pop it out. 408 | std::string value = q.front(); 409 | q.pop(); 410 | 411 | // Handle the message. 412 | json j = json::parse(value); 413 | // Iterate through messages since server can send array. 414 | for (auto& el : j.items()) { 415 | // Pass the message to actual handler. 416 | messageHandler.handleServerMessage(el.value()); 417 | 418 | // If server info received, it means client is connected so set the connection atomic variables 419 | // and notify all send threads that they are good to go. 420 | if (messageHandler.messageType == mhl::MessageTypes::ServerInfo) { 421 | isConnecting = 0; 422 | clientConnected = 1; 423 | condClient.notify_all(); 424 | } 425 | // If a device updated message, make sure to update the devices for the user. 426 | if (messageHandler.messageType == mhl::MessageTypes::DeviceAdded || 427 | messageHandler.messageType == mhl::MessageTypes::DeviceList || 428 | messageHandler.messageType == mhl::MessageTypes::DeviceRemoved) updateDevices(); 429 | 430 | if (messageHandler.messageType == mhl::MessageTypes::SensorReading) sensorData = messageHandler.sensorReading; 431 | 432 | // Log if logging is enabled. 433 | if (logging) 434 | logInfo.logReceivedMessage(el.value().begin().key(), static_cast(messageHandler.messageType)); 435 | 436 | // Callback function for the user. 437 | messageCallback(messageHandler); 438 | } 439 | 440 | lock.unlock(); 441 | log::debug("[subscriber] Received {}", value); 442 | } 443 | } 444 | -------------------------------------------------------------------------------- /src/log.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/log.h" 2 | 3 | void Logger::init(std::string filename) { 4 | logFile.open(filename, std::fstream::out); 5 | } 6 | 7 | void Logger::logSentMessage(std::string rqType, unsigned int id) { 8 | end = std::chrono::system_clock::now(); 9 | std::chrono::duration elapsed_seconds = end - start; 10 | RequestQueue tempQ; 11 | tempQ.id = id; 12 | tempQ.requestType = rqType; 13 | rQueue.push_back(tempQ); // CJ_LINK COMMENT 14 | 15 | logFile << elapsed_seconds.count() << " s, Request type sent: " << rqType << ", ID: " << id << std::endl; 16 | } 17 | 18 | void Logger::logReceivedMessage(std::string repType, unsigned int id) { 19 | //end = std::chrono::system_clock::now(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | //Geometry Plug main file 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "../include/buttplugCpp.h" 14 | 15 | using namespace geode::prelude; 16 | 17 | Client client( 18 | Mod::get()->getSettingValue("server-url"), 19 | std::max(Mod::get()->getSettingValue("server-port"), 0), 20 | "test.txt" 21 | ); 22 | std::vector myDevices; 23 | 24 | bool IsVibePercent = Mod::get()->getSettingValue("percentage-vibration"); 25 | bool IsDeathVibe = Mod::get()->getSettingValue("death-vibration"); 26 | bool IsVibeComplete = Mod::get()->getSettingValue("complete-vibration"); 27 | bool isVibeShake = Mod::get()->getSettingValue("shake-vibration"); 28 | bool isConnectedToServer = false; 29 | bool vibe = false; 30 | bool isLevelComplete = false; 31 | 32 | bool invertLevelPercentage = Mod::get()->getSettingValue("invert-level-percentage"); 33 | bool deathVibeWithLevelPercentage = Mod::get()->getSettingValue("death-vibration-with-level-percentage"); 34 | float deathVibeStrength = Mod::get()->getSettingValue("death-vibration-strength"); 35 | float deathVibeLength = Mod::get()->getSettingValue("death-vibration-length"); 36 | float completeVibeStrength = Mod::get()->getSettingValue("complete-vibration-strength"); 37 | 38 | int percentage; 39 | 40 | void callbackFunction(const mhl::Messages msg) { 41 | if (msg.messageType == mhl::MessageTypes::DeviceList) { 42 | log::debug("Device List callback"); 43 | } 44 | if (msg.messageType == mhl::MessageTypes::DeviceAdded) { 45 | log::debug("Device Added callback"); 46 | } 47 | if (msg.messageType == mhl::MessageTypes::ServerInfo) { 48 | log::debug("Server Info callback"); 49 | } 50 | if (msg.messageType == mhl::MessageTypes::DeviceRemoved) { 51 | log::debug("Device Removed callback"); 52 | } 53 | if (msg.messageType == mhl::MessageTypes::SensorReading) { 54 | log::debug("Sensor Reading callback"); 55 | } 56 | }; 57 | 58 | void clientReconnect() { 59 | client.stopAllDevices(); 60 | client.disconnect(); 61 | client.connect(callbackFunction); 62 | } 63 | 64 | $execute 65 | { 66 | listenForSettingChanges("server-url", +[](std::string p0) { 67 | client.setUrl(p0); 68 | clientReconnect(); 69 | }); 70 | 71 | listenForSettingChanges("server-port", +[](int64_t p0) { 72 | client.setPort(std::max(p0, 0)); 73 | clientReconnect(); 74 | }); 75 | 76 | listenForSettingChanges("invert-level-percentage", +[](bool p0) { invertLevelPercentage = p0; }); 77 | 78 | listenForSettingChanges("death-vibration-with-level-percentage", +[](bool p0) { deathVibeWithLevelPercentage = p0; }); 79 | 80 | listenForSettingChanges("death-vibration-strength", +[](int64_t p0) { deathVibeStrength = p0; }); 81 | 82 | listenForSettingChanges("shake-vibration", +[](bool p0) { isVibeShake = p0; }); 83 | 84 | listenForSettingChanges("death-vibration", +[](bool p0) { IsDeathVibe = p0; }); 85 | 86 | listenForSettingChanges("percentage-vibration", +[](bool p0) { IsVibePercent = p0; }); 87 | 88 | listenForSettingChanges("complete-vibration", +[](bool p0) { IsVibeComplete = p0; }); 89 | 90 | listenForSettingChanges("death-vibration-length", +[](double p0) { deathVibeLength = p0; }); 91 | 92 | listenForSettingChanges("complete-vibration-strength", +[](double p0) { completeVibeStrength = p0; }); 93 | } 94 | 95 | 96 | //connects to server 97 | int main() 98 | { 99 | client.connect(callbackFunction); 100 | client.requestDeviceList(); 101 | client.startScan(); 102 | client.stopScan(); 103 | 104 | return 0; 105 | } 106 | 107 | //vibrate device at a specified strength 108 | void VibratePercent(float per) 109 | { 110 | myDevices = client.getDevices(); 111 | if(myDevices.size() == 0) 112 | return; 113 | 114 | client.sendScalar(myDevices[0], per / 100.f); 115 | vibe = true; 116 | }; 117 | 118 | //stop device vibrating 119 | void StopVibrate() 120 | { 121 | if(myDevices.size() == 0) 122 | return; 123 | 124 | client.stopDevice(myDevices[0]); 125 | vibe = false; 126 | }; 127 | 128 | class $modify(MenuLayer) 129 | { 130 | //Start when the menu layer initializes 131 | bool init() 132 | { 133 | if((!MenuLayer::init())) 134 | return false; 135 | 136 | if(!isConnectedToServer) 137 | { 138 | main(); 139 | isConnectedToServer = true; 140 | } 141 | return true; 142 | } 143 | }; 144 | 145 | class $modify(MyPlayerObject, PlayerObject) 146 | { 147 | //MyPlayerObject Stop Vibrating 148 | void StopVibe() 149 | { 150 | if(myDevices.size() == 0) 151 | return; 152 | 153 | client.stopDevice(myDevices [0]); 154 | vibe = false; 155 | } 156 | 157 | void playDeathEffect() 158 | { 159 | PlayerObject::playDeathEffect(); 160 | 161 | if(IsVibePercent) 162 | { 163 | MyPlayerObject::StopVibe(); 164 | } 165 | if(IsDeathVibe) 166 | { 167 | auto pl = PlayLayer::get(); 168 | if (!pl) return; 169 | 170 | if (deathVibeWithLevelPercentage) { 171 | VibratePercent(invertLevelPercentage ? 100.f - pl->getCurrentPercent() : pl->getCurrentPercent()); 172 | } else { 173 | VibratePercent(deathVibeStrength); 174 | } 175 | 176 | vibe = true; 177 | auto action = pl->runAction(CCSequence::create(CCDelayTime::create(deathVibeLength), CCCallFunc::create(pl, callfunc_selector(MyPlayerObject::StopVibe)), nullptr)); 178 | } 179 | 180 | } 181 | }; 182 | 183 | class $modify(PlayLayer) 184 | { 185 | void updateProgressbar() 186 | { 187 | PlayLayer::updateProgressbar(); 188 | auto pl = PlayLayer::get(); 189 | percentage = pl->getCurrentPercentInt(); 190 | 191 | if(IsVibePercent && !isLevelComplete) 192 | { 193 | VibratePercent(percentage); 194 | } 195 | 196 | if((percentage == 100) && (!isLevelComplete)) 197 | { 198 | isLevelComplete = true; 199 | if(IsVibeComplete) 200 | { 201 | VibratePercent(completeVibeStrength); 202 | vibe = true; 203 | auto action = pl->runAction(CCSequence::create(CCDelayTime::create(2), CCCallFunc::create(pl, callfunc_selector(MyPlayerObject::StopVibe)), nullptr)); 204 | } 205 | } 206 | } 207 | 208 | bool init(GJGameLevel * p0, bool p1, bool p2) 209 | { 210 | if(!PlayLayer::init(p0, p1, p2)) 211 | return false; 212 | 213 | isLevelComplete = false; 214 | return true; 215 | } 216 | 217 | void resetLevelFromStart() 218 | { 219 | PlayLayer::resetLevelFromStart(); 220 | isLevelComplete = false; 221 | } 222 | 223 | void onExit() 224 | { 225 | PlayLayer::onExit(); 226 | 227 | if(vibe) 228 | StopVibrate(); 229 | } 230 | }; 231 | 232 | class $modify(EffectGameObject) { 233 | // GJBaseGameLayer::shakeCamera is inlined on Windows, so we have to detect 234 | // a shake trigger like this 235 | void triggerObject(GJBaseGameLayer* p0, int p1, const gd::vector* p2) { 236 | EffectGameObject::triggerObject(p0, p1, p2); 237 | auto pl = PlayLayer::get(); 238 | if (!pl) return; 239 | 240 | #ifdef GEODE_IS_MACOS 241 | float strength = *reinterpret_cast(this + 0x5c4); 242 | #else 243 | float strength = this->m_shakeStrength; 244 | #endif 245 | 246 | if (isVibeShake && strength != 0.f && this->m_duration > 0.f) { 247 | // normalize strength to 0-100 248 | if (strength > 5.f) strength = 5.f; 249 | strength = strength / 5.f * 100.f; 250 | 251 | VibratePercent(strength); 252 | vibe = true; 253 | 254 | pl->runAction(CCSequence::create( 255 | CCDelayTime::create(this->m_duration), 256 | CCCallFunc::create(pl, callfunc_selector(MyPlayerObject::StopVibe)), 257 | nullptr 258 | )); 259 | } 260 | } 261 | }; 262 | -------------------------------------------------------------------------------- /src/messageHandler.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/messageHandler.h" 2 | #include 3 | 4 | using namespace geode::prelude; 5 | 6 | namespace mhl { 7 | // Function that handles messages received from server. 8 | void Messages::handleServerMessage(json& msg) { 9 | // Grab the string of message type and find it in the map. 10 | auto msgType = msg.begin().key(); 11 | auto result = std::find_if( 12 | messageMap.begin(), 13 | messageMap.end(), 14 | [msgType](std::pair > mo) {return mo.second == msgType; }); 15 | auto msgEnumType = result->first; 16 | 17 | int i = 0; 18 | // Switch that converts message to class. 19 | switch (msgEnumType) { 20 | case mhl::MessageTypes::Ok: 21 | messageType = mhl::MessageTypes::Ok; 22 | ok = msg.get(); 23 | break; 24 | case mhl::MessageTypes::Error: 25 | messageType = mhl::MessageTypes::Error; 26 | error = msg.get(); 27 | break; 28 | case mhl::MessageTypes::ServerInfo: 29 | // Set message type and convert to class from json. 30 | log::debug("Server info!"); 31 | messageType = mhl::MessageTypes::ServerInfo; 32 | serverInfo = msg.get(); 33 | break; 34 | case mhl::MessageTypes::ScanningFinished: 35 | break; 36 | case mhl::MessageTypes::DeviceList: 37 | log::debug("Device list!"); 38 | messageType = mhl::MessageTypes::DeviceList; 39 | deviceList = msg.get(); 40 | break; 41 | case mhl::MessageTypes::DeviceAdded: 42 | deviceAdded = msg.get(); 43 | messageType = mhl::MessageTypes::DeviceAdded; 44 | // Push back to message handler class device list the newly added device. 45 | deviceList.Devices.push_back(deviceAdded.device); 46 | break; 47 | case mhl::MessageTypes::DeviceRemoved: 48 | deviceRemoved = msg.get(); 49 | messageType = mhl::MessageTypes::DeviceRemoved; 50 | // Erase device from message handler class device list. 51 | for (auto& el : deviceList.Devices) { 52 | if (deviceRemoved.DeviceIndex == el.DeviceIndex) { 53 | deviceList.Devices.erase(deviceList.Devices.begin() + i); 54 | break; 55 | } 56 | i++; 57 | } 58 | break; 59 | case mhl::MessageTypes::SensorReading: 60 | sensorReading = msg.get(); 61 | messageType = mhl::MessageTypes::SensorReading; 62 | break; 63 | default: 64 | break; 65 | } 66 | } 67 | 68 | // Convert client request classes to json. 69 | json Messages::handleClientRequest(Requests req) { 70 | json j; 71 | switch (messageType) { 72 | case mhl::MessageTypes::RequestServerInfo: 73 | j = req.requestServerInfo; 74 | break; 75 | case mhl::MessageTypes::RequestDeviceList: 76 | j = req.requestDeviceList; 77 | break; 78 | case mhl::MessageTypes::StartScanning: 79 | j = req.startScanning; 80 | break; 81 | case mhl::MessageTypes::StopScanning: 82 | j = req.stopScanning; 83 | break; 84 | case mhl::MessageTypes::StopDeviceCmd: 85 | j = req.stopDeviceCmd; 86 | break; 87 | case mhl::MessageTypes::StopAllDevices: 88 | j = req.stopAllDevices; 89 | break; 90 | case mhl::MessageTypes::ScalarCmd: 91 | j = req.scalarCmd; 92 | break; 93 | case mhl::MessageTypes::SensorReadCmd: 94 | j = req.sensorReadCmd; 95 | break; 96 | case mhl::MessageTypes::SensorSubscribeCmd: 97 | j = req.sensorSubscribeCmd; 98 | break; 99 | case mhl::MessageTypes::SensorUnsubscribeCmd: 100 | j = req.sensorUnsubscribeCmd; 101 | break; 102 | default: 103 | break; 104 | } 105 | return j; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/messages.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/messages.h" 2 | 3 | // Function definitions for json conversions. 4 | namespace msg { 5 | void to_json(json& j, const RequestServerInfo& k) { 6 | j["RequestServerInfo"] = { {"Id", k.Id}, {"ClientName", k.ClientName}, {"MessageVersion", k.MessageVersion} }; 7 | } 8 | 9 | void to_json(json& j, const StartScanning& k) { 10 | j["StartScanning"] = { {"Id", k.Id} }; 11 | } 12 | 13 | void to_json(json& j, const StopScanning& k) { 14 | j["StopScanning"] = { {"Id", k.Id} }; 15 | } 16 | 17 | void to_json(json& j, const RequestDeviceList& k) { 18 | j["RequestDeviceList"] = { {"Id", k.Id} }; 19 | } 20 | 21 | void to_json(json& j, const StopDeviceCmd& k) { 22 | j["StopDeviceCmd"] = { {"Id", k.Id}, {"DeviceIndex", k.DeviceIndex} }; 23 | } 24 | 25 | void to_json(json& j, const StopAllDevices& k) { 26 | j["StopAllDevices"] = { {"Id", k.Id} }; 27 | } 28 | 29 | void to_json(json& j, const ScalarCmd& k) { 30 | j["ScalarCmd"] = { {"Id", k.Id}, {"DeviceIndex", k.DeviceIndex} }; 31 | j["ScalarCmd"]["Scalars"] = json::array(); 32 | 33 | for (auto& vec : k.Scalars) { 34 | json jTemp = { { "Index", vec.Index }, { "Scalar", vec.ScalarVal }, { "ActuatorType", vec.ActuatorType } }; 35 | j["ScalarCmd"]["Scalars"].insert(j["ScalarCmd"]["Scalars"].end(), jTemp); 36 | } 37 | } 38 | 39 | void to_json(json& j, const SensorReadCmd& k) { 40 | j["SensorReadCmd"] = { {"Id", k.Id}, {"DeviceIndex", k.DeviceIndex}, {"SensorIndex", k.SensorIndex}, {"SensorType", k.SensorType}}; 41 | } 42 | 43 | void to_json(json& j, const SensorSubscribeCmd& k) { 44 | j["SensorSubscribeCmd"] = { {"Id", k.Id}, {"DeviceIndex", k.DeviceIndex}, {"SensorIndex", k.SensorIndex}, {"SensorType", k.SensorType} }; 45 | } 46 | 47 | void to_json(json& j, const SensorUnsubscribeCmd& k) { 48 | j["SensorUnsubscribeCmd"] = { {"Id", k.Id}, {"DeviceIndex", k.DeviceIndex}, {"SensorIndex", k.SensorIndex}, {"SensorType", k.SensorType} }; 49 | } 50 | 51 | void from_json(const json& j, ServerInfo& k) { 52 | json jTemp; 53 | j.at("ServerInfo").get_to(jTemp); 54 | jTemp.at("Id").get_to(k.Id); 55 | jTemp.at("ServerName").get_to(k.ServerName); 56 | jTemp.at("MessageVersion").get_to(k.MessageVersion); 57 | jTemp.at("MaxPingTime").get_to(k.MaxPingTime); 58 | } 59 | 60 | void from_json(const json& j, Ok& k) { 61 | json jTemp; 62 | j.at("Ok").get_to(jTemp); 63 | jTemp.at("Id").get_to(k.Id); 64 | } 65 | 66 | void from_json(const json& j, Error& k) { 67 | json jTemp; 68 | j.at("Error").get_to(jTemp); 69 | jTemp.at("Id").get_to(k.Id); 70 | jTemp.at("ErrorCode").get_to(k.ErrorCode); 71 | jTemp.at("ErrorMessage").get_to(k.ErrorMessage); 72 | } 73 | 74 | void from_json(const json& j, DeviceRemoved& k) { 75 | json jTemp; 76 | j.at("DeviceRemoved").get_to(jTemp); 77 | jTemp.at("Id").get_to(k.Id); 78 | jTemp.at("DeviceIndex").get_to(k.DeviceIndex); 79 | } 80 | 81 | // The device conversion functions are slightly more complicated since they have some 82 | // nested objects and arrays, but otherwise it is simply parsing, just a lot of it. 83 | void from_json(const json& j, DeviceList& k) { 84 | json jTemp; 85 | j.at("DeviceList").get_to(jTemp); 86 | jTemp.at("Id").get_to(k.Id); 87 | 88 | if (jTemp["Devices"].size() > 0) { 89 | for (auto& el : jTemp["Devices"].items()) { 90 | Device tempD; 91 | //std::cout << el.value() << std::endl; 92 | auto test = el.value().contains("DeviceMessageTimingGap"); 93 | if (el.value().contains("DeviceName")) tempD.DeviceName = el.value()["DeviceName"]; 94 | 95 | if (el.value().contains("DeviceIndex")) tempD.DeviceIndex = el.value()["DeviceIndex"]; 96 | 97 | if (el.value().contains("DeviceMessageTimingGap")) tempD.DeviceMessageTimingGap = el.value()["DeviceMessageTimingGap"]; 98 | 99 | if (el.value().contains("DeviceDisplayName")) tempD.DeviceName = el.value()["DeviceDisplayName"]; 100 | 101 | if (el.value().contains("DeviceMessages")) { 102 | json jTemp2; 103 | jTemp2 = el.value()["DeviceMessages"]; 104 | 105 | for (auto& el2 : jTemp2.items()) { 106 | DeviceCmd tempCmd; 107 | tempCmd.CmdType = el2.key(); 108 | 109 | if (!el2.key().compare("StopDeviceCmd")) { 110 | // Do something, not sure what yet 111 | continue; 112 | } 113 | 114 | for (auto& el3 : el2.value().items()) { 115 | DeviceCmdAttr tempAttr; 116 | //std::cout << el3.value() << std::endl; 117 | 118 | if (el3.value().contains("FeatureDescriptor")) tempAttr.FeatureDescriptor = el3.value()["FeatureDescriptor"]; 119 | 120 | if (el3.value().contains("StepCount")) tempAttr.StepCount = el3.value()["StepCount"]; 121 | 122 | if (el3.value().contains("ActuatorType")) tempAttr.ActuatorType = el3.value()["ActuatorType"]; 123 | 124 | if (el3.value().contains("SensorType")) tempAttr.SensorType = el3.value()["SensorType"]; 125 | 126 | if (el3.value().contains("SensorRange")) { 127 | //std::cout << el3.value()["SensorRange"] << std::endl; 128 | for (auto& el4 : el3.value()["SensorRange"].items()) { 129 | tempAttr.SensorRange.push_back(el4.value()[0]); 130 | tempAttr.SensorRange.push_back(el4.value()[1]); 131 | } 132 | //tempCmd.SensorRange.push_back(el2.value()["SensorRange"]); 133 | } 134 | 135 | //if (el2.value().contains("Endpoints")) tempCmd.Endpoints = el2.value()["Endpoints"]; 136 | //std::cout << el2.key() << std::endl; 137 | //std::cout << el2.value() << std::endl; 138 | tempCmd.DeviceCmdAttributes.push_back(tempAttr); 139 | } 140 | 141 | tempD.DeviceMessages.push_back(tempCmd); 142 | } 143 | } 144 | k.Devices.push_back(tempD); 145 | } 146 | } 147 | } 148 | 149 | void from_json(const json& j, DeviceAdded& k) { 150 | json jTemp; 151 | j.at("DeviceAdded").get_to(jTemp); 152 | jTemp.at("Id").get_to(k.Id); 153 | 154 | Device tempD; 155 | 156 | if (jTemp.contains("DeviceName")) k.device.DeviceName = jTemp["DeviceName"]; 157 | 158 | if (jTemp.contains("DeviceIndex")) k.device.DeviceIndex = jTemp["DeviceIndex"]; 159 | 160 | if (jTemp.contains("DeviceMessageTimingGap")) k.device.DeviceMessageTimingGap = jTemp["DeviceMessageTimingGap"]; 161 | 162 | if (jTemp.contains("DeviceDisplayName")) k.device.DeviceName = jTemp["DeviceDisplayName"]; 163 | 164 | if (jTemp.contains("DeviceMessages")) { 165 | json jTemp2; 166 | jTemp2 = jTemp["DeviceMessages"]; 167 | 168 | for (auto& el2 : jTemp2.items()) { 169 | DeviceCmd tempCmd; 170 | tempCmd.CmdType = el2.key(); 171 | 172 | if (!el2.key().compare("StopDeviceCmd")) { 173 | // Do something, not sure what yet 174 | continue; 175 | } 176 | 177 | for (auto& el3 : el2.value().items()) { 178 | DeviceCmdAttr tempAttr; 179 | //std::cout << el3.value() << std::endl; 180 | 181 | if (el3.value().contains("FeatureDescriptor")) tempAttr.FeatureDescriptor = el3.value()["FeatureDescriptor"]; 182 | 183 | if (el3.value().contains("StepCount")) tempAttr.StepCount = el3.value()["StepCount"]; 184 | 185 | if (el3.value().contains("ActuatorType")) tempAttr.ActuatorType = el3.value()["ActuatorType"]; 186 | 187 | if (el3.value().contains("SensorType")) tempAttr.SensorType = el3.value()["SensorType"]; 188 | 189 | if (el3.value().contains("SensorRange")) { 190 | //std::cout << el3.value()["SensorRange"] << std::endl; 191 | for (auto& el4 : el3.value()["SensorRange"].items()) { 192 | tempAttr.SensorRange.push_back(el4.value()[0]); 193 | tempAttr.SensorRange.push_back(el4.value()[1]); 194 | } 195 | } 196 | 197 | //if (el2.value().contains("Endpoints")) tempCmd.Endpoints = el2.value()["Endpoints"]; 198 | //std::cout << el2.key() << std::endl; 199 | //std::cout << el2.value() << std::endl; 200 | tempCmd.DeviceCmdAttributes.push_back(tempAttr); 201 | } 202 | 203 | k.device.DeviceMessages.push_back(tempCmd); 204 | } 205 | } 206 | } 207 | 208 | void from_json(const json& j, SensorReading& k) { 209 | json jTemp; 210 | j.at("SensorReading").get_to(jTemp); 211 | jTemp.at("Id").get_to(k.Id); 212 | jTemp.at("DeviceIndex").get_to(k.DeviceIndex); 213 | jTemp.at("SensorIndex").get_to(k.SensorIndex); 214 | jTemp.at("SensorType").get_to(k.SensorType); 215 | for (auto& el : jTemp["Data"].items()) 216 | k.Data.push_back(el.value()); 217 | } 218 | } --------------------------------------------------------------------------------