From 3f7f673216fdc2580296ec87cb06997b1b54327b Mon Sep 17 00:00:00 2001
From: TheQuinbox <thequinbox@gmail.com>
Date: Wed, 15 May 2024 00:45:57 -0600
Subject: [PATCH] Initial thoughts on a joystick implementation and some
 joystick docs.

---
 .../enums/joystick_power_level.md             |  10 ++
 .../user interface/enums/joystick_type.md     |  17 +++
 .../builtin/user interface/enums/key_code.md  |   2 +-
 src/input.cpp                                 | 105 +++++++++++++++++-
 src/input.h                                   |  75 +++++++++++++
 test/quick/joystick.nvgt                      |   4 +
 6 files changed, 210 insertions(+), 3 deletions(-)
 create mode 100644 doc/src/references/builtin/user interface/enums/joystick_power_level.md
 create mode 100644 doc/src/references/builtin/user interface/enums/joystick_type.md
 create mode 100644 test/quick/joystick.nvgt

diff --git a/doc/src/references/builtin/user interface/enums/joystick_power_level.md b/doc/src/references/builtin/user interface/enums/joystick_power_level.md
new file mode 100644
index 00000000..df6ae8ac
--- /dev/null
+++ b/doc/src/references/builtin/user interface/enums/joystick_power_level.md	
@@ -0,0 +1,10 @@
+# joystick_power_level
+Represents various charging states of a joystick.
+
+* JOYSTICK_POWER_UNKNOWN: the charging state is unknown.
+* JOYSTICK_POWER_EMPTY: the battery is empty (<= 5%).
+* JOYSTICK_POWER_LOW: the battery is low (<= 20%).
+* JOYSTICK_POWER_MEDIUM: the battery is medium (<= 70%).
+* JOYSTICK_POWER_FULL: the battery is full (<= 100%).
+* JOYSTICK_POWER_WIRED: the joystick is currently plugged in.
+    * Note: it is not possible to get the battery level of the joystick while it is charging.
diff --git a/doc/src/references/builtin/user interface/enums/joystick_type.md b/doc/src/references/builtin/user interface/enums/joystick_type.md
new file mode 100644
index 00000000..a50d15a2
--- /dev/null
+++ b/doc/src/references/builtin/user interface/enums/joystick_type.md	
@@ -0,0 +1,17 @@
+# joystick_type
+This enum represents the types of joysticks NVGT knows about. DO note that if you have a joystick that's not in this list, it will most likely still work, but you just won't be able to identify it by type.
+
+* JOYSTICK_TYPE_UNKNOWN: unknown joystick type.
+* JOYSTICK_TYPE_XBOX360: an XBOX 360 controller.
+* JOYSTICK_TYPE_XBOX1: an XBOX 1 series controller.
+* JOYSTICK_TYPE_PS3: a Playstation 3 controller.
+* JOYSTICK_TYPE_PS4: a Playstation 4 controller.
+* JOYSTICK_TYPE_NINTENDO_SWITCH_PRO: a controller for a non-lite (pro) Nintendo Switch.
+* JOYSTICK_TYPE_VIRTUAL: a virtual joystick (for example from a program like JoyToKey).
+* JOYSTICK_TYPE_PS5: a Playstation 5 controller.
+* JOYSTICK_TYPE_AMAZON_LUNA: an Amazon Luna controller.
+* JOYSTICK_TYPE_GOOGLE_STADIA: a Google Stadia controller.
+* JOYSTICK_TYPE_NVIDIA_SHIELD: an NVIDIA shield controller.
+* JOYSTICK_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: the left joycon of a Nintendo Switch.
+* JOYSTICK_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: the right joycon of a Nintendo Switch.
+* JOYSTICK_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: both joycons of a Nintendo switch.
diff --git a/doc/src/references/builtin/user interface/enums/key_code.md b/doc/src/references/builtin/user interface/enums/key_code.md
index 39ac58d4..cb2cb10a 100644
--- a/doc/src/references/builtin/user interface/enums/key_code.md	
+++ b/doc/src/references/builtin/user interface/enums/key_code.md	
@@ -1,5 +1,5 @@
 # key_code
-This is a complete list of possible keycodes in NVGT, as well as a short description of what they are. These are registered in the key_code enum, meaning you can use this type to pass any value listed here around though it is also safe to use integers.
+This is a complete list of possible keycodes in NVGT, as well as a short description of what they are. These are registered in the key_code enum, meaning you can use this type to pass any value listed here around though it is also safe to use unsigned integers.
 
 ## Letter keys
 * KEY_UNKNOWN: unknown.
diff --git a/src/input.cpp b/src/input.cpp
index 74c6f3bc..a7bfd9b4 100644
--- a/src/input.cpp
+++ b/src/input.cpp
@@ -18,7 +18,6 @@
 #endif
 #include <SDL2/SDL.h>
 #include <angelscript.h>
-#include <scriptarray.h>
 #include <obfuscate.h>
 #include <sstream>
 #include <string>
@@ -200,6 +199,57 @@ void mouse_update() {
 	g_MousePrevZ = g_MouseAbsZ;
 }
 
+int joystick_count(bool only_active = true) {
+	int total_joysticks = SDL_NumJoysticks();
+	if (!only_active) return total_joysticks;
+	int ret = 0;
+	for (int i = 0; i < total_joysticks; i++) {
+		if (SDL_IsGameController(i))
+			ret++;
+	}
+	return ret;
+}
+
+joystick::joystick() {
+	if (joystick_count() > 0)
+		stick = SDL_GameControllerOpen(0);
+}
+joystick::~joystick() {
+	SDL_GameControllerClose(stick);
+}
+
+unsigned int joystick::type() const {
+	return SDL_GameControllerGetType(stick);
+}
+unsigned int joystick::power_level() const {
+	return SDL_JoystickCurrentPowerLevel(SDL_GameControllerGetJoystick(stick));
+}
+std::string joystick::name() const {
+	return SDL_GameControllerName(stick);
+}
+bool joystick::active() const {
+	return SDL_GameControllerGetAttached(stick);
+}
+bool joystick::has_led() const {
+	return SDL_GameControllerHasLED(stick);
+}
+bool joystick::can_vibrate() const {
+	return SDL_GameControllerHasRumble(stick);
+}
+bool joystick::can_vibrate_triggers() const {
+	return SDL_GameControllerHasRumbleTriggers(stick);
+}
+
+bool joystick::set_led(unsigned char red, unsigned char green, unsigned char blue) {
+	return SDL_GameControllerSetLED(stick, red, green, blue);
+}
+bool joystick::vibrate(unsigned short low_frequency, unsigned short high_frequency, int duration) {
+	return SDL_GameControllerRumble(stick, low_frequency, high_frequency, duration);
+}
+bool joystick::vibrate_triggers(unsigned short left, unsigned short right, int duration) {
+	return SDL_GameControllerRumbleTriggers(stick, left, right, duration);
+}
+
 bool keyhook_active = true;
 #ifdef _WIN32
 // Thanks Quentin Cosendey (Universal Speech) for this jaws keyboard hook code.
@@ -271,6 +321,10 @@ void uninstall_keyhook() {
 void RegisterInput(asIScriptEngine* engine) {
 	engine->RegisterEnum(_O("key_modifier"));
 	engine->RegisterEnum(_O("key_code"));
+	engine->RegisterEnum(_O("joystick_type"));
+	engine->RegisterEnum(_O("joystick_bind_type"));
+	engine->RegisterEnum(_O("joystick_power_level"));
+	engine->RegisterEnum(_O("joystick_control_type"));
 	engine->RegisterGlobalFunction(_O("bool key_pressed(uint)"), asFUNCTION(KeyPressed), asCALL_CDECL);
 	engine->RegisterGlobalFunction(_O("bool key_repeating(uint)"), asFUNCTION(KeyRepeating), asCALL_CDECL);
 	engine->RegisterGlobalFunction(_O("bool key_down(uint)"), asFUNCTION(key_down), asCALL_CDECL);
@@ -566,4 +620,51 @@ void RegisterInput(asIScriptEngine* engine) {
 	engine->RegisterEnumValue("key_code", "KEY_SOFTRIGHT", SDL_SCANCODE_SOFTRIGHT);
 	engine->RegisterEnumValue("key_code", "KEY_CALL", SDL_SCANCODE_CALL);
 	engine->RegisterEnumValue("key_code", "KEY_ENDCALL", SDL_SCANCODE_ENDCALL);
-}
+	engine->RegisterEnumValue("joystick_type", "JOYSTICK_TYPE_UNKNOWN", SDL_CONTROLLER_TYPE_UNKNOWN);
+	engine->RegisterEnumValue("joystick_type", "JOYSTICK_TYPE_XBOX360", SDL_CONTROLLER_TYPE_XBOX360);
+	engine->RegisterEnumValue("joystick_type", "JOYSTICK_TYPE_XBOX1", SDL_CONTROLLER_TYPE_XBOXONE);
+	engine->RegisterEnumValue("joystick_type", "JOYSTICK_TYPE_PS3", SDL_CONTROLLER_TYPE_PS3);
+	engine->RegisterEnumValue("joystick_type", "JOYSTICK_TYPE_PS4", SDL_CONTROLLER_TYPE_PS4);
+	engine->RegisterEnumValue("joystick_type", "JOYSTICK_TYPE_NINTENDO_SWITCH_PRO", SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO);
+	engine->RegisterEnumValue("joystick_type", "JOYSTICK_TYPE_VIRTUAL", SDL_CONTROLLER_TYPE_VIRTUAL);
+	engine->RegisterEnumValue("joystick_type", "JOYSTICK_TYPE_PS5", SDL_CONTROLLER_TYPE_PS5);
+	engine->RegisterEnumValue("joystick_type", "JOYSTICK_TYPE_AMAZON_LUNA", SDL_CONTROLLER_TYPE_AMAZON_LUNA);
+	engine->RegisterEnumValue("joystick_type", "JOYSTICK_TYPE_GOOGLE_STADIA", SDL_CONTROLLER_TYPE_GOOGLE_STADIA);
+	engine->RegisterEnumValue("joystick_type", "JOYSTICK_TYPE_NVIDIA_SHIELD", SDL_CONTROLLER_TYPE_NVIDIA_SHIELD);
+	engine->RegisterEnumValue("joystick_type", "JOYSTICK_TYPE_NINTENDO_SWITCH_JOYCON_LEFT", SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_LEFT);
+	engine->RegisterEnumValue("joystick_type", "JOYSTICK_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT", SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT);
+	engine->RegisterEnumValue("joystick_type", "JOYSTICK_TYPE_NINTENDO_SWITCH_JOYCON_PAIR", SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_JOYCON_PAIR);
+	engine->RegisterEnumValue("joystick_bind_type", "JOYSTICK_BIND_TYPE_NONE", SDL_CONTROLLER_BINDTYPE_NONE);
+	engine->RegisterEnumValue("joystick_bind_type", "JOYSTICK_BIND_TYPE_BUTTON", SDL_CONTROLLER_BINDTYPE_BUTTON);
+	engine->RegisterEnumValue("joystick_bind_type", "JOYSTICK_BIND_TYPE_AXIS", SDL_CONTROLLER_BINDTYPE_AXIS);
+	engine->RegisterEnumValue("joystick_bind_type", "JOYSTICK_BIND_TYPE_HAT", SDL_CONTROLLER_BINDTYPE_HAT);
+	engine->RegisterEnumValue("joystick_power_level", "JOYSTICK_POWER_UNKNOWN", SDL_JOYSTICK_POWER_UNKNOWN);
+	engine->RegisterEnumValue("joystick_power_level", "JOYSTICK_POWER_EMPTY", SDL_JOYSTICK_POWER_EMPTY);
+	engine->RegisterEnumValue("joystick_power_level", "JOYSTICK_POWER_LOW", SDL_JOYSTICK_POWER_LOW);
+	engine->RegisterEnumValue("joystick_power_level", "JOYSTICK_POWER_MEDIUM", SDL_JOYSTICK_POWER_MEDIUM);
+	engine->RegisterEnumValue("joystick_power_level", "JOYSTICK_POWER_FULL", SDL_JOYSTICK_POWER_FULL);
+	engine->RegisterEnumValue("joystick_power_level", "JOYSTICK_POWER_WIRED", SDL_JOYSTICK_POWER_WIRED);
+	engine->RegisterEnumValue("joystick_control_type", "JOYSTICK_BUTTON_INVALID", SDL_CONTROLLER_BUTTON_INVALID);
+	engine->RegisterEnumValue("joystick_control_type", "JOYSTICK_BUTTON_A", SDL_CONTROLLER_BUTTON_A);
+	engine->RegisterEnumValue("joystick_control_type", "JOYSTICK_BUTTON_B", SDL_CONTROLLER_BUTTON_B);
+	engine->RegisterEnumValue("joystick_control_type", "JOYSTICK_BUTTON_X", SDL_CONTROLLER_BUTTON_X);
+	engine->RegisterEnumValue("joystick_control_type", "JOYSTICK_BUTTON_Y", SDL_CONTROLLER_BUTTON_Y);
+	engine->RegisterEnumValue("joystick_control_type", "JOYSTICK_BUTTON_BACK", SDL_CONTROLLER_BUTTON_BACK);
+	engine->RegisterEnumValue("joystick_control_type", "JOYSTICK_BUTTON_GUIDE", SDL_CONTROLLER_BUTTON_GUIDE);
+	engine->RegisterEnumValue("joystick_control_type", "JOYSTICK_BUTTON_START", SDL_CONTROLLER_BUTTON_START);
+	engine->RegisterEnumValue("joystick_control_type", "JOYSTICK_CONTROL_LEFT_STICK", SDL_CONTROLLER_BUTTON_LEFTSTICK);
+	engine->RegisterEnumValue("joystick_control_type", "JOYSTICK_CONTROL_RIGHT_STICK", SDL_CONTROLLER_BUTTON_RIGHTSTICK);
+	engine->RegisterEnumValue("joystick_control_type", "JOYSTICK_CONTROL_LEFT_SHOULDER", SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
+	engine->RegisterEnumValue("joystick_control_type", "JOYSTICK_CONTROL_RIGHT_SHOULDER", SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
+	engine->RegisterEnumValue("joystick_control_type", "JOYSTICK_CONTROL_DPAD_UP", SDL_CONTROLLER_BUTTON_DPAD_UP);
+	engine->RegisterEnumValue("joystick_control_type", "JOYSTICK_CONTROL_DPAD_DOWN", SDL_CONTROLLER_BUTTON_DPAD_DOWN);
+	engine->RegisterEnumValue("joystick_control_type", "JOYSTICK_CONTROL_DPAD_LEFT", SDL_CONTROLLER_BUTTON_DPAD_LEFT);
+	engine->RegisterEnumValue("joystick_control_type", "JOYSTICK_CONTROL_DPAD_RIGHT", SDL_CONTROLLER_BUTTON_DPAD_RIGHT);
+	engine->RegisterEnumValue("joystick_control_type", "JOYSTICK_BUTTON_MISC", SDL_CONTROLLER_BUTTON_MISC1);
+	engine->RegisterEnumValue("joystick_control_type", "JOYSTICK_CONTROL_PADDLE1", SDL_CONTROLLER_BUTTON_PADDLE1);
+	engine->RegisterEnumValue("joystick_control_type", "JOYSTICK_CONTROL_PADDLE2", SDL_CONTROLLER_BUTTON_PADDLE2);
+	engine->RegisterEnumValue("joystick_control_type", "JOYSTICK_CONTROL_PADDLE3", SDL_CONTROLLER_BUTTON_PADDLE3);
+	engine->RegisterEnumValue("joystick_control_type", "JOYSTICK_CONTROL_PADDLE4", SDL_CONTROLLER_BUTTON_PADDLE4);
+	engine->RegisterEnumValue("joystick_control_type", "JOYSTICK_CONTROL_TOUCHPAD", SDL_CONTROLLER_BUTTON_TOUCHPAD);
+	engine->RegisterGlobalFunction("int joystick_count(bool = true)", asFUNCTION(joystick_count), asCALL_CDECL);
+};
diff --git a/src/input.h b/src/input.h
index fe532ea2..0ab0a1e5 100644
--- a/src/input.h
+++ b/src/input.h
@@ -11,12 +11,87 @@
 */
 
 #pragma once
+
 #include <string>
 #include <angelscript.h>
+#include <scriptarray.h>
+#include <Poco/RefCountedObject.h>
+
 union SDL_Event;
 extern std::string g_UserInput;
 extern bool keyhook_active;
 
+class joystick : Poco::RefCountedObject {
+	SDL_GameController* stick;
+
+public:
+	unsigned int type() const;
+	unsigned int power_level() const;
+	bool has_led() const;
+	bool can_vibrate() const;
+	bool can_vibrate_triggers() const;
+	unsigned int buttons() const;
+	unsigned int sliders() const;
+	unsigned int povs() const;
+	std::string name() const;
+	bool active() const;
+	int preferred_joystick() const;
+	unsigned int x() const;
+	unsigned int y() const;
+	unsigned int z() const;
+	unsigned int r_x() const;
+	unsigned int r_y() const;
+	unsigned int r_z() const;
+	unsigned int slider_1() const;
+	unsigned int slider_2() const;
+	unsigned int pov_1() const;
+	unsigned int pov_2() const;
+	unsigned int pov_3() const;
+	unsigned int pov_4() const;
+	unsigned int v_x() const;
+	unsigned int v_y() const;
+	unsigned int v_z() const;
+	unsigned int vr_x() const;
+	unsigned int vr_y() const;
+	unsigned int vr_z() const;
+	unsigned int v_slider_1() const;
+	unsigned int v_slider_2() const;
+	unsigned int a_x() const;
+	unsigned int a_y() const;
+	unsigned int a_z() const;
+	unsigned int ar_x() const;
+	unsigned int ar_y() const;
+	unsigned int ar_z() const;
+	unsigned int a_slider_1() const;
+	unsigned int a_slider_2() const;
+	unsigned int f_x() const;
+	unsigned int f_y() const;
+	unsigned int f_z() const;
+	unsigned int fr_x() const;
+	unsigned int fr_y() const;
+	unsigned int fr_z() const;
+	unsigned int f_slider_1() const;
+	unsigned int f_slider_2() const;
+
+	joystick();
+	~joystick();
+	bool button_down(int button);
+	bool button_pressed(int button);
+	bool button_released(int button);
+	bool button_up(int button);
+	CScriptArray* buttons_down();
+	CScriptArray* buttons_pressed();
+	CScriptArray* buttons_released();
+	CScriptArray* buttons_up();
+	CScriptArray* list_joysticks();
+	bool pov_centered(int pov);
+	bool set_led(unsigned char red, unsigned char green, unsigned char blue);
+	bool vibrate(unsigned short low_frequency, unsigned short high_frequency, int duration);
+	bool vibrate_triggers(unsigned short left, unsigned short right, int duration);
+	bool refresh_joystick_list();
+	bool set(int index);
+};
+
 void InputInit();
 void InputDestroy();
 void InputEvent(SDL_Event* evt);
diff --git a/test/quick/joystick.nvgt b/test/quick/joystick.nvgt
new file mode 100644
index 00000000..aefcfc61
--- /dev/null
+++ b/test/quick/joystick.nvgt
@@ -0,0 +1,4 @@
+void main() {
+	int joysticks = joystick_count;
+	screen_reader_speak("Your system has " + joysticks + " " + (joysticks == 1 ? "joystick" : "joysticks") + " connected", true);
+}