From 9c723da099ba4ccfa58cc1298a2abe64c65c28ff Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Tue, 27 Jan 2026 22:24:57 -0800 Subject: [PATCH 01/26] WIP: Initial parts of Steam Input API support --- bin/amd64/game_actions_967460.vdf | 99 +++++++++++ src/engine/cdpi.cpp | 6 + src/engine/engine.h | 6 + src/engine/main.cpp | 3 + src/game/controller.cpp | 269 ++++++++++++++++++++++++++++++ src/game/controller.h | 6 + src/game/game.cpp | 14 +- src/game/game.h | 6 + src/game/physics.cpp | 2 + 9 files changed, 407 insertions(+), 4 deletions(-) create mode 100644 bin/amd64/game_actions_967460.vdf create mode 100644 src/game/controller.cpp create mode 100644 src/game/controller.h diff --git a/bin/amd64/game_actions_967460.vdf b/bin/amd64/game_actions_967460.vdf new file mode 100644 index 000000000..ea3416488 --- /dev/null +++ b/bin/amd64/game_actions_967460.vdf @@ -0,0 +1,99 @@ +"In Game Actions" +{ + "actions" + { + "InGameControls" + { + "title" "#Set_Ingame" + "StickPadGyro" + { + "move" + { + "title" "#Action_Move" + "input_mode" "joystick_move" + } + "camera" + { + "title" "#Action_Camera" + "input_mode" "absolute_mouse" + } + } + "Button" + { + "primary" "#Action_Primary" + "secondary" "#Action_Secondary" + "reload" "#Action_Reload" + "use" "#Action_Use" + "jump" "#Action_Jump" + "walk" "#Action_Walk" + "crouch" "#Action_Crouch" + "special" "#Action_Special" + "drop" "#Action_Drop" + "affinity" "#Action_Affinity" + "dash" "#Action_Dash" + + "next_weapon" "#Action_NextWeapon" + "previous_weapon" "#Action_PreviousWeapon" + "primary_weapon" "#Action_PrimaryWeapon" + "secondary_weapon" "#Action_SecondaryWeapon" + "wheel_select" "#Action_WheelSelect" + "change_loadout" "#Action_ChangeLoadout" + + "scoreboard" "#Action_Scoreboard" + "suicide" "#Action_Suicide" + + "recenter_camera" "#Action_RecenterCamera" + + } + } + "MenuControls" + { + "title" "#Set_Menu" + "StickPadGyro" + { + } + "AnalogTrigger" + { + } + "Button" + { + "menu_up" "#Menu_Up" + "menu_down" "#Menu_Down" + "menu_left" "#Menu_Left" + "menu_right" "#Menu_Right" + "menu_select" "#Menu_Select" + "menu_cancel" "#Menu_Cancel" + "pause_menu" "#Action_ReturnToGame" + } + } + } + "localization" + { + "english" + { + "Set_Ingame" "In-Game Controls" + "Set_Menu" "Menu Controls" + "Action_Move" "Move" + "Action_Camera" "Camera" + "Action_Primary" "Primary Fire" + "Action_Secondary" "Secondary Fire" + "Action_Reload" "Reload" + "Action_Use" "Use" + "Action_Jump" "Jump" + "Action_Walk" "Walk" + "Action_Crouch" "Crouch" + "Action_Special" "Special" + "Action_Drop" "Drop" + "Action_Affinity" "Affinity" + "Action_Dash" "Dash" + + "Action_RecenterCamera" "Recenter Camera" + "Menu_Up" "Up" + "Menu_Down" "Down" + "Menu_Left" "Left" + "Menu_Right" "Right" + "Menu_Select" "Select" + "Menu_Cancel" "Cancel" + } + } +} \ No newline at end of file diff --git a/src/engine/cdpi.cpp b/src/engine/cdpi.cpp index 8e0a3558a..76ac92d2a 100644 --- a/src/engine/cdpi.cpp +++ b/src/engine/cdpi.cpp @@ -1,6 +1,7 @@ // Content Delivery Platform Integrations #include "engine.h" +#include "controller.h" #include #if defined(USE_STEAM) #define HAS_STEAM 1 @@ -40,6 +41,7 @@ namespace cdpi ISteamUserStats *stats = NULL; ISteamClient *client = NULL, *sclient = NULL; ISteamGameServer *serv = NULL; + ISteamInput *input = NULL; HSteamPipe umpipe = 0, smpipe = 0; HSteamUser uupipe = 0, supipe = 0; HAuthTicket authticket = k_HAuthTicketInvalid; @@ -184,6 +186,10 @@ namespace cdpi if(!friends) { conoutf(colourred, "Failed to get Steam friends interface."); cleanup(SWCLIENT); return true; } stats = (ISteamUserStats *)SteamAPI_ISteamClient_GetISteamUserStats(client, uupipe, umpipe, STEAMUSERSTATS_INTERFACE_VERSION); if(!stats) { conoutf(colourred, "Failed to get Steam stats interface."); cleanup(SWCLIENT); return true; } + input = (ISteamInput *)SteamAPI_ISteamClient_GetISteamInput(client, uupipe, umpipe, STEAMINPUT_INTERFACE_VERSION); + if (!input) { conoutf(colourred, "Failed to get Steam Input interface."); cleanup(SWCLIENT); return true; } + input->Init(false); + controller::init_action_handles(); const char *name = SteamAPI_ISteamFriends_GetPersonaName(friends); if(name && *name) diff --git a/src/engine/engine.h b/src/engine/engine.h index c0c2d7da1..21dbaebf3 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -3,6 +3,9 @@ #include "version.h" #include "cube.h" +#if defined(USE_STEAM) +#include "steam_api_flat.h" +#endif #define LOG_FILE "log.txt" @@ -49,6 +52,9 @@ namespace cdpi namespace steam { extern char *steamusername, *steamuserid, *steamserverid; + #if defined(USE_STEAM) + extern ISteamInput *input; + #endif extern bool clientready(); extern bool clientauthticket(char *token, uint *tokenlen, ENetAddress *addr = NULL); diff --git a/src/engine/main.cpp b/src/engine/main.cpp index eff8bdc71..d6c0ccd3f 100644 --- a/src/engine/main.cpp +++ b/src/engine/main.cpp @@ -1,7 +1,9 @@ // main.cpp: initialisation & main loop #include "engine.h" +#include "controller.h" #include +#include #ifdef SDL_VIDEO_DRIVER_X11 #include "SDL_syswm.h" @@ -824,6 +826,7 @@ void checkinput() warping = false; if(grabinput && shouldwarp) resetcursor(true, false); } + controller::update_from_controller(); } void swapbuffers(bool overlay) diff --git a/src/game/controller.cpp b/src/game/controller.cpp new file mode 100644 index 000000000..cd4233b1a --- /dev/null +++ b/src/game/controller.cpp @@ -0,0 +1,269 @@ +#include "controller.h" +#include "game.h" +#include "engine.h" + +#if defined(USE_STEAM) +#include "steam_api_flat.h" +#endif + +#include + +#define DEF_ACTION_SET(x) InputActionSetHandle_t x##_handle = -1 +#define DEF_ANALOG_ACTION(x) InputAnalogActionHandle_t x##_handle = -1 +#define DEF_DIGITAL_ACTION(x) class digital_action_state x + +#define SET_ACTION_SET(x) x##_handle = cdpi::steam::input->GetActionSetHandle(#x) +#define SET_ANALOG_ACTION(x) x##_handle =cdpi::steam::input->GetAnalogActionHandle(#x) +#define SET_DIGITAL_ACTION(x) x.set_action_handle(cdpi::steam::input->GetDigitalActionHandle(#x)) + +namespace controller +{ +// If we are not using the SIAPI move action, then do not overwrite strafing +// data set by the regular keyboard bindings +bool lastmovementwaskeyboard = true; + +// This current controller implementation depends on Steam Input and is not +// available outside of Steam +#if defined(USE_STEAM) + +InputHandle_t *controllers = new InputHandle_t[STEAM_INPUT_MAX_COUNT]; + +class digital_action_state +{ + bool input_last_frame = false; + bool input_this_frame = false; + InputDigitalActionHandle_t handle = -1; + +public: + void set_action_handle(InputDigitalActionHandle_t handle) + { + this->handle = handle; + } + + bool get_digital_action_state() + { + InputDigitalActionData_t data = cdpi::steam::input->GetDigitalActionData(controllers[0], this->handle); + return data.bState; + } + + void update() + { + this->input_last_frame = this->input_this_frame; + this->input_this_frame = this->get_digital_action_state(); + } + + bool pressed() + { + return this->input_this_frame; + } + + bool released() + { + return !this->input_this_frame; + } + + bool just_pressed() + { + return this->input_this_frame && !this->input_last_frame; + } + + bool just_released() + { + return !this->input_this_frame && this->input_last_frame; + } +}; + +DEF_ACTION_SET(InGameControls); +DEF_ACTION_SET(MenuControls); + +DEF_ANALOG_ACTION(move); +DEF_ANALOG_ACTION(camera); + +DEF_DIGITAL_ACTION(primary); +DEF_DIGITAL_ACTION(secondary); +DEF_DIGITAL_ACTION(reload); +DEF_DIGITAL_ACTION(use); +DEF_DIGITAL_ACTION(jump); +DEF_DIGITAL_ACTION(walk); +DEF_DIGITAL_ACTION(crouch); +DEF_DIGITAL_ACTION(special); +DEF_DIGITAL_ACTION(drop); +DEF_DIGITAL_ACTION(affinity); +DEF_DIGITAL_ACTION(dash); + +DEF_DIGITAL_ACTION(next_weapon); +DEF_DIGITAL_ACTION(previous_weapon); +DEF_DIGITAL_ACTION(primary_weapon); +DEF_DIGITAL_ACTION(secondary_weapon); +DEF_DIGITAL_ACTION(wheel_select); +DEF_DIGITAL_ACTION(change_loadout); + +DEF_DIGITAL_ACTION(scoreboard); + +DEF_DIGITAL_ACTION(recenter_camera); + +bool get_digital_action_state(int siapi_digital_handle) +{ + InputDigitalActionData_t data = cdpi::steam::input->GetDigitalActionData(controllers[0], siapi_digital_handle); + return data.bState; +} + +void init_action_handles() +{ + SET_ACTION_SET(InGameControls); + SET_ACTION_SET(MenuControls); + + SET_ANALOG_ACTION(move); + SET_ANALOG_ACTION(camera); + + SET_DIGITAL_ACTION(primary); + SET_DIGITAL_ACTION(secondary); + SET_DIGITAL_ACTION(reload); + SET_DIGITAL_ACTION(use); + SET_DIGITAL_ACTION(jump); + SET_DIGITAL_ACTION(walk); + SET_DIGITAL_ACTION(crouch); + SET_DIGITAL_ACTION(special); + SET_DIGITAL_ACTION(drop); + SET_DIGITAL_ACTION(affinity); + //SET_DIGITAL_ACTION(dash); + //SET_DIGITAL_ACTION(next_weapon); + //SET_DIGITAL_ACTION(previous_weapon); + //SET_DIGITAL_ACTION(primary_weapon); + //SET_DIGITAL_ACTION(secondary_weapon); + //SET_DIGITAL_ACTION(wheel_select); + //SET_DIGITAL_ACTION(change_loadout); + SET_DIGITAL_ACTION(scoreboard); // showscores + + SET_DIGITAL_ACTION(recenter_camera); +} + +void handle_digital_action_ac(class digital_action_state *das, int ac) +{ + das->update(); + + if (das->just_pressed()) + physics::doaction(ac, true); + else if (das->just_released()) + physics::doaction(ac, false); +} + +void update_from_controller() +{ + // Steamworks ( https://partner.steamgames.com/doc/api/ISteamInput#RunFrame ) says that + // > Synchronize API state with the latest Steam Controller inputs + // > available. This is performed automatically by + // > SteamAPI_RunCallbacks, but for the absolute lowest possible + // > latency, you can call this directly before reading controller + // > state. + // which appears to be necessary here, otherwise we seem to drop some + // gamepad inputs + cdpi::steam::input->RunFrame(); + + int connected_count = cdpi::steam::input->GetConnectedControllers(controllers); + InputActionSetHandle_t current_set = hud::hasinput(true) ? MenuControls_handle : InGameControls_handle; + cdpi::steam::input->ActivateActionSet(STEAM_INPUT_HANDLE_ALL_CONTROLLERS, current_set); + InputAnalogActionData_t move_data = cdpi::steam::input->GetAnalogActionData( + controllers[0], + move_handle + ); + + //game::player1->move = move_data.y; + + if (move_data.y < -0.5f) + game::player1->move = -1; + else if (move_data.y > 0.5f) + game::player1->move = 1; + else if (!lastmovementwaskeyboard) + game::player1->move = 0; + + //game::player1->strafe = -move_data.x; + + if (move_data.x < -0.5f) + game::player1->strafe = 1; + else if (move_data.x > 0.5f) + game::player1->strafe = -1; + else if (!lastmovementwaskeyboard) + game::player1->strafe = 0; + + if (move_data.x != 0 && move_data.y != 0) + lastmovementwaskeyboard = false; + + // We have to read the camera delta every frame even if we don't intend + // on doing anything with it, otherwise it will 'build up', which is not + // what we want in the cases where we are going to deliberately ignore + // it. + InputAnalogActionData_t camera_delta = cdpi::steam::input->GetAnalogActionData( + controllers[0], + camera_handle + ); + + recenter_camera.update(); + if (recenter_camera.pressed()) { + game::player1->pitch = 0.0f; + } else { + // We deliberately *do not* respect the mouse sensitivity + // settings here. The Steamworks page 'getting started' page ( + // https://partner.steamgames.com/doc/features/steam_controller/getting_started_for_devs + // ) explicitly states that: + + // > You should either rely on the configurator to provide + // > sensitivity (ie you don't filter incoming Steam Input + // > data), or you should use a dedicated sensitivity option for + // > Steam Input that's distinct from the system mouse. + + // We opt to take Option A - make gamepad aim sensitivity + // entirely the responsibility of Steam Input. This reduces the + // amount of additional support code needed in-engine and also + // has the added benefit that the 'Dots per 360' setting for + // flick stick and RWS gyro configuration is always a fixed + // value in every configuration. (This value is 3600, for the + // record). We choose to make the universal base sensitivity + // the value exactly the same as the default sensitivity for + // mouse, so that Steam Input 100% sensitivity matches the + // game's default setting. + + game::player1->yaw += mousesens(camera_delta.x, 100.f, 10.f * game::zoomsens()); + game::player1->pitch -= mousesens(camera_delta.y, 100.f, 10.f * game::zoomsens()); + fixrange(game::player1->yaw, game::player1->pitch); + } + + // WIP: these things all work fine because I am not trying to call + // commands to make them go + handle_digital_action_ac(&primary, AC_PRIMARY); + handle_digital_action_ac(&secondary, AC_SECONDARY); + handle_digital_action_ac(&reload, AC_RELOAD); + handle_digital_action_ac(&use, AC_USE); + handle_digital_action_ac(&jump, AC_JUMP); + handle_digital_action_ac(&walk, AC_WALK); + handle_digital_action_ac(&crouch, AC_CROUCH); + handle_digital_action_ac(&special, AC_SPECIAL); + handle_digital_action_ac(&drop, AC_DROP); + handle_digital_action_ac(&affinity, AC_AFFINITY); + + // WIP: this thing does not work; I don't know how to call commands + // directly + scoreboard.update(); + tagval tv; + if (scoreboard.just_pressed()) { + printf("just pressed scoreboard\n"); + tv.setint(1); + execute(getident("showscores"), &tv, 1); + } else if (scoreboard.just_released()) { + printf("just released scoreboard\n"); + tv.setint(0); + execute(getident("showscores"), &tv, 1); + } +} +#else /* defined(USE_STEAM) */ +void init_action_handles() +{ + return; +} + +void update_from_controller() +{ + return; +} +#endif /* defined(USE_STEAM) */ +} diff --git a/src/game/controller.h b/src/game/controller.h new file mode 100644 index 000000000..f3bc9b901 --- /dev/null +++ b/src/game/controller.h @@ -0,0 +1,6 @@ +namespace controller +{ + extern bool lastmovementwaskeyboard; + extern void init_action_handles(); + extern void update_from_controller(); +} diff --git a/src/game/game.cpp b/src/game/game.cpp index 9e59bd977..fef9ca280 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -6,7 +6,7 @@ namespace game int nextmode = G_EDITING, nextmuts = 0, gamestate = G_S_WAITING, gamemode = G_EDITING, mutators = 0, maptime = 0, mapstart = 0, timeremaining = 0, timeelapsed = 0, timelast = 0, timewait = 0, timesync = 0, lastcamera = 0, lasttvcam = 0, lasttvchg = 0, lastzoom = 0, lastcamcn = -1; - bool zooming = false, inputmouse = false, inputview = false, inputmode = false, wantsloadoutmenu = false, hasspotlights = false; + bool zooming = false, inputmouse = false, inputview = false, inputmode = false, wantsloadoutmenu = false, hasspotlights = false, lastinputwaskeyboard = true; float swayfade = 0, swayspeed = 0, swaydist = 0, bobfade = 0, bobdist = 0; vec swaydir(0, 0, 0), swaypush(0, 0, 0); @@ -3057,11 +3057,17 @@ namespace game else curfov = float(fov()); } + float zoomsens() + { + if (focus == player1 && inzoom() && zoomsensitivity > 0) + return (1.f-((zoomlevel+1)/float(zoomlevels+2)))*zoomsensitivity; + else + return 1.f; + } + VAR(0, mouseoverride, 0, 0, 3); bool mousemove(int dx, int dy, int x, int y, int w, int h) { - #define mousesens(a,b,c) ((float(a)/float(b))*c) - if(mouseoverride&2 || (!mouseoverride && hud::hasinput(true))) { float mousemovex = mousesens(dx, w, mousesensitivity); @@ -3083,7 +3089,7 @@ namespace game physent *d = (!gs_playing(gamestate) || player1->state >= CS_SPECTATOR) && (focus == player1 || followaim()) ? camera1 : (allowmove(player1) ? player1 : NULL); if(d) { - float scale = (focus == player1 && inzoom() && zoomsensitivity > 0 ? (1.f-((zoomlevel+1)/float(zoomlevels+2)))*zoomsensitivity : 1.f)*sensitivity; + float scale = zoomsens()*sensitivity; d->yaw += mousesens(dx, sensitivityscale, yawsensitivity*scale); d->pitch -= mousesens(dy, sensitivityscale, pitchsensitivity*scale*(mouseinvert ? -1.f : 1.f)); fixrange(d->yaw, d->pitch); diff --git a/src/game/game.h b/src/game/game.h index 131a412a2..e92718c08 100644 --- a/src/game/game.h +++ b/src/game/game.h @@ -1246,9 +1246,13 @@ template inline void flashcolourf(T &r, T &g, T &b, T &f, T br, T bg, T f += (bf-f)*amt; } +#define mousesens(a,b,c) ((float(a)/float(b))*c) + namespace game { extern int gamestate, gamemode, mutators; + + extern float zoomsens(); } #define AFFINITYPOS(n) \ namespace n \ @@ -2775,6 +2779,8 @@ namespace physics extern float getwaterextinguish(int mat); extern float getwaterextinguishscale(int mat); + + extern void doaction(int type, bool down); } #define LIQUIDPHYS(name,mat) ((mat&MATF_VOLUME) == MAT_LAVA ? physics::getlava##name(mat)*physics::getlava##name##scale(mat) : physics::getwater##name(mat)*physics::getwater##name##scale(mat)) #define LIQUIDVAR(name,mat) ((mat&MATF_VOLUME) == MAT_LAVA ? physics::getlava##name(mat) : physics::getwater##name(mat)) diff --git a/src/game/physics.cpp b/src/game/physics.cpp index 628f25871..7aa7077bc 100644 --- a/src/game/physics.cpp +++ b/src/game/physics.cpp @@ -1,4 +1,5 @@ #include "game.h" +#include "controller.h" namespace physics { FVAR(IDF_MAP, stairheight, 0, 4.1f, 1000); @@ -71,6 +72,7 @@ namespace physics #define imov(name,v,u,d,s,os) \ void do##name(bool down) \ { \ + controller::lastmovementwaskeyboard = true; \ game::player1->s = down; \ int dir = game::player1->s ? d : (game::player1->os ? -(d) : 0); \ game::player1->v = dir; \ From 6859ab805c785972e2840dda090936173d9045c1 Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Wed, 28 Jan 2026 20:09:30 -0800 Subject: [PATCH 02/26] Remove leftover debug include --- src/engine/main.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/engine/main.cpp b/src/engine/main.cpp index d6c0ccd3f..a05a37651 100644 --- a/src/engine/main.cpp +++ b/src/engine/main.cpp @@ -3,7 +3,6 @@ #include "engine.h" #include "controller.h" #include -#include #ifdef SDL_VIDEO_DRIVER_X11 #include "SDL_syswm.h" From 0a13193b2705ce2260e9e622ba93d8b16646f8b0 Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Wed, 28 Jan 2026 20:10:16 -0800 Subject: [PATCH 03/26] Don't require both X and Y movement to consider it a controller move --- src/game/controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/controller.cpp b/src/game/controller.cpp index cd4233b1a..f6aaf9417 100644 --- a/src/game/controller.cpp +++ b/src/game/controller.cpp @@ -186,7 +186,7 @@ void update_from_controller() else if (!lastmovementwaskeyboard) game::player1->strafe = 0; - if (move_data.x != 0 && move_data.y != 0) + if (move_data.x != 0 || move_data.y != 0) lastmovementwaskeyboard = false; // We have to read the camera delta every frame even if we don't intend From d6d88e9bf2b40aa8086335fdb462e37658f5f77a Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Wed, 28 Jan 2026 20:53:23 -0800 Subject: [PATCH 04/26] Move to using keymaps instead of directly calling doaction --- bin/amd64/game_actions_967460.vdf | 17 +++- config/keymap.cfg | 22 +++++ config/setup.cfg | 26 +++++ src/game/controller.cpp | 155 +++++++++++++++++++++--------- src/game/game.h | 2 - 5 files changed, 172 insertions(+), 50 deletions(-) diff --git a/bin/amd64/game_actions_967460.vdf b/bin/amd64/game_actions_967460.vdf index ea3416488..489379ef9 100644 --- a/bin/amd64/game_actions_967460.vdf +++ b/bin/amd64/game_actions_967460.vdf @@ -41,6 +41,7 @@ "scoreboard" "#Action_Scoreboard" "suicide" "#Action_Suicide" + "menu" "#Action_Menu" "recenter_camera" "#Action_RecenterCamera" @@ -78,16 +79,26 @@ "Action_Primary" "Primary Fire" "Action_Secondary" "Secondary Fire" "Action_Reload" "Reload" - "Action_Use" "Use" + "Action_Use" "Use/Pick Up" "Action_Jump" "Jump" "Action_Walk" "Walk" "Action_Crouch" "Crouch" "Action_Special" "Special" - "Action_Drop" "Drop" - "Action_Affinity" "Affinity" + "Action_Drop" "Drop Weapon" + "Action_Affinity" "Drop Affinity" "Action_Dash" "Dash" + "Action_NextWeapon" "Next Weapon" + "Action_PreviousWeapon" "Previous Weapon" + "Action_PrimaryWeapon" "Primary Weapon" + "Action_SecondaryWeapon" "Secondary Weapon" + "Action_WheelSelect" "Wheel Select" + "Action_ChangeLoadout" "Change Loadout" + "Action_Scoreboard" "Scoreboard" + "Action_Suicide" "Suicide" + "Action_Menu" "Menu" "Action_RecenterCamera" "Recenter Camera" + "Menu_Up" "Up" "Menu_Down" "Down" "Menu_Left" "Left" diff --git a/config/keymap.cfg b/config/keymap.cfg index 05ff94180..cab05f0a6 100644 --- a/config/keymap.cfg +++ b/config/keymap.cfg @@ -16,6 +16,28 @@ keymap -13 MOUSE13 keymap -14 MOUSE14 keymap -15 MOUSE15 keymap -16 MOUSE16 +// Steam Input API controller actions +keymap -20 SIAPI_PRIMARY +keymap -21 SIAPI_SECONDARY +keymap -22 SIAPI_RELOAD +keymap -23 SIAPI_USE +keymap -24 SIAPI_JUMP +keymap -25 SIAPI_WALK +keymap -26 SIAPI_CROUCH +keymap -27 SIAPI_SPECIAL +keymap -28 SIAPI_DROP +keymap -29 SIAPI_AFFINITY +keymap -30 SIAPI_DASH +keymap -31 SIAPI_NEXT_WEAPON +keymap -32 SIAPI_PREVIOUS_WEAPON +keymap -33 SIAPI_PRIMARY_WEAPON +keymap -34 SIAPI_SECONDARY_WEAPON +keymap -35 SIAPI_WHEEL_SELECT +keymap -36 SIAPI_CHANGE_LOADOUT +keymap -37 SIAPI_SCOREBOARD +keymap -38 SIAPI_SUICIDE +keymap -39 SIAPI_MENU + keymap 8 BACKSPACE keymap 9 TAB keymap 13 RETURN diff --git a/config/setup.cfg b/config/setup.cfg index db8c94664..558192910 100644 --- a/config/setup.cfg +++ b/config/setup.cfg @@ -131,6 +131,32 @@ bind K [ suicide ] bind TAB [ showscores ] +// Steam Input controller binds; these actions _should not be rebound_ - do it +// through the Steam Input configuration interface +bind SIAPI_PRIMARY [ primary ] +specbind SIAPI_PRIMARY [ spectate 0 ] +bind SIAPI_SECONDARY [ secondary ] +specbind SIAPI_SECONDARY [ spectate 0 ] +bind SIAPI_WHEEL_SELECT [ game_hud_piemenu_open_weapsel_key ] // Currently broken +bind SIAPI_NEXT_WEAPON [ universaldelta 1 ] +bind SIAPI_PREVIOUS_WEAPON [ universaldelta -1 ] +bind SIAPI_PRIMARY_WEAPON [ weapon (weapload 0) 1 ] +bind SIAPI_SECONDARY_WEAPON [ weapon (weapload 1) 1 ] +bind SIAPI_MENU [ uitoggle ] +bind SIAPI_JUMP [ jump ] +specbind SIAPI_JUMP [ specmodeswitch ] +bind SIAPI_SPECIAL [ special ] +bind SIAPI_CROUCH [ crouch ] +bind SIAPI_USE [ use ] +specbind SIAPI_USE [ spectate 0 ] +bind SIAPI_RELOAD [ reload ] +specbind SIAPI_RELOAD [ specmodeswitch ] +bind SIAPI_DROP [ drop ] +bind SIAPI_AFFINITY [ affinity ] +bind SIAPI_SCOREBOARD [ showscores ] +bind SIAPI_CHANGE_LOADOUT [ gameui_player_show_loadout ] +bind SIAPI_SUICIDE [ suicide ] + saytextcolour = 0; setpersist saytextcolour 1; setcomplete saytextcolour 1 getsaycolour = [ sc = $saytextcolour diff --git a/src/game/controller.cpp b/src/game/controller.cpp index f6aaf9417..9c57b5bec 100644 --- a/src/game/controller.cpp +++ b/src/game/controller.cpp @@ -22,6 +22,30 @@ namespace controller // data set by the regular keyboard bindings bool lastmovementwaskeyboard = true; +// keymap codes to nice names - keep this in sync with keymaps.cfg +enum siapi_keycodes { + SIAPI_PRIMARY = -20, + SIAPI_SECONDARY = -21, + SIAPI_RELOAD = -22, + SIAPI_USE = -23, + SIAPI_JUMP = -24, + SIAPI_WALK = -25, + SIAPI_CROUCH = -26, + SIAPI_SPECIAL = -27, + SIAPI_DROP = -28, + SIAPI_AFFINITY = -29, + SIAPI_DASH = -30, + SIAPI_NEXT_WEAPON = -31, + SIAPI_PREVIOUS_WEAPON = -32, + SIAPI_PRIMARY_WEAPON = -33, + SIAPI_SECONDARY_WEAPON = -34, + SIAPI_WHEEL_SELECT = -35, + SIAPI_CHANGE_LOADOUT = -36, + SIAPI_SCOREBOARD = -37, + SIAPI_SUICIDE = -38, + SIAPI_MENU = -39, +}; + // This current controller implementation depends on Steam Input and is not // available outside of Steam #if defined(USE_STEAM) @@ -33,6 +57,7 @@ class digital_action_state bool input_last_frame = false; bool input_this_frame = false; InputDigitalActionHandle_t handle = -1; + int keymap_id = -1; public: void set_action_handle(InputDigitalActionHandle_t handle) @@ -40,6 +65,11 @@ class digital_action_state this->handle = handle; } + void set_keymap_id(enum siapi_keycodes id) + { + this->keymap_id = id; + } + bool get_digital_action_state() { InputDigitalActionData_t data = cdpi::steam::input->GetDigitalActionData(controllers[0], this->handle); @@ -71,6 +101,16 @@ class digital_action_state { return !this->input_this_frame && this->input_last_frame; } + + void process() + { + this->update(); + + if (this->just_pressed()) + processkey(this->keymap_id, true); + else if (this->just_released()) + processkey(this->keymap_id, false); + } }; DEF_ACTION_SET(InGameControls); @@ -90,15 +130,15 @@ DEF_DIGITAL_ACTION(special); DEF_DIGITAL_ACTION(drop); DEF_DIGITAL_ACTION(affinity); DEF_DIGITAL_ACTION(dash); - DEF_DIGITAL_ACTION(next_weapon); DEF_DIGITAL_ACTION(previous_weapon); DEF_DIGITAL_ACTION(primary_weapon); DEF_DIGITAL_ACTION(secondary_weapon); DEF_DIGITAL_ACTION(wheel_select); DEF_DIGITAL_ACTION(change_loadout); - DEF_DIGITAL_ACTION(scoreboard); +DEF_DIGITAL_ACTION(suicide); +DEF_DIGITAL_ACTION(menu); DEF_DIGITAL_ACTION(recenter_camera); @@ -117,35 +157,66 @@ void init_action_handles() SET_ANALOG_ACTION(camera); SET_DIGITAL_ACTION(primary); + primary.set_keymap_id(SIAPI_PRIMARY); + SET_DIGITAL_ACTION(secondary); + secondary.set_keymap_id(SIAPI_SECONDARY); + SET_DIGITAL_ACTION(reload); + reload.set_keymap_id(SIAPI_RELOAD); + SET_DIGITAL_ACTION(use); + use.set_keymap_id(SIAPI_USE); + SET_DIGITAL_ACTION(jump); + jump.set_keymap_id(SIAPI_JUMP); + SET_DIGITAL_ACTION(walk); + walk.set_keymap_id(SIAPI_WALK); + SET_DIGITAL_ACTION(crouch); + crouch.set_keymap_id(SIAPI_CROUCH); + SET_DIGITAL_ACTION(special); + special.set_keymap_id(SIAPI_SPECIAL); + SET_DIGITAL_ACTION(drop); + drop.set_keymap_id(SIAPI_DROP); + SET_DIGITAL_ACTION(affinity); - //SET_DIGITAL_ACTION(dash); - //SET_DIGITAL_ACTION(next_weapon); - //SET_DIGITAL_ACTION(previous_weapon); - //SET_DIGITAL_ACTION(primary_weapon); - //SET_DIGITAL_ACTION(secondary_weapon); - //SET_DIGITAL_ACTION(wheel_select); - //SET_DIGITAL_ACTION(change_loadout); - SET_DIGITAL_ACTION(scoreboard); // showscores + affinity.set_keymap_id(SIAPI_AFFINITY); - SET_DIGITAL_ACTION(recenter_camera); -} + SET_DIGITAL_ACTION(dash); + dash.set_keymap_id(SIAPI_DASH); -void handle_digital_action_ac(class digital_action_state *das, int ac) -{ - das->update(); + SET_DIGITAL_ACTION(next_weapon); + next_weapon.set_keymap_id(SIAPI_NEXT_WEAPON); + + SET_DIGITAL_ACTION(previous_weapon); + previous_weapon.set_keymap_id(SIAPI_PREVIOUS_WEAPON); + + SET_DIGITAL_ACTION(primary_weapon); + primary_weapon.set_keymap_id(SIAPI_PRIMARY_WEAPON); - if (das->just_pressed()) - physics::doaction(ac, true); - else if (das->just_released()) - physics::doaction(ac, false); + SET_DIGITAL_ACTION(secondary_weapon); + secondary_weapon.set_keymap_id(SIAPI_SECONDARY_WEAPON); + + SET_DIGITAL_ACTION(wheel_select); + wheel_select.set_keymap_id(SIAPI_WHEEL_SELECT); + + SET_DIGITAL_ACTION(change_loadout); + change_loadout.set_keymap_id(SIAPI_CHANGE_LOADOUT); + + SET_DIGITAL_ACTION(scoreboard); + scoreboard.set_keymap_id(SIAPI_SCOREBOARD); + + SET_DIGITAL_ACTION(suicide); + suicide.set_keymap_id(SIAPI_SUICIDE); + + SET_DIGITAL_ACTION(menu); + menu.set_keymap_id(SIAPI_MENU); + + SET_DIGITAL_ACTION(recenter_camera); } void update_from_controller() @@ -228,32 +299,26 @@ void update_from_controller() fixrange(game::player1->yaw, game::player1->pitch); } - // WIP: these things all work fine because I am not trying to call - // commands to make them go - handle_digital_action_ac(&primary, AC_PRIMARY); - handle_digital_action_ac(&secondary, AC_SECONDARY); - handle_digital_action_ac(&reload, AC_RELOAD); - handle_digital_action_ac(&use, AC_USE); - handle_digital_action_ac(&jump, AC_JUMP); - handle_digital_action_ac(&walk, AC_WALK); - handle_digital_action_ac(&crouch, AC_CROUCH); - handle_digital_action_ac(&special, AC_SPECIAL); - handle_digital_action_ac(&drop, AC_DROP); - handle_digital_action_ac(&affinity, AC_AFFINITY); - - // WIP: this thing does not work; I don't know how to call commands - // directly - scoreboard.update(); - tagval tv; - if (scoreboard.just_pressed()) { - printf("just pressed scoreboard\n"); - tv.setint(1); - execute(getident("showscores"), &tv, 1); - } else if (scoreboard.just_released()) { - printf("just released scoreboard\n"); - tv.setint(0); - execute(getident("showscores"), &tv, 1); - } + primary.process(); + secondary.process(); + reload.process(); + use.process(); + jump.process(); + walk.process(); + crouch.process(); + special.process(); + drop.process(); + affinity.process(); + dash.process(); + next_weapon.process(); + previous_weapon.process(); + primary_weapon.process(); + secondary_weapon.process(); + wheel_select.process(); + change_loadout.process(); + scoreboard.process(); + suicide.process(); + menu.process(); } #else /* defined(USE_STEAM) */ void init_action_handles() diff --git a/src/game/game.h b/src/game/game.h index e92718c08..e17b40a2f 100644 --- a/src/game/game.h +++ b/src/game/game.h @@ -2779,8 +2779,6 @@ namespace physics extern float getwaterextinguish(int mat); extern float getwaterextinguishscale(int mat); - - extern void doaction(int type, bool down); } #define LIQUIDPHYS(name,mat) ((mat&MATF_VOLUME) == MAT_LAVA ? physics::getlava##name(mat)*physics::getlava##name##scale(mat) : physics::getwater##name(mat)*physics::getwater##name##scale(mat)) #define LIQUIDVAR(name,mat) ((mat&MATF_VOLUME) == MAT_LAVA ? physics::getlava##name(mat) : physics::getwater##name(mat)) From 57e5cbd9f74ac71617bf537074a1636813cb488f Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Thu, 29 Jan 2026 23:50:43 -0800 Subject: [PATCH 05/26] Make the handle and keymap_id public members --- src/game/controller.cpp | 55 +++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/src/game/controller.cpp b/src/game/controller.cpp index 9c57b5bec..850b930cd 100644 --- a/src/game/controller.cpp +++ b/src/game/controller.cpp @@ -14,7 +14,7 @@ #define SET_ACTION_SET(x) x##_handle = cdpi::steam::input->GetActionSetHandle(#x) #define SET_ANALOG_ACTION(x) x##_handle =cdpi::steam::input->GetAnalogActionHandle(#x) -#define SET_DIGITAL_ACTION(x) x.set_action_handle(cdpi::steam::input->GetDigitalActionHandle(#x)) +#define SET_DIGITAL_ACTION(x) x.handle = cdpi::steam::input->GetDigitalActionHandle(#x) namespace controller { @@ -56,19 +56,10 @@ class digital_action_state { bool input_last_frame = false; bool input_this_frame = false; - InputDigitalActionHandle_t handle = -1; - int keymap_id = -1; public: - void set_action_handle(InputDigitalActionHandle_t handle) - { - this->handle = handle; - } - - void set_keymap_id(enum siapi_keycodes id) - { - this->keymap_id = id; - } + InputDigitalActionHandle_t handle = -1; + int keymap_id = -1; bool get_digital_action_state() { @@ -157,64 +148,64 @@ void init_action_handles() SET_ANALOG_ACTION(camera); SET_DIGITAL_ACTION(primary); - primary.set_keymap_id(SIAPI_PRIMARY); + primary.keymap_id = SIAPI_PRIMARY; SET_DIGITAL_ACTION(secondary); - secondary.set_keymap_id(SIAPI_SECONDARY); + secondary.keymap_id = SIAPI_SECONDARY; SET_DIGITAL_ACTION(reload); - reload.set_keymap_id(SIAPI_RELOAD); + reload.keymap_id = SIAPI_RELOAD; SET_DIGITAL_ACTION(use); - use.set_keymap_id(SIAPI_USE); + use.keymap_id = SIAPI_USE; SET_DIGITAL_ACTION(jump); - jump.set_keymap_id(SIAPI_JUMP); + jump.keymap_id = SIAPI_JUMP; SET_DIGITAL_ACTION(walk); - walk.set_keymap_id(SIAPI_WALK); + walk.keymap_id = SIAPI_WALK; SET_DIGITAL_ACTION(crouch); - crouch.set_keymap_id(SIAPI_CROUCH); + crouch.keymap_id = SIAPI_CROUCH; SET_DIGITAL_ACTION(special); - special.set_keymap_id(SIAPI_SPECIAL); + special.keymap_id = SIAPI_SPECIAL; SET_DIGITAL_ACTION(drop); - drop.set_keymap_id(SIAPI_DROP); + drop.keymap_id = SIAPI_DROP; SET_DIGITAL_ACTION(affinity); - affinity.set_keymap_id(SIAPI_AFFINITY); + affinity.keymap_id = SIAPI_AFFINITY; SET_DIGITAL_ACTION(dash); - dash.set_keymap_id(SIAPI_DASH); + dash.keymap_id = SIAPI_DASH; SET_DIGITAL_ACTION(next_weapon); - next_weapon.set_keymap_id(SIAPI_NEXT_WEAPON); + next_weapon.keymap_id = SIAPI_NEXT_WEAPON; SET_DIGITAL_ACTION(previous_weapon); - previous_weapon.set_keymap_id(SIAPI_PREVIOUS_WEAPON); + previous_weapon.keymap_id = SIAPI_PREVIOUS_WEAPON; SET_DIGITAL_ACTION(primary_weapon); - primary_weapon.set_keymap_id(SIAPI_PRIMARY_WEAPON); + primary_weapon.keymap_id = SIAPI_PRIMARY_WEAPON; SET_DIGITAL_ACTION(secondary_weapon); - secondary_weapon.set_keymap_id(SIAPI_SECONDARY_WEAPON); + secondary_weapon.keymap_id = SIAPI_SECONDARY_WEAPON; SET_DIGITAL_ACTION(wheel_select); - wheel_select.set_keymap_id(SIAPI_WHEEL_SELECT); + wheel_select.keymap_id = SIAPI_WHEEL_SELECT; SET_DIGITAL_ACTION(change_loadout); - change_loadout.set_keymap_id(SIAPI_CHANGE_LOADOUT); + change_loadout.keymap_id = SIAPI_CHANGE_LOADOUT; SET_DIGITAL_ACTION(scoreboard); - scoreboard.set_keymap_id(SIAPI_SCOREBOARD); + scoreboard.keymap_id = SIAPI_SCOREBOARD; SET_DIGITAL_ACTION(suicide); - suicide.set_keymap_id(SIAPI_SUICIDE); + suicide.keymap_id = SIAPI_SUICIDE; SET_DIGITAL_ACTION(menu); - menu.set_keymap_id(SIAPI_MENU); + menu.keymap_id = SIAPI_MENU; SET_DIGITAL_ACTION(recenter_camera); } From c82ecbe8e60881cc88881c184d1ddf5ee0790c23 Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Thu, 29 Jan 2026 23:53:46 -0800 Subject: [PATCH 06/26] Deduplicate function --- src/game/controller.cpp | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/game/controller.cpp b/src/game/controller.cpp index 850b930cd..ee94a2ba1 100644 --- a/src/game/controller.cpp +++ b/src/game/controller.cpp @@ -52,6 +52,12 @@ enum siapi_keycodes { InputHandle_t *controllers = new InputHandle_t[STEAM_INPUT_MAX_COUNT]; +bool get_digital_action_state(int siapi_digital_handle) +{ + InputDigitalActionData_t data = cdpi::steam::input->GetDigitalActionData(controllers[0], siapi_digital_handle); + return data.bState; +} + class digital_action_state { bool input_last_frame = false; @@ -61,16 +67,10 @@ class digital_action_state InputDigitalActionHandle_t handle = -1; int keymap_id = -1; - bool get_digital_action_state() - { - InputDigitalActionData_t data = cdpi::steam::input->GetDigitalActionData(controllers[0], this->handle); - return data.bState; - } - void update() { this->input_last_frame = this->input_this_frame; - this->input_this_frame = this->get_digital_action_state(); + this->input_this_frame = get_digital_action_state(this->handle); } bool pressed() @@ -133,12 +133,6 @@ DEF_DIGITAL_ACTION(menu); DEF_DIGITAL_ACTION(recenter_camera); -bool get_digital_action_state(int siapi_digital_handle) -{ - InputDigitalActionData_t data = cdpi::steam::input->GetDigitalActionData(controllers[0], siapi_digital_handle); - return data.bState; -} - void init_action_handles() { SET_ACTION_SET(InGameControls); From af302731632ac932827b2dc12039cf647d737bcb Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Tue, 3 Feb 2026 00:06:59 -0800 Subject: [PATCH 07/26] Initial glyph implementation --- config/ui/game/settings.cfg | 24 ++++++++++ src/engine/main.cpp | 5 ++ src/engine/rendertext.cpp | 55 +++++++++++++++------ src/engine/rendertext.h | 18 +++++++ src/game/controller.cpp | 96 ++++++++++++++++++++++++++++++++++++- src/game/controller.h | 9 ++++ 6 files changed, 191 insertions(+), 16 deletions(-) create mode 100644 src/engine/rendertext.h diff --git a/config/ui/game/settings.cfg b/config/ui/game/settings.cfg index 7cbe4ddc4..76b4486cb 100644 --- a/config/ui/game/settings.cfg +++ b/config/ui/game/settings.cfg @@ -429,6 +429,30 @@ ui_gameui_settings_controls_dims = 0.72 ] ] + uivlist 0.025 [ + ui_gameui_group [ + uistyle clampx + + uigrid 2 0.05 0 [ + uistyle clampx + + uicolourtext "Type" 0xaaaaaa + ui_gameui_switch textkeyimagepreference [ + p_options = [ + "Automatic" + "Always Keyboard/Mouse" + "Always Controller" + "Both" + ] + p_tip = "Which device icons to show in text" + p_id = #(gameui_get_id switch) + ] + ] + ] [ + p_label = "Input Icons" + ] + ] + ui_gameui_vscrollarea [ uiborderedimageclamped $skintex 0x44010101 0 $ui_texborder $ui_screenborder 0.56 0.5 [ uivlist 0 [ diff --git a/src/engine/main.cpp b/src/engine/main.cpp index a05a37651..cb32fcba1 100644 --- a/src/engine/main.cpp +++ b/src/engine/main.cpp @@ -738,7 +738,10 @@ void checkinput() case SDL_KEYDOWN: case SDL_KEYUP: if(keyrepeatmask || !event.key.repeat) + { + controller::lastinputwassiapi = false; processkey(event.key.keysym.sym, event.key.state==SDL_PRESSED); + } break; case SDL_WINDOWEVENT: @@ -808,10 +811,12 @@ void checkinput() int button = event.button.button; if(button >= 6) button += 4; // skip mousewheel X (-4,-5) & Y (-8, 9) else if(button >= 4) button += 2; // skip mousewheel X (-4,-5) + controller::lastinputwassiapi = false; processkey(-button, event.button.state==SDL_PRESSED); break; } case SDL_MOUSEWHEEL: + controller::lastinputwassiapi = false; if(event.wheel.y > 0) { processkey(-4, true); processkey(-4, false); } else if(event.wheel.y < 0) { processkey(-5, true); processkey(-5, false); } else if(event.wheel.x > 0) { processkey(-8, true); processkey(-8, false); } diff --git a/src/engine/rendertext.cpp b/src/engine/rendertext.cpp index 7ea63cb65..69c277f14 100644 --- a/src/engine/rendertext.cpp +++ b/src/engine/rendertext.cpp @@ -1,4 +1,13 @@ +#include "rendertext.h" #include "engine.h" +#include "controller.h" + +enum textkeyimagetype { + tkip_automatic, // Figure out which glyphs to show based on last input + tkip_kbm, // Always show keyboard/mouse glyphs + tkip_controller, // Always show controller glyphs + tkip_both, // Always show *both* keyboard/mouse and controller glyphs +}; VARF(IDF_PERSIST, textsupersample, 0, 1, 2, initwarning("Text Supersampling", INIT_LOAD, CHANGE_SHADERS)); @@ -14,6 +23,7 @@ FVAR(IDF_PERSIST, textspacescale, 0, 0.5f, 10); FVAR(IDF_PERSIST, textimagescale, 0, 0.8f, FVAR_MAX); VAR(IDF_PERSIST, textkeyimages, 0, 1, 1); +VAR(IDF_PERSIST, textkeyimagepreference, tkip_automatic, tkip_automatic, tkip_both); FVAR(IDF_PERSIST, textkeyimagescale, 0, 0.8f, FVAR_MAX); SVAR(IDF_PERSIST, textkeyprefix, "textures/keys/"); VAR(IDF_PERSIST, textkeyseps, 0, 1, 1); @@ -34,6 +44,21 @@ font *curfont = NULL; int curfontpass = 0; bool wantfontpass = false; +bool shouldkeepkey(const char *str) +{ + bool is_siapi_textkey = controller::is_siapi_textkey(str); + switch (textkeyimagepreference) { + case tkip_automatic: + return controller::lastinputwassiapi ? is_siapi_textkey : !is_siapi_textkey; + case tkip_kbm: + return !is_siapi_textkey; + case tkip_controller: + return is_siapi_textkey; + case tkip_both: + return true; + }; +} + void fontscale(float *scale) { if(!fontdef) return; @@ -742,22 +767,12 @@ void text_boundsf(const char *str, float &width, float &height, float xpad, floa #undef TEXTCHAR } -struct textkey -{ - char *name, *file; - Texture *tex; - textkey() : name(NULL), file(NULL), tex(NULL) {} - textkey(char *n, char *f, Texture *t) : name(newstring(n)), file(newstring(f)), tex(t) {} - ~textkey() - { - DELETEA(name); - DELETEA(file); - } -}; vector textkeys; textkey *findtextkey(const char *str) { + // SIAPI actions have special handling because we can't cache them + if(controller::is_siapi_textkey(str)) return controller::get_siapi_textkey(str); loopv(textkeys) if(!strcmp(textkeys[i]->name, str)) return textkeys[i]; static string key; copystring(key, textkeyprefix); @@ -819,9 +834,15 @@ float key_widthf(const char *str) vector list; explodelist(keyn, list); float width = 0, scale = curfont->scale*curtextscale*textkeyimagescale; + int skippedkeys = 0; loopv(list) { - if(i && textkeyseps) width += text_widthf(" or "); + if(!shouldkeepkey(list[i])) + { + skippedkeys++; + continue; + } + if(i && i > skippedkeys && textkeyseps) width += text_widthf(" or "); if(textkeyimages) { textkey *t = findtextkey(list[i]); @@ -847,9 +868,15 @@ static float draw_key(Texture *&tex, const char *str, float sx, float sy, bvec4 vector list; explodelist(keyn, list); float width = 0; + int skippedkeys = 0; loopv(list) { - if(i && textkeyseps) + if(!shouldkeepkey(list[i])) + { + skippedkeys++; + continue; + } + if(i && i > skippedkeys && textkeyseps) { if(!curfontpass) { diff --git a/src/engine/rendertext.h b/src/engine/rendertext.h new file mode 100644 index 000000000..0280b7a52 --- /dev/null +++ b/src/engine/rendertext.h @@ -0,0 +1,18 @@ +#ifndef CPP_RENDERTEXT_HEADER +#define CPP_RENDERTEXT_HEADER + +#include "engine.h" + +struct textkey +{ + char *name, *file; + Texture *tex; + textkey() : name(NULL), file(NULL), tex(NULL) {} + textkey(char *n, char *f, Texture *t) : name(newstring(n)), file(newstring(f)), tex(t) {} + ~textkey() + { + DELETEA(name); + DELETEA(file); + } +}; +#endif diff --git a/src/game/controller.cpp b/src/game/controller.cpp index ee94a2ba1..44cb0b590 100644 --- a/src/game/controller.cpp +++ b/src/game/controller.cpp @@ -1,5 +1,7 @@ -#include "controller.h" +//#include "controller.h" #include "game.h" +#include "tools.h" +#include "rendertext.h" #include "engine.h" #if defined(USE_STEAM) @@ -18,6 +20,12 @@ namespace controller { +// Track if last input was keyboard or mouse buttons. +// Set to true when using any SIAPI actions +// Set to false when using any other type of action +// Note that mouse motion does _not_ reset the state of this variable. +bool lastinputwassiapi = false; + // If we are not using the SIAPI move action, then do not overwrite strafing // data set by the regular keyboard bindings bool lastmovementwaskeyboard = true; @@ -55,6 +63,7 @@ InputHandle_t *controllers = new InputHandle_t[STEAM_INPUT_MAX_COUNT]; bool get_digital_action_state(int siapi_digital_handle) { InputDigitalActionData_t data = cdpi::steam::input->GetDigitalActionData(controllers[0], siapi_digital_handle); + if (data.bState) lastinputwassiapi = true; return data.bState; } @@ -66,6 +75,9 @@ class digital_action_state public: InputDigitalActionHandle_t handle = -1; int keymap_id = -1; + // Should be more than one origin eventually! + EInputActionOrigin origin = k_EInputActionOrigin_None; + textkey *tk = NULL; void update() { @@ -242,8 +254,10 @@ void update_from_controller() else if (!lastmovementwaskeyboard) game::player1->strafe = 0; - if (move_data.x != 0 || move_data.y != 0) + if (move_data.x != 0 || move_data.y != 0) { + lastinputwassiapi = true; lastmovementwaskeyboard = false; + } // We have to read the camera delta every frame even if we don't intend // on doing anything with it, otherwise it will 'build up', which is not @@ -279,6 +293,7 @@ void update_from_controller() // mouse, so that Steam Input 100% sensitivity matches the // game's default setting. + if (camera_delta.x != 0 || camera_delta.y != 0) lastinputwassiapi = true; game::player1->yaw += mousesens(camera_delta.x, 100.f, 10.f * game::zoomsens()); game::player1->pitch -= mousesens(camera_delta.y, 100.f, 10.f * game::zoomsens()); fixrange(game::player1->yaw, game::player1->pitch); @@ -305,6 +320,73 @@ void update_from_controller() suicide.process(); menu.process(); } + +bool is_siapi_textkey(const char *str) +{ + return !strncmp(str, "SIAPI_", 6); +} + +digital_action_state *get_das_for_keymap_name(const char *str) +{ + // This function is awful, redo to be smarter + if (!strcmp(str, "SIAPI_PRIMARY")) return &primary; + if (!strcmp(str, "SIAPI_SECONDARY")) return &secondary; + if (!strcmp(str, "SIAPI_RELOAD")) return &reload; + if (!strcmp(str, "SIAPI_USE")) return &use; + if (!strcmp(str, "SIAPI_JUMP")) return &jump; + if (!strcmp(str, "SIAPI_WALK")) return &walk; + if (!strcmp(str, "SIAPI_CROUCH")) return &crouch; + if (!strcmp(str, "SIAPI_SPECIAL")) return &special; + if (!strcmp(str, "SIAPI_DROP")) return &drop; + if (!strcmp(str, "SIAPI_AFFINITY")) return &affinity; + if (!strcmp(str, "SIAPI_DASH")) return ‐ + if (!strcmp(str, "SIAPI_NEXT_WEAPON")) return &next_weapon; + if (!strcmp(str, "SIAPI_PREVIOUS_WEAPON")) return &previous_weapon; + if (!strcmp(str, "SIAPI_PRIMARY_WEAPON")) return &primary_weapon; + if (!strcmp(str, "SIAPI_SECONDARY_WEAPON")) return &secondary_weapon; + if (!strcmp(str, "SIAPI_WHEEL_SELECT")) return &wheel_select; + if (!strcmp(str, "SIAPI_CHANGE_LOADOUT")) return &change_loadout; + if (!strcmp(str, "SIAPI_SCOREBOARD")) return &scoreboard; + if (!strcmp(str, "SIAPI_SUICIDE")) return &suicide; + if (!strcmp(str, "SIAPI_MENU")) return &menu; + return NULL; // Should never happen +} + +EInputActionOrigin *origins = NULL; + +textkey *get_siapi_textkey(const char *str) +{ + digital_action_state *das = get_das_for_keymap_name(str); + + if (!das->tk) { + das->tk = new textkey; + das->tk->file = NULL; // we don't use this here + das->tk->name = newstring(str); + } + // We have to check if the origin has changed, and if so, reload the texture + if(!origins) origins = new EInputActionOrigin[STEAM_INPUT_MAX_ORIGINS]; + // Have to clear the origins ourselves + origins[0] = k_EInputActionOrigin_None; + cdpi::steam::input->GetDigitalActionOrigins( + controllers[0], + hud::hasinput(true) ? MenuControls_handle : InGameControls_handle, + das->handle, + origins + ); + if (origins[0] == k_EInputActionOrigin_None || origins[0] > k_EInputActionOrigin_MaximumPossibleValue) { + das->tk->tex = NULL; + } else if (origins[0] != das->origin) { + const char *siapi_origin_glyph = cdpi::steam::input->GetGlyphPNGForActionOrigin( + origins[0], + k_ESteamInputGlyphSize_Medium, + ESteamInputGlyphStyle_Dark + ); + das->tk->tex = textureload(siapi_origin_glyph, 3, true, false); + if(das->tk->tex == notexture) das->tk->tex = NULL; + } + das->origin = origins[0]; + return das->tk; +} #else /* defined(USE_STEAM) */ void init_action_handles() { @@ -315,5 +397,15 @@ void update_from_controller() { return; } + +bool is_siapi_textkey(const char *str) +{ + return false; +} + +textkey *get_siapi_textkey(const char *str) +{ + return NULL; +} #endif /* defined(USE_STEAM) */ } diff --git a/src/game/controller.h b/src/game/controller.h index f3bc9b901..b96511128 100644 --- a/src/game/controller.h +++ b/src/game/controller.h @@ -1,6 +1,15 @@ +#ifndef CPP_CONTROLLER_HEADER +#define CPP_CONTROLLER_HEADER + +#include "rendertext.h" + namespace controller { + extern bool lastinputwassiapi; extern bool lastmovementwaskeyboard; extern void init_action_handles(); extern void update_from_controller(); + extern bool is_siapi_textkey(const char *str); + extern textkey *get_siapi_textkey(const char *str); } +#endif From bf5482d7c1b947be35d615a747eb982328e7a08e Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Wed, 4 Feb 2026 23:06:27 -0800 Subject: [PATCH 08/26] Add menu action set, do cursor/camera actions 'the right way' --- bin/amd64/game_actions_967460.vdf | 24 ++--- src/engine/main.cpp | 2 +- src/game/controller.cpp | 144 ++++++++++++++++++++---------- src/game/game.cpp | 52 ++++++++--- src/game/game.h | 4 - src/shared/igame.h | 2 +- 6 files changed, 152 insertions(+), 76 deletions(-) diff --git a/bin/amd64/game_actions_967460.vdf b/bin/amd64/game_actions_967460.vdf index 489379ef9..6b8dc64bc 100644 --- a/bin/amd64/game_actions_967460.vdf +++ b/bin/amd64/game_actions_967460.vdf @@ -52,19 +52,21 @@ "title" "#Set_Menu" "StickPadGyro" { + "menu_cursor" + { + "title" "#Menu_Cursor" + "input_mode" "absolute_mouse" + } } "AnalogTrigger" { } "Button" { - "menu_up" "#Menu_Up" - "menu_down" "#Menu_Down" - "menu_left" "#Menu_Left" - "menu_right" "#Menu_Right" "menu_select" "#Menu_Select" "menu_cancel" "#Menu_Cancel" - "pause_menu" "#Action_ReturnToGame" + "menu_scroll_up" "#Menu_ScrollUp" + "menu_scroll_down" "#Menu_ScrollDown" } } } @@ -73,7 +75,7 @@ "english" { "Set_Ingame" "In-Game Controls" - "Set_Menu" "Menu Controls" + "Action_Move" "Move" "Action_Camera" "Camera" "Action_Primary" "Primary Fire" @@ -99,12 +101,12 @@ "Action_RecenterCamera" "Recenter Camera" - "Menu_Up" "Up" - "Menu_Down" "Down" - "Menu_Left" "Left" - "Menu_Right" "Right" + "Set_Menu" "Menu Controls" + "Menu_Cursor" "Move Cursor" "Menu_Select" "Select" - "Menu_Cancel" "Cancel" + "Menu_Cancel" "Cancel/Return" + "Menu_ScrollUp" "Scroll Up" + "Menu_ScrollDown" "Scroll Down" } } } \ No newline at end of file diff --git a/src/engine/main.cpp b/src/engine/main.cpp index cb32fcba1..77343014e 100644 --- a/src/engine/main.cpp +++ b/src/engine/main.cpp @@ -788,7 +788,7 @@ void checkinput() { int dx = event.motion.xrel, dy = event.motion.yrel; checkmousemotion(dx, dy); - shouldwarp = game::mousemove(dx, dy, event.motion.x, event.motion.y, screenw, screenh); // whether game controls engine cursor + shouldwarp = game::mousemove(dx, dy, event.motion.x, event.motion.y, screenw, screenh, false); // whether game controls engine cursor mousemoved = true; } else if(shouldgrab) inputgrab(grabinput = true); diff --git a/src/game/controller.cpp b/src/game/controller.cpp index 44cb0b590..0e3f3c9f9 100644 --- a/src/game/controller.cpp +++ b/src/game/controller.cpp @@ -52,6 +52,13 @@ enum siapi_keycodes { SIAPI_SCOREBOARD = -37, SIAPI_SUICIDE = -38, SIAPI_MENU = -39, + // The menu code does not use the keymap system and doesn't allow you to + // rebind those controls. Because of this, it is safe to 'pretend' to be + // the other keys directly with no consequences. + SIAPI_MENU_SELECT = -1, // left mouse + SIAPI_MENU_CANCEL = -2, // right mouse + SIAPI_MENU_SCROLL_UP = -4, // scroll wheel -Y + SIAPI_MENU_SCROLL_DOWN = -5, // scroll wheel +Y }; // This current controller implementation depends on Steam Input and is not @@ -105,7 +112,7 @@ class digital_action_state return !this->input_this_frame && this->input_last_frame; } - void process() + void ingame_process() { this->update(); @@ -114,10 +121,19 @@ class digital_action_state else if (this->just_released()) processkey(this->keymap_id, false); } + + void menu_process() + { + this->update(); + + if (this->just_pressed()) + UI::keypress(this->keymap_id, true); + else if (this->just_released()) + UI::keypress(this->keymap_id, false); + } }; DEF_ACTION_SET(InGameControls); -DEF_ACTION_SET(MenuControls); DEF_ANALOG_ACTION(move); DEF_ANALOG_ACTION(camera); @@ -145,10 +161,17 @@ DEF_DIGITAL_ACTION(menu); DEF_DIGITAL_ACTION(recenter_camera); +DEF_ACTION_SET(MenuControls); +DEF_ANALOG_ACTION(menu_cursor); +DEF_DIGITAL_ACTION(menu_select); +DEF_DIGITAL_ACTION(menu_cancel); +DEF_DIGITAL_ACTION(menu_scroll_up); +DEF_DIGITAL_ACTION(menu_scroll_down); + + void init_action_handles() { SET_ACTION_SET(InGameControls); - SET_ACTION_SET(MenuControls); SET_ANALOG_ACTION(move); SET_ANALOG_ACTION(camera); @@ -214,8 +237,26 @@ void init_action_handles() menu.keymap_id = SIAPI_MENU; SET_DIGITAL_ACTION(recenter_camera); + + SET_ACTION_SET(MenuControls); + SET_ANALOG_ACTION(menu_cursor); + + SET_DIGITAL_ACTION(menu_select); + menu_select.keymap_id = SIAPI_MENU_SELECT; + + SET_DIGITAL_ACTION(menu_cancel); + menu_cancel.keymap_id = SIAPI_MENU_CANCEL; + + SET_DIGITAL_ACTION(menu_scroll_up); + menu_scroll_up.keymap_id = SIAPI_MENU_SCROLL_UP; + + SET_DIGITAL_ACTION(menu_scroll_down); + menu_scroll_down.keymap_id = SIAPI_MENU_SCROLL_DOWN; } +void update_ingame_actions(void); +void update_menu_actions(void); + void update_from_controller() { // Steamworks ( https://partner.steamgames.com/doc/api/ISteamInput#RunFrame ) says that @@ -229,8 +270,18 @@ void update_from_controller() cdpi::steam::input->RunFrame(); int connected_count = cdpi::steam::input->GetConnectedControllers(controllers); - InputActionSetHandle_t current_set = hud::hasinput(true) ? MenuControls_handle : InGameControls_handle; - cdpi::steam::input->ActivateActionSet(STEAM_INPUT_HANDLE_ALL_CONTROLLERS, current_set); + + if (hud::hasinput(true)) { + cdpi::steam::input->ActivateActionSet(STEAM_INPUT_HANDLE_ALL_CONTROLLERS, MenuControls_handle); + update_menu_actions(); + } else { + cdpi::steam::input->ActivateActionSet(STEAM_INPUT_HANDLE_ALL_CONTROLLERS, InGameControls_handle); + update_ingame_actions(); + } +} + +void update_ingame_actions(void) +{ InputAnalogActionData_t move_data = cdpi::steam::input->GetAnalogActionData( controllers[0], move_handle @@ -272,53 +323,48 @@ void update_from_controller() if (recenter_camera.pressed()) { game::player1->pitch = 0.0f; } else { - // We deliberately *do not* respect the mouse sensitivity - // settings here. The Steamworks page 'getting started' page ( - // https://partner.steamgames.com/doc/features/steam_controller/getting_started_for_devs - // ) explicitly states that: - - // > You should either rely on the configurator to provide - // > sensitivity (ie you don't filter incoming Steam Input - // > data), or you should use a dedicated sensitivity option for - // > Steam Input that's distinct from the system mouse. - - // We opt to take Option A - make gamepad aim sensitivity - // entirely the responsibility of Steam Input. This reduces the - // amount of additional support code needed in-engine and also - // has the added benefit that the 'Dots per 360' setting for - // flick stick and RWS gyro configuration is always a fixed - // value in every configuration. (This value is 3600, for the - // record). We choose to make the universal base sensitivity - // the value exactly the same as the default sensitivity for - // mouse, so that Steam Input 100% sensitivity matches the - // game's default setting. - + game::mousemove(camera_delta.x, camera_delta.y, 0, 0, screenw, screenh, true); if (camera_delta.x != 0 || camera_delta.y != 0) lastinputwassiapi = true; - game::player1->yaw += mousesens(camera_delta.x, 100.f, 10.f * game::zoomsens()); - game::player1->pitch -= mousesens(camera_delta.y, 100.f, 10.f * game::zoomsens()); - fixrange(game::player1->yaw, game::player1->pitch); } - primary.process(); - secondary.process(); - reload.process(); - use.process(); - jump.process(); - walk.process(); - crouch.process(); - special.process(); - drop.process(); - affinity.process(); - dash.process(); - next_weapon.process(); - previous_weapon.process(); - primary_weapon.process(); - secondary_weapon.process(); - wheel_select.process(); - change_loadout.process(); - scoreboard.process(); - suicide.process(); - menu.process(); + primary.ingame_process(); + secondary.ingame_process(); + reload.ingame_process(); + use.ingame_process(); + jump.ingame_process(); + walk.ingame_process(); + crouch.ingame_process(); + special.ingame_process(); + drop.ingame_process(); + affinity.ingame_process(); + dash.ingame_process(); + next_weapon.ingame_process(); + previous_weapon.ingame_process(); + primary_weapon.ingame_process(); + secondary_weapon.ingame_process(); + wheel_select.ingame_process(); + change_loadout.ingame_process(); + scoreboard.ingame_process(); + suicide.ingame_process(); + menu.ingame_process(); +} + +void update_menu_actions(void) +{ + InputAnalogActionData_t cursor_data = cdpi::steam::input->GetAnalogActionData( + controllers[0], + menu_cursor_handle + ); + + game::mousemove(cursor_data.x, cursor_data.y, 0, 0, screenw, screenh, true); + + if (cursor_data.x != 0 || cursor_data.y != 0) + lastinputwassiapi = true; + + menu_select.menu_process(); + menu_cancel.menu_process(); + menu_scroll_up.menu_process(); + menu_scroll_down.menu_process(); } bool is_siapi_textkey(const char *str) diff --git a/src/game/game.cpp b/src/game/game.cpp index fef9ca280..faf9906d7 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -3060,18 +3060,43 @@ namespace game float zoomsens() { if (focus == player1 && inzoom() && zoomsensitivity > 0) - return (1.f-((zoomlevel+1)/float(zoomlevels+2)))*zoomsensitivity; - else - return 1.f; + return (1.f-((zoomlevel+1)/float(zoomlevels+2)))*zoomsensitivity; + else + return 1.f; } VAR(0, mouseoverride, 0, 0, 3); - bool mousemove(int dx, int dy, int x, int y, int w, int h) - { + // FIXME: We take x and y parameters but don't use them. A relic of an + // earlier implementation? Delete if possible. + bool mousemove(float dx, float dy, int x, int y, int w, int h, bool fromcontroller) + { + // When input comes from a controller, we deliberately *do not* respect + // the mouse sensitivity settings. The Steamworks page 'getting started' + // page ( + // https://partner.steamgames.com/doc/features/steam_controller/getting_started_for_devs + // ) explicitly states that: + + // > You should either rely on the configurator to provide sensitivity + // > (ie you don't filter incoming Steam Input data), or you should use + // > a dedicated sensitivity option for Steam Input that's distinct from + // > the system mouse. + + // We opt to take Option A - make gamepad aim sensitivity entirely the + // responsibility of Steam Input. This reduces the amount of additional + // support code needed in-engine and also has the added benefit that the + // 'Dots per 360' setting for flick stick and RWS gyro configuration is + // always a fixed value in every configuration. (This value is 3600, for + // the record). We choose to make the universal base sensitivity the + // value exactly the same as the default sensitivity for mouse, so that + // Steam Input 100% sensitivity matches the game's default setting. + + #define mousesens(a,b,c) ((float(a)/float(b))*c) + if(mouseoverride&2 || (!mouseoverride && hud::hasinput(true))) { - float mousemovex = mousesens(dx, w, mousesensitivity); - float mousemovey = mousesens(dy, h, mousesensitivity); + float scale = fromcontroller ? 1.0f : mousesensitivity; + float mousemovex = mousesens(dx, w, scale); + float mousemovey = mousesens(dy, h, scale); UI::mousetrack(mousemovex, mousemovey); @@ -3089,9 +3114,16 @@ namespace game physent *d = (!gs_playing(gamestate) || player1->state >= CS_SPECTATOR) && (focus == player1 || followaim()) ? camera1 : (allowmove(player1) ? player1 : NULL); if(d) { - float scale = zoomsens()*sensitivity; - d->yaw += mousesens(dx, sensitivityscale, yawsensitivity*scale); - d->pitch -= mousesens(dy, sensitivityscale, pitchsensitivity*scale*(mouseinvert ? -1.f : 1.f)); + if (fromcontroller) + { + float scale = zoomsens()*10.f; + d->yaw += mousesens(dx, 100.f, scale); + d->pitch -= mousesens(dy, 100.f, scale); + } else { + float scale = zoomsens()*sensitivity; + d->yaw += mousesens(dx, sensitivityscale, yawsensitivity*scale); + d->pitch -= mousesens(dy, sensitivityscale, pitchsensitivity*scale*(mouseinvert ? -1.f : 1.f)); + } fixrange(d->yaw, d->pitch); } return true; diff --git a/src/game/game.h b/src/game/game.h index e17b40a2f..131a412a2 100644 --- a/src/game/game.h +++ b/src/game/game.h @@ -1246,13 +1246,9 @@ template inline void flashcolourf(T &r, T &g, T &b, T &f, T br, T bg, T f += (bf-f)*amt; } -#define mousesens(a,b,c) ((float(a)/float(b))*c) - namespace game { extern int gamestate, gamemode, mutators; - - extern float zoomsens(); } #define AFFINITYPOS(n) \ namespace n \ diff --git a/src/shared/igame.h b/src/shared/igame.h index 813adc738..65a21d839 100644 --- a/src/shared/igame.h +++ b/src/shared/igame.h @@ -182,7 +182,7 @@ namespace game extern void fxtrack(vec &pos, physent *owner, int mode, int tag = 0); extern void particletrack(particle *p, uint type, int &ts, bool step); extern void dynlighttrack(physent *owner, vec &o, vec &hud); - extern bool mousemove(int dx, int dy, int x, int y, int w, int h); + extern bool mousemove(float dx, float dy, int x, int y, int w, int h, bool fromcontroller); extern void recomputecamera(); extern void adjustorientation(vec &pos); extern int gettimeremain(); From 36f75cb85755aac02c721287c5db21280f4bdb2c Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Sat, 7 Feb 2026 11:56:06 -0800 Subject: [PATCH 09/26] Add 'real' interface to reset player pitch --- src/game/controller.cpp | 2 +- src/game/game.cpp | 9 +++++++++ src/shared/igame.h | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/game/controller.cpp b/src/game/controller.cpp index 0e3f3c9f9..be19ce4d5 100644 --- a/src/game/controller.cpp +++ b/src/game/controller.cpp @@ -321,7 +321,7 @@ void update_ingame_actions(void) recenter_camera.update(); if (recenter_camera.pressed()) { - game::player1->pitch = 0.0f; + game::resetplayerpitch(); } else { game::mousemove(camera_delta.x, camera_delta.y, 0, 0, screenw, screenh, true); if (camera_delta.x != 0 || camera_delta.y != 0) lastinputwassiapi = true; diff --git a/src/game/game.cpp b/src/game/game.cpp index faf9906d7..a5fc3d214 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -3131,6 +3131,15 @@ namespace game return false; } + void resetplayerpitch() + { + if(!gs_waiting(gamestate) && (mouseoverride&1 || (!mouseoverride && !tvmode()))) + { + if((!gs_playing(gamestate) || player1->state >= CS_SPECTATOR && (focus == player1 || followaim()))) return; + if(allowmove(player1)) player1->pitch = 0.0f; + } + } + void getyawpitch(const vec &from, const vec &pos, float &yaw, float &pitch) { float dist = from.dist(pos); diff --git a/src/shared/igame.h b/src/shared/igame.h index 65a21d839..97ce979bd 100644 --- a/src/shared/igame.h +++ b/src/shared/igame.h @@ -183,6 +183,7 @@ namespace game extern void particletrack(particle *p, uint type, int &ts, bool step); extern void dynlighttrack(physent *owner, vec &o, vec &hud); extern bool mousemove(float dx, float dy, int x, int y, int w, int h, bool fromcontroller); + extern void resetplayerpitch(void); extern void recomputecamera(); extern void adjustorientation(vec &pos); extern int gettimeremain(); From 645f1086502307bce870b65d8b8dceaf5c5b17a0 Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Sat, 7 Feb 2026 13:06:52 -0800 Subject: [PATCH 10/26] Accept input from all connected controllers --- src/game/controller.cpp | 173 +++++++++++++++++++++------------------- 1 file changed, 93 insertions(+), 80 deletions(-) diff --git a/src/game/controller.cpp b/src/game/controller.cpp index be19ce4d5..c6fd27e6d 100644 --- a/src/game/controller.cpp +++ b/src/game/controller.cpp @@ -66,18 +66,22 @@ enum siapi_keycodes { #if defined(USE_STEAM) InputHandle_t *controllers = new InputHandle_t[STEAM_INPUT_MAX_COUNT]; +InputHandle_t lastusedcontroller = 0; -bool get_digital_action_state(int siapi_digital_handle) +bool get_digital_action_state(int controlleridx, int siapi_digital_handle) { - InputDigitalActionData_t data = cdpi::steam::input->GetDigitalActionData(controllers[0], siapi_digital_handle); - if (data.bState) lastinputwassiapi = true; + InputDigitalActionData_t data = cdpi::steam::input->GetDigitalActionData(controllers[controlleridx], siapi_digital_handle); + if (data.bState) { + lastinputwassiapi = true; + lastusedcontroller = controllers[controlleridx]; + } return data.bState; } class digital_action_state { - bool input_last_frame = false; - bool input_this_frame = false; + bool input_last_frame[STEAM_INPUT_MAX_COUNT]; + bool input_this_frame[STEAM_INPUT_MAX_COUNT]; public: InputDigitalActionHandle_t handle = -1; @@ -86,49 +90,49 @@ class digital_action_state EInputActionOrigin origin = k_EInputActionOrigin_None; textkey *tk = NULL; - void update() + void update(int controlleridx) { - this->input_last_frame = this->input_this_frame; - this->input_this_frame = get_digital_action_state(this->handle); + this->input_last_frame[controlleridx] = this->input_this_frame[controlleridx]; + this->input_this_frame[controlleridx] = get_digital_action_state(controlleridx, this->handle); } - bool pressed() + bool pressed(int controlleridx) { - return this->input_this_frame; + return this->input_this_frame[controlleridx]; } - bool released() + bool released(int controlleridx) { - return !this->input_this_frame; + return !this->input_this_frame[controlleridx]; } - bool just_pressed() + bool just_pressed(int controlleridx) { - return this->input_this_frame && !this->input_last_frame; + return this->input_this_frame[controlleridx] && !this->input_last_frame[controlleridx]; } - bool just_released() + bool just_released(int controlleridx) { - return !this->input_this_frame && this->input_last_frame; + return !this->input_this_frame[controlleridx] && this->input_last_frame[controlleridx]; } - void ingame_process() + void ingame_process(int controlleridx) { - this->update(); + this->update(controlleridx); - if (this->just_pressed()) + if (this->just_pressed(controlleridx)) processkey(this->keymap_id, true); - else if (this->just_released()) + else if (this->just_released(controlleridx)) processkey(this->keymap_id, false); } - void menu_process() + void menu_process(int controlleridx) { - this->update(); + this->update(controlleridx); - if (this->just_pressed()) + if (this->just_pressed(controlleridx)) UI::keypress(this->keymap_id, true); - else if (this->just_released()) + else if (this->just_released(controlleridx)) UI::keypress(this->keymap_id, false); } }; @@ -254,8 +258,8 @@ void init_action_handles() menu_scroll_down.keymap_id = SIAPI_MENU_SCROLL_DOWN; } -void update_ingame_actions(void); -void update_menu_actions(void); +void update_ingame_actions(int controlleridx); +void update_menu_actions(int controlleridx); void update_from_controller() { @@ -271,42 +275,48 @@ void update_from_controller() int connected_count = cdpi::steam::input->GetConnectedControllers(controllers); - if (hud::hasinput(true)) { - cdpi::steam::input->ActivateActionSet(STEAM_INPUT_HANDLE_ALL_CONTROLLERS, MenuControls_handle); - update_menu_actions(); - } else { - cdpi::steam::input->ActivateActionSet(STEAM_INPUT_HANDLE_ALL_CONTROLLERS, InGameControls_handle); - update_ingame_actions(); - } + if (connected_count == 0) return; + + for (int i = 0; i < connected_count; i++) + if (hud::hasinput(true)) { + cdpi::steam::input->ActivateActionSet(STEAM_INPUT_HANDLE_ALL_CONTROLLERS, MenuControls_handle); + update_menu_actions(i); + } else { + cdpi::steam::input->ActivateActionSet(STEAM_INPUT_HANDLE_ALL_CONTROLLERS, InGameControls_handle); + update_ingame_actions(i); + } } -void update_ingame_actions(void) +void update_ingame_actions(int controlleridx) { InputAnalogActionData_t move_data = cdpi::steam::input->GetAnalogActionData( - controllers[0], + controllers[controlleridx], move_handle ); - //game::player1->move = move_data.y; + if (lastusedcontroller == controllers[controlleridx]) { + //game::player1->move = move_data.y; - if (move_data.y < -0.5f) - game::player1->move = -1; - else if (move_data.y > 0.5f) - game::player1->move = 1; - else if (!lastmovementwaskeyboard) - game::player1->move = 0; + if (move_data.y < -0.5f) + game::player1->move = -1; + else if (move_data.y > 0.5f) + game::player1->move = 1; + else if (!lastmovementwaskeyboard) + game::player1->move = 0; - //game::player1->strafe = -move_data.x; + //game::player1->strafe = -move_data.x; - if (move_data.x < -0.5f) - game::player1->strafe = 1; - else if (move_data.x > 0.5f) - game::player1->strafe = -1; - else if (!lastmovementwaskeyboard) - game::player1->strafe = 0; + if (move_data.x < -0.5f) + game::player1->strafe = 1; + else if (move_data.x > 0.5f) + game::player1->strafe = -1; + else if (!lastmovementwaskeyboard) + game::player1->strafe = 0; + } if (move_data.x != 0 || move_data.y != 0) { lastinputwassiapi = true; + lastusedcontroller = controllers[controlleridx]; lastmovementwaskeyboard = false; } @@ -315,44 +325,47 @@ void update_ingame_actions(void) // what we want in the cases where we are going to deliberately ignore // it. InputAnalogActionData_t camera_delta = cdpi::steam::input->GetAnalogActionData( - controllers[0], + controllers[controlleridx], camera_handle ); - recenter_camera.update(); - if (recenter_camera.pressed()) { + recenter_camera.update(controlleridx); + if (recenter_camera.pressed(controlleridx)) { game::resetplayerpitch(); } else { game::mousemove(camera_delta.x, camera_delta.y, 0, 0, screenw, screenh, true); - if (camera_delta.x != 0 || camera_delta.y != 0) lastinputwassiapi = true; + if (camera_delta.x != 0 || camera_delta.y != 0) { + lastinputwassiapi = true; + lastusedcontroller = controllers[controlleridx]; + } } - primary.ingame_process(); - secondary.ingame_process(); - reload.ingame_process(); - use.ingame_process(); - jump.ingame_process(); - walk.ingame_process(); - crouch.ingame_process(); - special.ingame_process(); - drop.ingame_process(); - affinity.ingame_process(); - dash.ingame_process(); - next_weapon.ingame_process(); - previous_weapon.ingame_process(); - primary_weapon.ingame_process(); - secondary_weapon.ingame_process(); - wheel_select.ingame_process(); - change_loadout.ingame_process(); - scoreboard.ingame_process(); - suicide.ingame_process(); - menu.ingame_process(); + primary.ingame_process(controlleridx); + secondary.ingame_process(controlleridx); + reload.ingame_process(controlleridx); + use.ingame_process(controlleridx); + jump.ingame_process(controlleridx); + walk.ingame_process(controlleridx); + crouch.ingame_process(controlleridx); + special.ingame_process(controlleridx); + drop.ingame_process(controlleridx); + affinity.ingame_process(controlleridx); + dash.ingame_process(controlleridx); + next_weapon.ingame_process(controlleridx); + previous_weapon.ingame_process(controlleridx); + primary_weapon.ingame_process(controlleridx); + secondary_weapon.ingame_process(controlleridx); + wheel_select.ingame_process(controlleridx); + change_loadout.ingame_process(controlleridx); + scoreboard.ingame_process(controlleridx); + suicide.ingame_process(controlleridx); + menu.ingame_process(controlleridx); } -void update_menu_actions(void) +void update_menu_actions(int controlleridx) { InputAnalogActionData_t cursor_data = cdpi::steam::input->GetAnalogActionData( - controllers[0], + controllers[controlleridx], menu_cursor_handle ); @@ -361,10 +374,10 @@ void update_menu_actions(void) if (cursor_data.x != 0 || cursor_data.y != 0) lastinputwassiapi = true; - menu_select.menu_process(); - menu_cancel.menu_process(); - menu_scroll_up.menu_process(); - menu_scroll_down.menu_process(); + menu_select.menu_process(controlleridx); + menu_cancel.menu_process(controlleridx); + menu_scroll_up.menu_process(controlleridx); + menu_scroll_down.menu_process(controlleridx); } bool is_siapi_textkey(const char *str) @@ -414,7 +427,7 @@ textkey *get_siapi_textkey(const char *str) // Have to clear the origins ourselves origins[0] = k_EInputActionOrigin_None; cdpi::steam::input->GetDigitalActionOrigins( - controllers[0], + lastusedcontroller, hud::hasinput(true) ? MenuControls_handle : InGameControls_handle, das->handle, origins From 86e0406b32147ec3a458d7a7343b9c2eab24ada1 Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Sat, 7 Feb 2026 13:38:11 -0800 Subject: [PATCH 11/26] Be prepared to initialize SIAPI handles after boot --- src/engine/cdpi.cpp | 2 -- src/game/controller.cpp | 10 ++++------ src/game/controller.h | 1 - 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/engine/cdpi.cpp b/src/engine/cdpi.cpp index 76ac92d2a..b9044ce95 100644 --- a/src/engine/cdpi.cpp +++ b/src/engine/cdpi.cpp @@ -1,7 +1,6 @@ // Content Delivery Platform Integrations #include "engine.h" -#include "controller.h" #include #if defined(USE_STEAM) #define HAS_STEAM 1 @@ -189,7 +188,6 @@ namespace cdpi input = (ISteamInput *)SteamAPI_ISteamClient_GetISteamInput(client, uupipe, umpipe, STEAMINPUT_INTERFACE_VERSION); if (!input) { conoutf(colourred, "Failed to get Steam Input interface."); cleanup(SWCLIENT); return true; } input->Init(false); - controller::init_action_handles(); const char *name = SteamAPI_ISteamFriends_GetPersonaName(friends); if(name && *name) diff --git a/src/game/controller.cpp b/src/game/controller.cpp index c6fd27e6d..5a68074a2 100644 --- a/src/game/controller.cpp +++ b/src/game/controller.cpp @@ -173,7 +173,7 @@ DEF_DIGITAL_ACTION(menu_scroll_up); DEF_DIGITAL_ACTION(menu_scroll_down); -void init_action_handles() +void init_siapi_handles() { SET_ACTION_SET(InGameControls); @@ -263,6 +263,9 @@ void update_menu_actions(int controlleridx); void update_from_controller() { + // Initialize handles if needed + if(!InGameControls_handle) controller::init_siapi_handles(); + // Steamworks ( https://partner.steamgames.com/doc/api/ISteamInput#RunFrame ) says that // > Synchronize API state with the latest Steam Controller inputs // > available. This is performed automatically by @@ -447,11 +450,6 @@ textkey *get_siapi_textkey(const char *str) return das->tk; } #else /* defined(USE_STEAM) */ -void init_action_handles() -{ - return; -} - void update_from_controller() { return; diff --git a/src/game/controller.h b/src/game/controller.h index b96511128..3f972dc42 100644 --- a/src/game/controller.h +++ b/src/game/controller.h @@ -7,7 +7,6 @@ namespace controller { extern bool lastinputwassiapi; extern bool lastmovementwaskeyboard; - extern void init_action_handles(); extern void update_from_controller(); extern bool is_siapi_textkey(const char *str); extern textkey *get_siapi_textkey(const char *str); From 7a1034c1a8b7f6732f09b2f4bd78c5fc83679406 Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Sat, 7 Feb 2026 16:19:22 -0800 Subject: [PATCH 12/26] Fix default state of some handles --- src/game/controller.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game/controller.cpp b/src/game/controller.cpp index 5a68074a2..5d6250f31 100644 --- a/src/game/controller.cpp +++ b/src/game/controller.cpp @@ -10,8 +10,8 @@ #include -#define DEF_ACTION_SET(x) InputActionSetHandle_t x##_handle = -1 -#define DEF_ANALOG_ACTION(x) InputAnalogActionHandle_t x##_handle = -1 +#define DEF_ACTION_SET(x) InputActionSetHandle_t x##_handle = 0 +#define DEF_ANALOG_ACTION(x) InputAnalogActionHandle_t x##_handle = 0 #define DEF_DIGITAL_ACTION(x) class digital_action_state x #define SET_ACTION_SET(x) x##_handle = cdpi::steam::input->GetActionSetHandle(#x) From db87ec22e72c9fad0c56dc8caf096374b1d1c7d4 Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Sat, 7 Feb 2026 16:36:23 -0800 Subject: [PATCH 13/26] Add 'open controller configurator' button to controls settings --- config/ui/game/settings.cfg | 5 +++++ src/game/controller.cpp | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/config/ui/game/settings.cfg b/config/ui/game/settings.cfg index 76b4486cb..c53ef1527 100644 --- a/config/ui/game/settings.cfg +++ b/config/ui/game/settings.cfg @@ -451,6 +451,11 @@ ui_gameui_settings_controls_dims = 0.72 ] [ p_label = "Input Icons" ] + ui_gameui_button [ + p_label = "Open Controller Configurator" + p_on_click = [ showsiapibindpanel ] + p_id = #(gameui_get_id button) + ] ] ui_gameui_vscrollarea [ diff --git a/src/game/controller.cpp b/src/game/controller.cpp index 5d6250f31..71cf0ebef 100644 --- a/src/game/controller.cpp +++ b/src/game/controller.cpp @@ -449,6 +449,8 @@ textkey *get_siapi_textkey(const char *str) das->origin = origins[0]; return das->tk; } + +ICOMMAND(0, showsiapibindpanel, "", (), { cdpi::steam::input->ShowBindingPanel(lastusedcontroller); }); #else /* defined(USE_STEAM) */ void update_from_controller() { @@ -464,5 +466,7 @@ textkey *get_siapi_textkey(const char *str) { return NULL; } + +ICOMMAND(0, showsiapibindpanel, "", (), { return; }); #endif /* defined(USE_STEAM) */ } From 993a7ef7dd28af350b59afc5daea850c0f25befa Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Sat, 7 Feb 2026 21:25:11 -0800 Subject: [PATCH 14/26] Add actions for every individual weapon --- bin/amd64/game_actions_967460.vdf | 34 ++++++- config/keymap.cfg | 16 ++++ config/setup.cfg | 16 ++++ src/game/controller.cpp | 150 ++++++++++++++++++++++++------ 4 files changed, 188 insertions(+), 28 deletions(-) diff --git a/bin/amd64/game_actions_967460.vdf b/bin/amd64/game_actions_967460.vdf index 6b8dc64bc..917508881 100644 --- a/bin/amd64/game_actions_967460.vdf +++ b/bin/amd64/game_actions_967460.vdf @@ -36,7 +36,23 @@ "previous_weapon" "#Action_PreviousWeapon" "primary_weapon" "#Action_PrimaryWeapon" "secondary_weapon" "#Action_SecondaryWeapon" - "wheel_select" "#Action_WheelSelect" + "claw" "#Action_Claw" + "pistol" "#Action_Pistol" + "sword" "#Action_Sword" + "shotgun" "#Action_Shotgun" + "smg" "#Action_SMG" + "flamer" "#Action_Flamer" + "plasma" "#Action_Plasma" + "zapper" "#Action_Zapper" + "rifle" "#Action_Rifle" + "corroder" "#Action_Corroder" + "grenade" "#Action_Grenade" + "mine" "#Action_Mine" + "rocket" "#Action_Rocket" + "minigun" "#Action_Minigun" + "jetsaw" "#Action_Jetsaw" + "melee" "#Action_Melee" + "wheel_select" "#Action_WheelSelect" "change_loadout" "#Action_ChangeLoadout" "scoreboard" "#Action_Scoreboard" @@ -93,6 +109,22 @@ "Action_PreviousWeapon" "Previous Weapon" "Action_PrimaryWeapon" "Primary Weapon" "Action_SecondaryWeapon" "Secondary Weapon" + "Action_Claw" "Claw Attack" + "Action_Pistol" "Sidearm Pistol" + "Action_Sword" "Energy Sword" + "Action_Shotgun" "Super Shotgun" + "Action_SMG" "Submachine Gun" + "Action_Flamer" "Flame Thrower" + "Action_Plasma" "Plasma Inductor" + "Action_Zapper" "Electro Zapper" + "Action_Rifle" "Laser Rifle" + "Action_Corroder" "Corrosion Cannon" + "Action_Grenade" "Frag Grenade" + "Action_Mine" "Shock Mine" + "Action_Rocket" "Rocket Launcher" + "Action_Minigun" "Heavy Minigun" + "Action_Jetsaw" "Jet Chainsaw" + "Action_Melee" "Eclipse Rod" "Action_WheelSelect" "Wheel Select" "Action_ChangeLoadout" "Change Loadout" "Action_Scoreboard" "Scoreboard" diff --git a/config/keymap.cfg b/config/keymap.cfg index cab05f0a6..7da36bd5e 100644 --- a/config/keymap.cfg +++ b/config/keymap.cfg @@ -37,6 +37,22 @@ keymap -36 SIAPI_CHANGE_LOADOUT keymap -37 SIAPI_SCOREBOARD keymap -38 SIAPI_SUICIDE keymap -39 SIAPI_MENU +keymap -40 SIAPI_CLAW +keymap -41 SIAPI_PISTOL +keymap -42 SIAPI_SWORD +keymap -43 SIAPI_SHOTGUN +keymap -44 SIAPI_SMG +keymap -45 SIAPI_FLAMER +keymap -46 SIAPI_PLASMA +keymap -47 SIAPI_ZAPPER +keymap -48 SIAPI_RIFLE +keymap -49 SIAPI_CORRODER +keymap -51 SIAPI_GRENADE +keymap -52 SIAPI_MINE +keymap -53 SIAPI_ROCKET +keymap -54 SIAPI_MINIGUN +keymap -55 SIAPI_JETSAW +keymap -56 SIAPI_MELEE keymap 8 BACKSPACE keymap 9 TAB diff --git a/config/setup.cfg b/config/setup.cfg index 558192910..7d911e9a2 100644 --- a/config/setup.cfg +++ b/config/setup.cfg @@ -156,6 +156,22 @@ bind SIAPI_AFFINITY [ affinity ] bind SIAPI_SCOREBOARD [ showscores ] bind SIAPI_CHANGE_LOADOUT [ gameui_player_show_loadout ] bind SIAPI_SUICIDE [ suicide ] +bind SIAPI_CLAW [ weapon 0 1] +bind SIAPI_PISTOL [ weapon 1 1] +bind SIAPI_SWORD [ weapon 2 1] +bind SIAPI_SHOTGUN [ weapon 3 1] +bind SIAPI_SMG [ weapon 4 1] +bind SIAPI_FLAMER [ weapon 5 1] +bind SIAPI_PLASMA [ weapon 6 1] +bind SIAPI_ZAPPER [ weapon 7 1] +bind SIAPI_RIFLE [ weapon 8 1] +bind SIAPI_CORRODER [ weapon 9 1] +bind SIAPI_GRENADE [ weapon 10 1] +bind SIAPI_MINE [ weapon 11 1] +bind SIAPI_ROCKET [ weapon 12 1] +bind SIAPI_MINIGUN [ weapon 13 1] +bind SIAPI_JETSAW [ weapon 14 1] +bind SIAPI_MELEE [ weapon 15 1] saytextcolour = 0; setpersist saytextcolour 1; setcomplete saytextcolour 1 getsaycolour = [ diff --git a/src/game/controller.cpp b/src/game/controller.cpp index 71cf0ebef..0c37a8d6e 100644 --- a/src/game/controller.cpp +++ b/src/game/controller.cpp @@ -12,7 +12,6 @@ #define DEF_ACTION_SET(x) InputActionSetHandle_t x##_handle = 0 #define DEF_ANALOG_ACTION(x) InputAnalogActionHandle_t x##_handle = 0 -#define DEF_DIGITAL_ACTION(x) class digital_action_state x #define SET_ACTION_SET(x) x##_handle = cdpi::steam::input->GetActionSetHandle(#x) #define SET_ANALOG_ACTION(x) x##_handle =cdpi::steam::input->GetAnalogActionHandle(#x) @@ -52,6 +51,22 @@ enum siapi_keycodes { SIAPI_SCOREBOARD = -37, SIAPI_SUICIDE = -38, SIAPI_MENU = -39, + SIAPI_CLAW = -40, + SIAPI_PISTOL = -41, + SIAPI_SWORD = -42, + SIAPI_SHOTGUN = -43, + SIAPI_SMG = -44, + SIAPI_FLAMER = -45, + SIAPI_PLASMA = -46, + SIAPI_ZAPPER = -47, + SIAPI_RIFLE = -48, + SIAPI_CORRODER = -49, + SIAPI_GRENADE = -51, + SIAPI_MINE = -52, + SIAPI_ROCKET = -53, + SIAPI_MINIGUN = -54, + SIAPI_JETSAW = -55, + SIAPI_MELEE = -56, // The menu code does not use the keymap system and doesn't allow you to // rebind those controls. Because of this, it is safe to 'pretend' to be // the other keys directly with no consequences. @@ -142,36 +157,53 @@ DEF_ACTION_SET(InGameControls); DEF_ANALOG_ACTION(move); DEF_ANALOG_ACTION(camera); -DEF_DIGITAL_ACTION(primary); -DEF_DIGITAL_ACTION(secondary); -DEF_DIGITAL_ACTION(reload); -DEF_DIGITAL_ACTION(use); -DEF_DIGITAL_ACTION(jump); -DEF_DIGITAL_ACTION(walk); -DEF_DIGITAL_ACTION(crouch); -DEF_DIGITAL_ACTION(special); -DEF_DIGITAL_ACTION(drop); -DEF_DIGITAL_ACTION(affinity); -DEF_DIGITAL_ACTION(dash); -DEF_DIGITAL_ACTION(next_weapon); -DEF_DIGITAL_ACTION(previous_weapon); -DEF_DIGITAL_ACTION(primary_weapon); -DEF_DIGITAL_ACTION(secondary_weapon); -DEF_DIGITAL_ACTION(wheel_select); -DEF_DIGITAL_ACTION(change_loadout); -DEF_DIGITAL_ACTION(scoreboard); -DEF_DIGITAL_ACTION(suicide); -DEF_DIGITAL_ACTION(menu); - -DEF_DIGITAL_ACTION(recenter_camera); +class digital_action_state primary; +class digital_action_state secondary; +class digital_action_state reload; +class digital_action_state use; +class digital_action_state jump; +class digital_action_state walk; +class digital_action_state crouch; +class digital_action_state special; +class digital_action_state drop; +class digital_action_state affinity; +class digital_action_state dash; +class digital_action_state next_weapon; +class digital_action_state previous_weapon; +class digital_action_state primary_weapon; +class digital_action_state secondary_weapon; +class digital_action_state claw; +class digital_action_state pistol; +class digital_action_state sword; +class digital_action_state shotgun; +class digital_action_state smg; +class digital_action_state flamer; +class digital_action_state plasma; +class digital_action_state zapper; +class digital_action_state rifle; +class digital_action_state corroder; +class digital_action_state grenade; +class digital_action_state mine; +class digital_action_state rocket; +class digital_action_state minigun; +class digital_action_state jetsaw; +class digital_action_state melee; +class digital_action_state wheel_select; +class digital_action_state change_loadout; +class digital_action_state scoreboard; +class digital_action_state suicide; +class digital_action_state menu; + +class digital_action_state recenter_camera; DEF_ACTION_SET(MenuControls); DEF_ANALOG_ACTION(menu_cursor); -DEF_DIGITAL_ACTION(menu_select); -DEF_DIGITAL_ACTION(menu_cancel); -DEF_DIGITAL_ACTION(menu_scroll_up); -DEF_DIGITAL_ACTION(menu_scroll_down); +class digital_action_state menu_select; +class digital_action_state menu_cancel; +class digital_action_state menu_scroll_up; +class digital_action_state menu_scroll_down; +DEF_ACTION_SET(EditingControls); void init_siapi_handles() { @@ -225,6 +257,54 @@ void init_siapi_handles() SET_DIGITAL_ACTION(secondary_weapon); secondary_weapon.keymap_id = SIAPI_SECONDARY_WEAPON; + SET_DIGITAL_ACTION(claw); + claw.keymap_id = SIAPI_CLAW; + + SET_DIGITAL_ACTION(pistol); + pistol.keymap_id = SIAPI_PISTOL; + + SET_DIGITAL_ACTION(sword); + sword.keymap_id = SIAPI_SWORD; + + SET_DIGITAL_ACTION(shotgun); + shotgun.keymap_id = SIAPI_SHOTGUN; + + SET_DIGITAL_ACTION(smg); + smg.keymap_id = SIAPI_SMG; + + SET_DIGITAL_ACTION(flamer); + flamer.keymap_id = SIAPI_FLAMER; + + SET_DIGITAL_ACTION(plasma); + plasma.keymap_id = SIAPI_PLASMA; + + SET_DIGITAL_ACTION(zapper); + zapper.keymap_id = SIAPI_ZAPPER; + + SET_DIGITAL_ACTION(rifle); + rifle.keymap_id = SIAPI_RIFLE; + + SET_DIGITAL_ACTION(corroder); + corroder.keymap_id = SIAPI_CORRODER; + + SET_DIGITAL_ACTION(grenade); + grenade.keymap_id = SIAPI_GRENADE; + + SET_DIGITAL_ACTION(mine); + mine.keymap_id = SIAPI_MINE; + + SET_DIGITAL_ACTION(rocket); + rocket.keymap_id = SIAPI_ROCKET; + + SET_DIGITAL_ACTION(minigun); + minigun.keymap_id = SIAPI_MINIGUN; + + SET_DIGITAL_ACTION(jetsaw); + jetsaw.keymap_id = SIAPI_JETSAW; + + SET_DIGITAL_ACTION(melee); + melee.keymap_id = SIAPI_MELEE; + SET_DIGITAL_ACTION(wheel_select); wheel_select.keymap_id = SIAPI_WHEEL_SELECT; @@ -358,6 +438,22 @@ void update_ingame_actions(int controlleridx) previous_weapon.ingame_process(controlleridx); primary_weapon.ingame_process(controlleridx); secondary_weapon.ingame_process(controlleridx); + claw.ingame_process(controlleridx); + pistol.ingame_process(controlleridx); + sword.ingame_process(controlleridx); + shotgun.ingame_process(controlleridx); + smg.ingame_process(controlleridx); + flamer.ingame_process(controlleridx); + plasma.ingame_process(controlleridx); + zapper.ingame_process(controlleridx); + rifle.ingame_process(controlleridx); + corroder.ingame_process(controlleridx); + grenade.ingame_process(controlleridx); + mine.ingame_process(controlleridx); + rocket.ingame_process(controlleridx); + minigun.ingame_process(controlleridx); + jetsaw.ingame_process(controlleridx); + melee.ingame_process(controlleridx); wheel_select.ingame_process(controlleridx); change_loadout.ingame_process(controlleridx); scoreboard.ingame_process(controlleridx); From 648814193807e7570348e98876a3dc57ea05b996 Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Sat, 7 Feb 2026 23:38:28 -0800 Subject: [PATCH 15/26] Add editing mode action set --- bin/amd64/game_actions_967460.vdf | 15 +++++++++++++++ src/game/controller.cpp | 22 +++++++++++++++++----- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/bin/amd64/game_actions_967460.vdf b/bin/amd64/game_actions_967460.vdf index 917508881..adfb8c987 100644 --- a/bin/amd64/game_actions_967460.vdf +++ b/bin/amd64/game_actions_967460.vdf @@ -85,6 +85,19 @@ "menu_scroll_down" "#Menu_ScrollDown" } } + "EditingControls" + { + "title" "#Set_Editing" + "StickPadGyro" + { + } + "AnalogTrigger" + { + } + "Button" + { + } + } } "localization" { @@ -139,6 +152,8 @@ "Menu_Cancel" "Cancel/Return" "Menu_ScrollUp" "Scroll Up" "Menu_ScrollDown" "Scroll Down" + + "Set_Editing" "Editing Mode Controls" } } } \ No newline at end of file diff --git a/src/game/controller.cpp b/src/game/controller.cpp index 0c37a8d6e..a8de4e9ea 100644 --- a/src/game/controller.cpp +++ b/src/game/controller.cpp @@ -336,6 +336,8 @@ void init_siapi_handles() SET_DIGITAL_ACTION(menu_scroll_down); menu_scroll_down.keymap_id = SIAPI_MENU_SCROLL_DOWN; + + SET_ACTION_SET(EditingControls); } void update_ingame_actions(int controlleridx); @@ -344,7 +346,7 @@ void update_menu_actions(int controlleridx); void update_from_controller() { // Initialize handles if needed - if(!InGameControls_handle) controller::init_siapi_handles(); + if(!(InGameControls_handle && MenuControls_handle && EditingControls_handle)) controller::init_siapi_handles(); // Steamworks ( https://partner.steamgames.com/doc/api/ISteamInput#RunFrame ) says that // > Synchronize API state with the latest Steam Controller inputs @@ -360,14 +362,24 @@ void update_from_controller() if (connected_count == 0) return; - for (int i = 0; i < connected_count; i++) + for (int i = 0; i < connected_count; i++) { + if (editmode) { + // TODO: We currently don't have SIAPI actions for + // editing mode, but we do provide an action set for + // convenience + cdpi::steam::input->ActivateActionSet(STEAM_INPUT_HANDLE_ALL_CONTROLLERS, EditingControls_handle); + continue; + } + if (hud::hasinput(true)) { cdpi::steam::input->ActivateActionSet(STEAM_INPUT_HANDLE_ALL_CONTROLLERS, MenuControls_handle); update_menu_actions(i); - } else { - cdpi::steam::input->ActivateActionSet(STEAM_INPUT_HANDLE_ALL_CONTROLLERS, InGameControls_handle); - update_ingame_actions(i); + continue; } + + cdpi::steam::input->ActivateActionSet(STEAM_INPUT_HANDLE_ALL_CONTROLLERS, InGameControls_handle); + update_ingame_actions(i); + } } void update_ingame_actions(int controlleridx) From 26619b9a58fb9440db839c7b1d92a30bae5c7dd4 Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Sat, 7 Feb 2026 23:43:03 -0800 Subject: [PATCH 16/26] Move handle init check to after controller count check --- src/game/controller.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/game/controller.cpp b/src/game/controller.cpp index a8de4e9ea..033050eda 100644 --- a/src/game/controller.cpp +++ b/src/game/controller.cpp @@ -345,9 +345,6 @@ void update_menu_actions(int controlleridx); void update_from_controller() { - // Initialize handles if needed - if(!(InGameControls_handle && MenuControls_handle && EditingControls_handle)) controller::init_siapi_handles(); - // Steamworks ( https://partner.steamgames.com/doc/api/ISteamInput#RunFrame ) says that // > Synchronize API state with the latest Steam Controller inputs // > available. This is performed automatically by @@ -362,6 +359,9 @@ void update_from_controller() if (connected_count == 0) return; + // Initialize handles if needed + if(!(InGameControls_handle && MenuControls_handle && EditingControls_handle)) controller::init_siapi_handles(); + for (int i = 0; i < connected_count; i++) { if (editmode) { // TODO: We currently don't have SIAPI actions for From 9b13b637fb9abe032d42a2d9c403acfe9d431bfc Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Sat, 7 Feb 2026 23:51:35 -0800 Subject: [PATCH 17/26] Add SIAPI action for 'last weapon' --- bin/amd64/game_actions_967460.vdf | 2 ++ config/keymap.cfg | 31 +++++++++++++------------- config/setup.cfg | 1 + src/game/controller.cpp | 36 ++++++++++++++++++------------- 4 files changed, 40 insertions(+), 30 deletions(-) diff --git a/bin/amd64/game_actions_967460.vdf b/bin/amd64/game_actions_967460.vdf index adfb8c987..e7866543b 100644 --- a/bin/amd64/game_actions_967460.vdf +++ b/bin/amd64/game_actions_967460.vdf @@ -36,6 +36,7 @@ "previous_weapon" "#Action_PreviousWeapon" "primary_weapon" "#Action_PrimaryWeapon" "secondary_weapon" "#Action_SecondaryWeapon" + "last_weapon" "#Action_LastWeapon" "claw" "#Action_Claw" "pistol" "#Action_Pistol" "sword" "#Action_Sword" @@ -122,6 +123,7 @@ "Action_PreviousWeapon" "Previous Weapon" "Action_PrimaryWeapon" "Primary Weapon" "Action_SecondaryWeapon" "Secondary Weapon" + "Action_LastWeapon" "Last Weapon" "Action_Claw" "Claw Attack" "Action_Pistol" "Sidearm Pistol" "Action_Sword" "Energy Sword" diff --git a/config/keymap.cfg b/config/keymap.cfg index 7da36bd5e..9a89e89b8 100644 --- a/config/keymap.cfg +++ b/config/keymap.cfg @@ -32,21 +32,22 @@ keymap -31 SIAPI_NEXT_WEAPON keymap -32 SIAPI_PREVIOUS_WEAPON keymap -33 SIAPI_PRIMARY_WEAPON keymap -34 SIAPI_SECONDARY_WEAPON -keymap -35 SIAPI_WHEEL_SELECT -keymap -36 SIAPI_CHANGE_LOADOUT -keymap -37 SIAPI_SCOREBOARD -keymap -38 SIAPI_SUICIDE -keymap -39 SIAPI_MENU -keymap -40 SIAPI_CLAW -keymap -41 SIAPI_PISTOL -keymap -42 SIAPI_SWORD -keymap -43 SIAPI_SHOTGUN -keymap -44 SIAPI_SMG -keymap -45 SIAPI_FLAMER -keymap -46 SIAPI_PLASMA -keymap -47 SIAPI_ZAPPER -keymap -48 SIAPI_RIFLE -keymap -49 SIAPI_CORRODER +keymap -35 SIAPI_LAST_WEAPON +keymap -36 SIAPI_WHEEL_SELECT +keymap -37 SIAPI_CHANGE_LOADOUT +keymap -38 SIAPI_SCOREBOARD +keymap -39 SIAPI_SUICIDE +keymap -40 SIAPI_MENU +keymap -41 SIAPI_CLAW +keymap -42 SIAPI_PISTOL +keymap -43 SIAPI_SWORD +keymap -44 SIAPI_SHOTGUN +keymap -45 SIAPI_SMG +keymap -46 SIAPI_FLAMER +keymap -47 SIAPI_PLASMA +keymap -48 SIAPI_ZAPPER +keymap -49 SIAPI_RIFLE +keymap -50 SIAPI_CORRODER keymap -51 SIAPI_GRENADE keymap -52 SIAPI_MINE keymap -53 SIAPI_ROCKET diff --git a/config/setup.cfg b/config/setup.cfg index 7d911e9a2..308ad5c48 100644 --- a/config/setup.cfg +++ b/config/setup.cfg @@ -142,6 +142,7 @@ bind SIAPI_NEXT_WEAPON [ universaldelta 1 ] bind SIAPI_PREVIOUS_WEAPON [ universaldelta -1 ] bind SIAPI_PRIMARY_WEAPON [ weapon (weapload 0) 1 ] bind SIAPI_SECONDARY_WEAPON [ weapon (weapload 1) 1 ] +bind SIAPI_LAST_WEAPON [ weapon (weapprev) 1 ] bind SIAPI_MENU [ uitoggle ] bind SIAPI_JUMP [ jump ] specbind SIAPI_JUMP [ specmodeswitch ] diff --git a/src/game/controller.cpp b/src/game/controller.cpp index 033050eda..a22b26290 100644 --- a/src/game/controller.cpp +++ b/src/game/controller.cpp @@ -46,21 +46,22 @@ enum siapi_keycodes { SIAPI_PREVIOUS_WEAPON = -32, SIAPI_PRIMARY_WEAPON = -33, SIAPI_SECONDARY_WEAPON = -34, - SIAPI_WHEEL_SELECT = -35, - SIAPI_CHANGE_LOADOUT = -36, - SIAPI_SCOREBOARD = -37, - SIAPI_SUICIDE = -38, - SIAPI_MENU = -39, - SIAPI_CLAW = -40, - SIAPI_PISTOL = -41, - SIAPI_SWORD = -42, - SIAPI_SHOTGUN = -43, - SIAPI_SMG = -44, - SIAPI_FLAMER = -45, - SIAPI_PLASMA = -46, - SIAPI_ZAPPER = -47, - SIAPI_RIFLE = -48, - SIAPI_CORRODER = -49, + SIAPI_LAST_WEAPON = -35, + SIAPI_WHEEL_SELECT = -36, + SIAPI_CHANGE_LOADOUT = -37, + SIAPI_SCOREBOARD = -38, + SIAPI_SUICIDE = -39, + SIAPI_MENU = -40, + SIAPI_CLAW = -41, + SIAPI_PISTOL = -42, + SIAPI_SWORD = -43, + SIAPI_SHOTGUN = -44, + SIAPI_SMG = -45, + SIAPI_FLAMER = -46, + SIAPI_PLASMA = -47, + SIAPI_ZAPPER = -48, + SIAPI_RIFLE = -49, + SIAPI_CORRODER = -50, SIAPI_GRENADE = -51, SIAPI_MINE = -52, SIAPI_ROCKET = -53, @@ -172,6 +173,7 @@ class digital_action_state next_weapon; class digital_action_state previous_weapon; class digital_action_state primary_weapon; class digital_action_state secondary_weapon; +class digital_action_state last_weapon; class digital_action_state claw; class digital_action_state pistol; class digital_action_state sword; @@ -257,6 +259,9 @@ void init_siapi_handles() SET_DIGITAL_ACTION(secondary_weapon); secondary_weapon.keymap_id = SIAPI_SECONDARY_WEAPON; + SET_DIGITAL_ACTION(last_weapon); + last_weapon.keymap_id = SIAPI_LAST_WEAPON; + SET_DIGITAL_ACTION(claw); claw.keymap_id = SIAPI_CLAW; @@ -450,6 +455,7 @@ void update_ingame_actions(int controlleridx) previous_weapon.ingame_process(controlleridx); primary_weapon.ingame_process(controlleridx); secondary_weapon.ingame_process(controlleridx); + last_weapon.ingame_process(controlleridx); claw.ingame_process(controlleridx); pistol.ingame_process(controlleridx); sword.ingame_process(controlleridx); From 32c6e9e1bcd55d2c3e8306357608137867bc8ce8 Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Wed, 11 Feb 2026 00:16:47 -0800 Subject: [PATCH 18/26] Add gameplay window style and use to sense in vs out of game menus --- config/setup.cfg | 2 +- config/ui/game/hud/pie.cfg | 1 + src/engine/ui.cpp | 26 ++++++++++++++++++++++++-- src/game/controller.cpp | 2 +- src/shared/iengine.h | 1 + 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/config/setup.cfg b/config/setup.cfg index 308ad5c48..178ef2bc2 100644 --- a/config/setup.cfg +++ b/config/setup.cfg @@ -137,7 +137,7 @@ bind SIAPI_PRIMARY [ primary ] specbind SIAPI_PRIMARY [ spectate 0 ] bind SIAPI_SECONDARY [ secondary ] specbind SIAPI_SECONDARY [ spectate 0 ] -bind SIAPI_WHEEL_SELECT [ game_hud_piemenu_open_weapsel_key ] // Currently broken +bind SIAPI_WHEEL_SELECT [ game_hud_piemenu_open_weapsel_key ] bind SIAPI_NEXT_WEAPON [ universaldelta 1 ] bind SIAPI_PREVIOUS_WEAPON [ universaldelta -1 ] bind SIAPI_PRIMARY_WEAPON [ weapon (weapload 0) 1 ] diff --git a/config/ui/game/hud/pie.cfg b/config/ui/game/hud/pie.cfg index 76cf0f124..4f1c27023 100644 --- a/config/ui/game/hud/pie.cfg +++ b/config/ui/game/hud/pie.cfg @@ -222,6 +222,7 @@ ui_game_hud_piemenu = [ ] newui "hud_piemenu" $SURFACE_FOREGROUND [ + uiwinstyle $WINSTYLE_GAMEPLAY uiallowinput 1 ui_game_hud_piemenu diff --git a/src/engine/ui.cpp b/src/engine/ui.cpp index 9279d32df..750e3c4f0 100644 --- a/src/engine/ui.cpp +++ b/src/engine/ui.cpp @@ -955,7 +955,7 @@ namespace UI } }; - #define WINSTYLE_ENUM(en, um) en(um, Normal, NORMAL) en(um, Tool Tip, TOOLTIP) en(um, Popup, POPUP) en(um, Crosshair, CROSSHAIR) en(um, Cursor, CURSOR) en(um, Max, MAX) + #define WINSTYLE_ENUM(en, um) en(um, Normal, NORMAL) en(um, Gameplay, GAMEPLAY) en(um, Tool Tip, TOOLTIP) en(um, Popup, POPUP) en(um, Crosshair, CROSSHAIR) en(um, Cursor, CURSOR) en(um, Max, MAX) ENUM_DLN(WINSTYLE); struct Window : Object @@ -2057,6 +2057,16 @@ namespace UI return false; } + bool menuisgameplay() + { + if(surfaceinput == type) loopwindows(w, + { + if(!w->visible || !checkexclusive(w) || w->winstyle >= WINSTYLE_CROSSHAIR) continue; + if(!(w->state&STATE_HIDDEN) && w->winstyle == WINSTYLE_GAMEPLAY) return true; + }); + return false; + } + const char *topname() { loopwindowsrev(w, @@ -2078,7 +2088,12 @@ namespace UI uiscale = 1; switch(w->winstyle) { - case WINSTYLE_NORMAL: default: break; + // Gameplay window style is a means for the controller code + // to know if a UI element is supposed to be used in-game + // (and so should continue to use in-game controls) or is a + // menu (and so should switch to menu controls); it + // shouldn't actually affect handling or rendering. + case WINSTYLE_GAMEPLAY: case WINSTYLE_NORMAL: default: break; case WINSTYLE_TOOLTIP: { w->setpos(getuicursorx() - w->w * getuicursorx(false), getuicursory() >= 0.5f ? getuicursory() - w->h - uitipoffset : getuicursory() + hud::cursorsize + uitipoffset); @@ -7295,6 +7310,13 @@ namespace UI return ret; } + bool menuisgameplay(int stype) + { + bool ret = false; + SWSURFACE(stype, if(surface->menuisgameplay()) ret = true); + return ret; + } + ICOMMANDV(0, uihasmenu, hasmenu()); ICOMMAND(0, uigetmenu, "ib", (int *pass, int *stype), intret(hasmenu(*pass != 0, *stype >= 0 ? *stype : -1))); diff --git a/src/game/controller.cpp b/src/game/controller.cpp index a22b26290..104d22bc2 100644 --- a/src/game/controller.cpp +++ b/src/game/controller.cpp @@ -376,7 +376,7 @@ void update_from_controller() continue; } - if (hud::hasinput(true)) { + if (UI::hasinput() && !UI::menuisgameplay()) { cdpi::steam::input->ActivateActionSet(STEAM_INPUT_HANDLE_ALL_CONTROLLERS, MenuControls_handle); update_menu_actions(i); continue; diff --git a/src/shared/iengine.h b/src/shared/iengine.h index 8a03109da..303e96a88 100644 --- a/src/shared/iengine.h +++ b/src/shared/iengine.h @@ -554,6 +554,7 @@ namespace UI extern bool uitest(const char *name, int stype = SURFACE_FOREGROUND, int param = -1); extern int hasinput(bool cursor = false, int stype = -1); extern bool hasmenu(bool pass = true, int stype = -1); + extern bool menuisgameplay(int stype = -1); extern bool keypress(int code, bool isdown); extern bool textinput(const char *str, int len); From cff533242eea8da299b5e24af92540d3fe96e6e9 Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Sat, 14 Feb 2026 09:56:47 -0800 Subject: [PATCH 19/26] Factor out common textkey fetch code and also use in SIAPI code --- src/engine/rendertext.cpp | 38 +++++++++++++++++++++++++++----------- src/engine/rendertext.h | 2 ++ src/game/controller.cpp | 33 +++++++++++++-------------------- 3 files changed, 42 insertions(+), 31 deletions(-) diff --git a/src/engine/rendertext.cpp b/src/engine/rendertext.cpp index 69c277f14..ec3637ed2 100644 --- a/src/engine/rendertext.cpp +++ b/src/engine/rendertext.cpp @@ -767,27 +767,43 @@ void text_boundsf(const char *str, float &width, float &height, float xpad, floa #undef TEXTCHAR } -vector textkeys; - -textkey *findtextkey(const char *str) +textkey *findtextkey_common(const char *str, vector textkeycache, const char *filename) { - // SIAPI actions have special handling because we can't cache them - if(controller::is_siapi_textkey(str)) return controller::get_siapi_textkey(str); - loopv(textkeys) if(!strcmp(textkeys[i]->name, str)) return textkeys[i]; + loopv(textkeycache) if(!strcmp(textkeycache[i]->name, str)) return textkeycache[i]; + static string key; - copystring(key, textkeyprefix); - int q = strlen(key); - concatstring(key, str); - for(int r = strlen(key); q < r; q++) key[q] = tolower(key[q]); + + // The controller code has a separate way of determining the filename, so + // take the passed filename if given + if (!filename) + { + copystring(key, textkeyprefix); + int q = strlen(key); + concatstring(key, str); + for(int r = strlen(key); q < r; q++) key[q] = tolower(key[q]); + } else { + copystring(key, filename); + } + textkey *t = new textkey; t->name = newstring(str); t->file = newstring(key); t->tex = textureload(t->file, 3, true, false); if(t->tex == notexture) t->tex = NULL; - textkeys.add(t); + textkeycache.add(t); return t; } +vector textkeys; + +textkey *findtextkey(const char *str) +{ + // SIAPI actions have special handling because we can't cache them + if(controller::is_siapi_textkey(str)) return controller::get_siapi_textkey(str); + return findtextkey_common(str, textkeys, NULL); + +} + struct tklookup { char *name; diff --git a/src/engine/rendertext.h b/src/engine/rendertext.h index 0280b7a52..b3661fed4 100644 --- a/src/engine/rendertext.h +++ b/src/engine/rendertext.h @@ -15,4 +15,6 @@ struct textkey DELETEA(file); } }; + +textkey *findtextkey_common(const char *str, vector textkeycache, const char *filename = NULL); #endif diff --git a/src/game/controller.cpp b/src/game/controller.cpp index 104d22bc2..55cef5f78 100644 --- a/src/game/controller.cpp +++ b/src/game/controller.cpp @@ -104,7 +104,6 @@ class digital_action_state int keymap_id = -1; // Should be more than one origin eventually! EInputActionOrigin origin = k_EInputActionOrigin_None; - textkey *tk = NULL; void update(int controlleridx) { @@ -530,16 +529,12 @@ digital_action_state *get_das_for_keymap_name(const char *str) EInputActionOrigin *origins = NULL; +vector textkeys; + textkey *get_siapi_textkey(const char *str) { digital_action_state *das = get_das_for_keymap_name(str); - if (!das->tk) { - das->tk = new textkey; - das->tk->file = NULL; // we don't use this here - das->tk->name = newstring(str); - } - // We have to check if the origin has changed, and if so, reload the texture if(!origins) origins = new EInputActionOrigin[STEAM_INPUT_MAX_ORIGINS]; // Have to clear the origins ourselves origins[0] = k_EInputActionOrigin_None; @@ -549,19 +544,17 @@ textkey *get_siapi_textkey(const char *str) das->handle, origins ); - if (origins[0] == k_EInputActionOrigin_None || origins[0] > k_EInputActionOrigin_MaximumPossibleValue) { - das->tk->tex = NULL; - } else if (origins[0] != das->origin) { - const char *siapi_origin_glyph = cdpi::steam::input->GetGlyphPNGForActionOrigin( - origins[0], - k_ESteamInputGlyphSize_Medium, - ESteamInputGlyphStyle_Dark - ); - das->tk->tex = textureload(siapi_origin_glyph, 3, true, false); - if(das->tk->tex == notexture) das->tk->tex = NULL; - } - das->origin = origins[0]; - return das->tk; + + const char *siapi_origin_glyph = cdpi::steam::input->GetGlyphPNGForActionOrigin( + origins[0], + k_ESteamInputGlyphSize_Medium, + ESteamInputGlyphStyle_Dark + ); + + char origin_enum_string[13]; + snprintf(origin_enum_string, 13, "origin_%d", origins[0]); + + return findtextkey_common(origin_enum_string, textkeys, siapi_origin_glyph); } ICOMMAND(0, showsiapibindpanel, "", (), { cdpi::steam::input->ShowBindingPanel(lastusedcontroller); }); From ffbbac3e59dbc8dc80ca45b6e022204891965810 Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Sat, 14 Feb 2026 11:50:46 -0800 Subject: [PATCH 20/26] Add multiple origins support for SIAPI actions --- src/engine/rendertext.cpp | 111 +++++++++++++++++++++++++------------- src/game/controller.cpp | 56 ++++++++++++------- src/game/controller.h | 2 +- 3 files changed, 114 insertions(+), 55 deletions(-) diff --git a/src/engine/rendertext.cpp b/src/engine/rendertext.cpp index ec3637ed2..77c1f5897 100644 --- a/src/engine/rendertext.cpp +++ b/src/engine/rendertext.cpp @@ -795,12 +795,22 @@ textkey *findtextkey_common(const char *str, vector textkeycache, con } vector textkeys; +vector _findtextkeys_container; -textkey *findtextkey(const char *str) +vector findtextkeys(const char *str) { - // SIAPI actions have special handling because we can't cache them - if(controller::is_siapi_textkey(str)) return controller::get_siapi_textkey(str); - return findtextkey_common(str, textkeys, NULL); + // SIAPI actions have special handling because there are several fundamental + // differences between SIAPI textkeys and KB/M textkeys + if(controller::is_siapi_textkey(str)) return controller::get_siapi_textkeys(str); + + textkey *tk = findtextkey_common(str, textkeys, NULL); + + // Should probably just arrange to have this vector be 1 long at + // initialization, but I don't know how to do this... + if(!_findtextkeys_container.capacity()) _findtextkeys_container.add(tk); + else _findtextkeys_container[0] = tk; + + return _findtextkeys_container; } @@ -859,18 +869,26 @@ float key_widthf(const char *str) continue; } if(i && i > skippedkeys && textkeyseps) width += text_widthf(" or "); + bool foundtextkey = false; if(textkeyimages) { - textkey *t = findtextkey(list[i]); - if(t && t->tex) + vector tks = findtextkeys(list[i]); + loopvj(tks) { - width += (t->tex->w*scale)/float(t->tex->h); - continue; + textkey *t = tks[j]; + if(t && t->tex) + { + width += (t->tex->w*scale)/float(t->tex->h); + foundtextkey = true; + } + if(j && textkeyseps) width += text_widthf(" or "); } - // fallback if not found } - defformatkey(keystr, list[i]); - width += text_widthf(keystr); + if(!foundtextkey) + { + defformatkey(keystr, list[i]); + width += text_widthf(keystr); + } } list.deletearrays(); return width; @@ -906,44 +924,65 @@ static float draw_key(Texture *&tex, const char *str, float sx, float sy, bvec4 } width += text_widthf(" or "); } + bool foundtextkey = false; if(textkeyimages) { - textkey *t = findtextkey(list[i]); - if(t && t->tex) + vector tks = findtextkeys(list[i]); + loopvj(tks) { - float sh = curfont->scale*curtextscale, h = sh*textkeyimagescale, w = (t->tex->w*h)/float(t->tex->h); - if(curfontpass) + textkey *t = tks[j]; + if(j && textkeyseps) + { + if(!curfontpass) + { + if(tex != oldtex) + { + xtraverts += gle::end(); + tex = oldtex; + settexture(tex); + } + draw_text(" or ", sx + width, sy, color.r, color.g, color.b, color.a, 0, -1, -1, 1); + } + width += text_widthf(" or "); + } + if(t && t->tex) { - if(tex != t->tex) + float sh = curfont->scale*curtextscale, h = sh*textkeyimagescale, w = (t->tex->w*h)/float(t->tex->h); + if(curfontpass) { - xtraverts += gle::end(); - tex = t->tex; - settexture(tex); + if(tex != t->tex) + { + xtraverts += gle::end(); + tex = t->tex; + settexture(tex); + } + float oh = h-sh, oy = sy-oh*0.5f; + textvert(sx + width, oy ); gle::attribf(0, 0); + textvert(sx + width + w, oy ); gle::attribf(1, 0); + textvert(sx + width + w, oy + h); gle::attribf(1, 1); + textvert(sx + width, oy + h); gle::attribf(0, 1); } - float oh = h-sh, oy = sy-oh*0.5f; - textvert(sx + width, oy ); gle::attribf(0, 0); - textvert(sx + width + w, oy ); gle::attribf(1, 0); - textvert(sx + width + w, oy + h); gle::attribf(1, 1); - textvert(sx + width, oy + h); gle::attribf(0, 1); + else wantfontpass = true; + width += w; + foundtextkey = true; } - else wantfontpass = true; - width += w; - continue; } - // fallback if not found } - defformatkey(keystr, list[i]); - if(!curfontpass) + if(!foundtextkey) { - if(tex != oldtex) + defformatkey(keystr, list[i]); + if(!curfontpass) { - xtraverts += gle::end(); - tex = oldtex; - settexture(tex); + if(tex != oldtex) + { + xtraverts += gle::end(); + tex = oldtex; + settexture(tex); + } + draw_text(keystr, sx + width, sy, color.r, color.g, color.b, color.a, 0, -1, -1, 1); } - draw_text(keystr, sx + width, sy, color.r, color.g, color.b, color.a, 0, -1, -1, 1); + width += text_widthf(keystr); } - width += text_widthf(keystr); } list.deletearrays(); return width; diff --git a/src/game/controller.cpp b/src/game/controller.cpp index 55cef5f78..29f41359e 100644 --- a/src/game/controller.cpp +++ b/src/game/controller.cpp @@ -206,6 +206,9 @@ class digital_action_state menu_scroll_down; DEF_ACTION_SET(EditingControls); +// For returning SIAPI glyphs as a list +vector textkeyvec; + void init_siapi_handles() { SET_ACTION_SET(InGameControls); @@ -342,6 +345,9 @@ void init_siapi_handles() menu_scroll_down.keymap_id = SIAPI_MENU_SCROLL_DOWN; SET_ACTION_SET(EditingControls); + + // Also initialize textkey glyph return buffer to its maximum size + textkeyvec.growbuf(STEAM_INPUT_MAX_ORIGINS); } void update_ingame_actions(int controlleridx); @@ -531,30 +537,44 @@ EInputActionOrigin *origins = NULL; vector textkeys; -textkey *get_siapi_textkey(const char *str) +vector get_siapi_textkeys(const char *str) { digital_action_state *das = get_das_for_keymap_name(str); if(!origins) origins = new EInputActionOrigin[STEAM_INPUT_MAX_ORIGINS]; - // Have to clear the origins ourselves - origins[0] = k_EInputActionOrigin_None; - cdpi::steam::input->GetDigitalActionOrigins( - lastusedcontroller, - hud::hasinput(true) ? MenuControls_handle : InGameControls_handle, - das->handle, - origins - ); - const char *siapi_origin_glyph = cdpi::steam::input->GetGlyphPNGForActionOrigin( - origins[0], - k_ESteamInputGlyphSize_Medium, - ESteamInputGlyphStyle_Dark + // We have to clear the buffer ourselves between calls + memset(origins, 0, STEAM_INPUT_MAX_ORIGINS * sizeof(EInputActionOrigin)); + cdpi::steam::input->GetDigitalActionOrigins( + lastusedcontroller, + hud::hasinput(true) ? MenuControls_handle : InGameControls_handle, + das->handle, + origins ); - char origin_enum_string[13]; - snprintf(origin_enum_string, 13, "origin_%d", origins[0]); - - return findtextkey_common(origin_enum_string, textkeys, siapi_origin_glyph); + for (int i = 0; i < STEAM_INPUT_MAX_ORIGINS; i++) + { + if (origins[i] == k_EInputActionOrigin_None + || origins[i] > k_EInputActionOrigin_MaximumPossibleValue) { + // textkeyvec is always STEAM_INPUT_MAX_ORIGINS items long, + // but we adjust the reported length so that the draw code + // doesn't try to draw origins we don't actually have + textkeyvec.setsize(i); + break; + } + const char *siapi_origin_glyph = cdpi::steam::input->GetGlyphPNGForActionOrigin( + origins[i], + k_ESteamInputGlyphSize_Medium, + ESteamInputGlyphStyle_Dark + ); + + char origin_enum_string[13]; + snprintf(origin_enum_string, 13, "origin_%d", origins[0]); + + textkeyvec[i] = findtextkey_common(origin_enum_string, textkeys, siapi_origin_glyph); + } + + return textkeyvec; } ICOMMAND(0, showsiapibindpanel, "", (), { cdpi::steam::input->ShowBindingPanel(lastusedcontroller); }); @@ -569,7 +589,7 @@ bool is_siapi_textkey(const char *str) return false; } -textkey *get_siapi_textkey(const char *str) +vector get_siapi_textkeys(const char *str) { return NULL; } diff --git a/src/game/controller.h b/src/game/controller.h index 3f972dc42..517939cf4 100644 --- a/src/game/controller.h +++ b/src/game/controller.h @@ -9,6 +9,6 @@ namespace controller extern bool lastmovementwaskeyboard; extern void update_from_controller(); extern bool is_siapi_textkey(const char *str); - extern textkey *get_siapi_textkey(const char *str); + extern vector get_siapi_textkeys(const char *str); } #endif From 2e7aa7fa03ce5d417e9ae32b8c368d5adf6f8141 Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Sat, 14 Feb 2026 13:04:08 -0800 Subject: [PATCH 21/26] Add joystick-like mode for pie menu cursor Inspiration and encouragement courtesy of Mennenth --- bin/amd64/game_actions_967460.vdf | 6 ++++++ config/ui/game/hud/pie.cfg | 2 +- src/engine/ui.cpp | 34 +++++++++++++++++++++++-------- src/game/controller.cpp | 28 +++++++++++++++++++++++++ src/shared/iengine.h | 1 + 5 files changed, 62 insertions(+), 9 deletions(-) diff --git a/bin/amd64/game_actions_967460.vdf b/bin/amd64/game_actions_967460.vdf index e7866543b..d6e339b14 100644 --- a/bin/amd64/game_actions_967460.vdf +++ b/bin/amd64/game_actions_967460.vdf @@ -17,6 +17,11 @@ "title" "#Action_Camera" "input_mode" "absolute_mouse" } + "pie_cursor" + { + "title" "#Action_PieCursor" + "input_mode" "joystick_move" + } } "Button" { @@ -108,6 +113,7 @@ "Action_Move" "Move" "Action_Camera" "Camera" + "Action_PieCursor" "Pie Cursor" "Action_Primary" "Primary Fire" "Action_Secondary" "Secondary Fire" "Action_Reload" "Reload" diff --git a/config/ui/game/hud/pie.cfg b/config/ui/game/hud/pie.cfg index 4f1c27023..dcc1afd64 100644 --- a/config/ui/game/hud/pie.cfg +++ b/config/ui/game/hud/pie.cfg @@ -222,7 +222,7 @@ ui_game_hud_piemenu = [ ] newui "hud_piemenu" $SURFACE_FOREGROUND [ - uiwinstyle $WINSTYLE_GAMEPLAY + uiwinstyle $WINSTYLE_PIE uiallowinput 1 ui_game_hud_piemenu diff --git a/src/engine/ui.cpp b/src/engine/ui.cpp index 750e3c4f0..51b43a2ac 100644 --- a/src/engine/ui.cpp +++ b/src/engine/ui.cpp @@ -955,7 +955,7 @@ namespace UI } }; - #define WINSTYLE_ENUM(en, um) en(um, Normal, NORMAL) en(um, Gameplay, GAMEPLAY) en(um, Tool Tip, TOOLTIP) en(um, Popup, POPUP) en(um, Crosshair, CROSSHAIR) en(um, Cursor, CURSOR) en(um, Max, MAX) + #define WINSTYLE_ENUM(en, um) en(um, Normal, NORMAL) en(um, Pie, PIE) en(um, Gameplay, GAMEPLAY) en(um, Tool Tip, TOOLTIP) en(um, Popup, POPUP) en(um, Crosshair, CROSSHAIR) en(um, Cursor, CURSOR) en(um, Max, MAX) ENUM_DLN(WINSTYLE); struct Window : Object @@ -2062,7 +2062,17 @@ namespace UI if(surfaceinput == type) loopwindows(w, { if(!w->visible || !checkexclusive(w) || w->winstyle >= WINSTYLE_CROSSHAIR) continue; - if(!(w->state&STATE_HIDDEN) && w->winstyle == WINSTYLE_GAMEPLAY) return true; + if(!(w->state&STATE_HIDDEN) && (w->winstyle == WINSTYLE_PIE || w->winstyle == WINSTYLE_GAMEPLAY)) return true; + }); + return false; + } + + bool menuispie() + { + if(surfaceinput == type) loopwindows(w, + { + if(!w->visible || !checkexclusive(w) || w->winstyle >= WINSTYLE_CROSSHAIR) continue; + if(!(w->state&STATE_HIDDEN) && w->winstyle == WINSTYLE_PIE) return true; }); return false; } @@ -2088,12 +2098,13 @@ namespace UI uiscale = 1; switch(w->winstyle) { - // Gameplay window style is a means for the controller code - // to know if a UI element is supposed to be used in-game - // (and so should continue to use in-game controls) or is a - // menu (and so should switch to menu controls); it - // shouldn't actually affect handling or rendering. - case WINSTYLE_GAMEPLAY: case WINSTYLE_NORMAL: default: break; + // Pie and gameplay window style are means for the + // controller code to know if a UI element is supposed to be + // used in-game (and so should continue to use in-game + // controls) or is a menu (and so should switch to menu + // controls); it shouldn't actually affect handling or + // rendering. + case WINSTYLE_PIE: case WINSTYLE_GAMEPLAY: case WINSTYLE_NORMAL: default: break; case WINSTYLE_TOOLTIP: { w->setpos(getuicursorx() - w->w * getuicursorx(false), getuicursory() >= 0.5f ? getuicursory() - w->h - uitipoffset : getuicursory() + hud::cursorsize + uitipoffset); @@ -7317,6 +7328,13 @@ namespace UI return ret; } + bool menuispie(int stype) + { + bool ret = false; + SWSURFACE(stype, if(surface->menuispie()) ret = true); + return ret; + } + ICOMMANDV(0, uihasmenu, hasmenu()); ICOMMAND(0, uigetmenu, "ib", (int *pass, int *stype), intret(hasmenu(*pass != 0, *stype >= 0 ? *stype : -1))); diff --git a/src/game/controller.cpp b/src/game/controller.cpp index 29f41359e..654638b7a 100644 --- a/src/game/controller.cpp +++ b/src/game/controller.cpp @@ -156,6 +156,7 @@ DEF_ACTION_SET(InGameControls); DEF_ANALOG_ACTION(move); DEF_ANALOG_ACTION(camera); +DEF_ANALOG_ACTION(pie_cursor); class digital_action_state primary; class digital_action_state secondary; @@ -215,6 +216,7 @@ void init_siapi_handles() SET_ANALOG_ACTION(move); SET_ANALOG_ACTION(camera); + SET_ANALOG_ACTION(pie_cursor); SET_DIGITAL_ACTION(primary); primary.keymap_id = SIAPI_PRIMARY; @@ -443,6 +445,32 @@ void update_ingame_actions(int controlleridx) lastinputwassiapi = true; lastusedcontroller = controllers[controlleridx]; } + + if (UI::menuispie()) { + // For pie menus (like the weapon select wheel), we have + // a special joystick-like mode that's also available to + // use. It will override the standard camera action if + // in use (good for not needing to disable a gyro, for + // example). The expectation is that the controller + // config will modeshift the stick/pad when a button + // bound to a pie menu is hit. + InputAnalogActionData_t pie_pos = cdpi::steam::input->GetAnalogActionData( + controllers[controlleridx], + pie_cursor_handle + ); + + if (pie_pos.x != 0.0f || pie_pos.y != 0.0f) { + resetcursor(true, true); + game::mousemove( + // screenh is not a typo + pie_pos.x * (screenh / 4), + -pie_pos.y * (screenh / 4), + 0, 0, + screenw, screenh, + true + ); + } + } } primary.ingame_process(controlleridx); diff --git a/src/shared/iengine.h b/src/shared/iengine.h index 303e96a88..0c43a2031 100644 --- a/src/shared/iengine.h +++ b/src/shared/iengine.h @@ -555,6 +555,7 @@ namespace UI extern int hasinput(bool cursor = false, int stype = -1); extern bool hasmenu(bool pass = true, int stype = -1); extern bool menuisgameplay(int stype = -1); + extern bool menuispie(int stype = -1); extern bool keypress(int code, bool isdown); extern bool textinput(const char *str, int len); From dcb8364fb5ec7b09db68e4848202f8345d7c0e36 Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Sat, 14 Feb 2026 13:27:15 -0800 Subject: [PATCH 22/26] Kill tabs in files that already existed --- config/ui/game/settings.cfg | 24 +++++++++--------- src/engine/cdpi.cpp | 8 +++--- src/engine/engine.h | 4 +-- src/engine/main.cpp | 8 +++--- src/engine/rendertext.cpp | 50 ++++++++++++++++++------------------- src/game/game.cpp | 6 ++--- 6 files changed, 50 insertions(+), 50 deletions(-) diff --git a/config/ui/game/settings.cfg b/config/ui/game/settings.cfg index c53ef1527..a86c15ed4 100644 --- a/config/ui/game/settings.cfg +++ b/config/ui/game/settings.cfg @@ -429,7 +429,7 @@ ui_gameui_settings_controls_dims = 0.72 ] ] - uivlist 0.025 [ + uivlist 0.025 [ ui_gameui_group [ uistyle clampx @@ -438,12 +438,12 @@ ui_gameui_settings_controls_dims = 0.72 uicolourtext "Type" 0xaaaaaa ui_gameui_switch textkeyimagepreference [ - p_options = [ - "Automatic" - "Always Keyboard/Mouse" - "Always Controller" - "Both" - ] + p_options = [ + "Automatic" + "Always Keyboard/Mouse" + "Always Controller" + "Both" + ] p_tip = "Which device icons to show in text" p_id = #(gameui_get_id switch) ] @@ -451,11 +451,11 @@ ui_gameui_settings_controls_dims = 0.72 ] [ p_label = "Input Icons" ] - ui_gameui_button [ - p_label = "Open Controller Configurator" - p_on_click = [ showsiapibindpanel ] - p_id = #(gameui_get_id button) - ] + ui_gameui_button [ + p_label = "Open Controller Configurator" + p_on_click = [ showsiapibindpanel ] + p_id = #(gameui_get_id button) + ] ] ui_gameui_vscrollarea [ diff --git a/src/engine/cdpi.cpp b/src/engine/cdpi.cpp index b9044ce95..f362d3459 100644 --- a/src/engine/cdpi.cpp +++ b/src/engine/cdpi.cpp @@ -40,7 +40,7 @@ namespace cdpi ISteamUserStats *stats = NULL; ISteamClient *client = NULL, *sclient = NULL; ISteamGameServer *serv = NULL; - ISteamInput *input = NULL; + ISteamInput *input = NULL; HSteamPipe umpipe = 0, smpipe = 0; HSteamUser uupipe = 0, supipe = 0; HAuthTicket authticket = k_HAuthTicketInvalid; @@ -185,9 +185,9 @@ namespace cdpi if(!friends) { conoutf(colourred, "Failed to get Steam friends interface."); cleanup(SWCLIENT); return true; } stats = (ISteamUserStats *)SteamAPI_ISteamClient_GetISteamUserStats(client, uupipe, umpipe, STEAMUSERSTATS_INTERFACE_VERSION); if(!stats) { conoutf(colourred, "Failed to get Steam stats interface."); cleanup(SWCLIENT); return true; } - input = (ISteamInput *)SteamAPI_ISteamClient_GetISteamInput(client, uupipe, umpipe, STEAMINPUT_INTERFACE_VERSION); - if (!input) { conoutf(colourred, "Failed to get Steam Input interface."); cleanup(SWCLIENT); return true; } - input->Init(false); + input = (ISteamInput *)SteamAPI_ISteamClient_GetISteamInput(client, uupipe, umpipe, STEAMINPUT_INTERFACE_VERSION); + if (!input) { conoutf(colourred, "Failed to get Steam Input interface."); cleanup(SWCLIENT); return true; } + input->Init(false); const char *name = SteamAPI_ISteamFriends_GetPersonaName(friends); if(name && *name) diff --git a/src/engine/engine.h b/src/engine/engine.h index 21dbaebf3..b777d7729 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -52,8 +52,8 @@ namespace cdpi namespace steam { extern char *steamusername, *steamuserid, *steamserverid; - #if defined(USE_STEAM) - extern ISteamInput *input; + #if defined(USE_STEAM) + extern ISteamInput *input; #endif extern bool clientready(); diff --git a/src/engine/main.cpp b/src/engine/main.cpp index 77343014e..4d412c908 100644 --- a/src/engine/main.cpp +++ b/src/engine/main.cpp @@ -738,10 +738,10 @@ void checkinput() case SDL_KEYDOWN: case SDL_KEYUP: if(keyrepeatmask || !event.key.repeat) - { + { controller::lastinputwassiapi = false; processkey(event.key.keysym.sym, event.key.state==SDL_PRESSED); - } + } break; case SDL_WINDOWEVENT: @@ -811,12 +811,12 @@ void checkinput() int button = event.button.button; if(button >= 6) button += 4; // skip mousewheel X (-4,-5) & Y (-8, 9) else if(button >= 4) button += 2; // skip mousewheel X (-4,-5) - controller::lastinputwassiapi = false; + controller::lastinputwassiapi = false; processkey(-button, event.button.state==SDL_PRESSED); break; } case SDL_MOUSEWHEEL: - controller::lastinputwassiapi = false; + controller::lastinputwassiapi = false; if(event.wheel.y > 0) { processkey(-4, true); processkey(-4, false); } else if(event.wheel.y < 0) { processkey(-5, true); processkey(-5, false); } else if(event.wheel.x > 0) { processkey(-8, true); processkey(-8, false); } diff --git a/src/engine/rendertext.cpp b/src/engine/rendertext.cpp index 77c1f5897..71ce275ea 100644 --- a/src/engine/rendertext.cpp +++ b/src/engine/rendertext.cpp @@ -3,10 +3,10 @@ #include "controller.h" enum textkeyimagetype { - tkip_automatic, // Figure out which glyphs to show based on last input - tkip_kbm, // Always show keyboard/mouse glyphs - tkip_controller, // Always show controller glyphs - tkip_both, // Always show *both* keyboard/mouse and controller glyphs + tkip_automatic, // Figure out which glyphs to show based on last input + tkip_kbm, // Always show keyboard/mouse glyphs + tkip_controller, // Always show controller glyphs + tkip_both, // Always show *both* keyboard/mouse and controller glyphs }; VARF(IDF_PERSIST, textsupersample, 0, 1, 2, initwarning("Text Supersampling", INIT_LOAD, CHANGE_SHADERS)); @@ -46,17 +46,17 @@ bool wantfontpass = false; bool shouldkeepkey(const char *str) { - bool is_siapi_textkey = controller::is_siapi_textkey(str); - switch (textkeyimagepreference) { - case tkip_automatic: - return controller::lastinputwassiapi ? is_siapi_textkey : !is_siapi_textkey; - case tkip_kbm: - return !is_siapi_textkey; - case tkip_controller: - return is_siapi_textkey; - case tkip_both: - return true; - }; + bool is_siapi_textkey = controller::is_siapi_textkey(str); + switch (textkeyimagepreference) { + case tkip_automatic: + return controller::lastinputwassiapi ? is_siapi_textkey : !is_siapi_textkey; + case tkip_kbm: + return !is_siapi_textkey; + case tkip_controller: + return is_siapi_textkey; + case tkip_both: + return true; + }; } void fontscale(float *scale) @@ -863,11 +863,11 @@ float key_widthf(const char *str) int skippedkeys = 0; loopv(list) { - if(!shouldkeepkey(list[i])) - { - skippedkeys++; - continue; - } + if(!shouldkeepkey(list[i])) + { + skippedkeys++; + continue; + } if(i && i > skippedkeys && textkeyseps) width += text_widthf(" or "); bool foundtextkey = false; if(textkeyimages) @@ -905,11 +905,11 @@ static float draw_key(Texture *&tex, const char *str, float sx, float sy, bvec4 int skippedkeys = 0; loopv(list) { - if(!shouldkeepkey(list[i])) - { - skippedkeys++; - continue; - } + if(!shouldkeepkey(list[i])) + { + skippedkeys++; + continue; + } if(i && i > skippedkeys && textkeyseps) { if(!curfontpass) diff --git a/src/game/game.cpp b/src/game/game.cpp index a5fc3d214..cf15a5db1 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -3133,10 +3133,10 @@ namespace game void resetplayerpitch() { - if(!gs_waiting(gamestate) && (mouseoverride&1 || (!mouseoverride && !tvmode()))) + if(!gs_waiting(gamestate) && (mouseoverride&1 || (!mouseoverride && !tvmode()))) { - if((!gs_playing(gamestate) || player1->state >= CS_SPECTATOR && (focus == player1 || followaim()))) return; - if(allowmove(player1)) player1->pitch = 0.0f; + if((!gs_playing(gamestate) || player1->state >= CS_SPECTATOR && (focus == player1 || followaim()))) return; + if(allowmove(player1)) player1->pitch = 0.0f; } } From 5d08d26d9368a2d533256e0b0dc09b51ad32727c Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Sat, 14 Feb 2026 13:37:32 -0800 Subject: [PATCH 23/26] Format more like the Romans do --- src/game/controller.cpp | 815 ++++++++++++++++++++-------------------- src/game/controller.h | 10 +- 2 files changed, 416 insertions(+), 409 deletions(-) diff --git a/src/game/controller.cpp b/src/game/controller.cpp index 654638b7a..b2e5359d6 100644 --- a/src/game/controller.cpp +++ b/src/game/controller.cpp @@ -31,50 +31,50 @@ bool lastmovementwaskeyboard = true; // keymap codes to nice names - keep this in sync with keymaps.cfg enum siapi_keycodes { - SIAPI_PRIMARY = -20, - SIAPI_SECONDARY = -21, - SIAPI_RELOAD = -22, - SIAPI_USE = -23, - SIAPI_JUMP = -24, - SIAPI_WALK = -25, - SIAPI_CROUCH = -26, - SIAPI_SPECIAL = -27, - SIAPI_DROP = -28, - SIAPI_AFFINITY = -29, - SIAPI_DASH = -30, - SIAPI_NEXT_WEAPON = -31, - SIAPI_PREVIOUS_WEAPON = -32, - SIAPI_PRIMARY_WEAPON = -33, - SIAPI_SECONDARY_WEAPON = -34, - SIAPI_LAST_WEAPON = -35, - SIAPI_WHEEL_SELECT = -36, - SIAPI_CHANGE_LOADOUT = -37, - SIAPI_SCOREBOARD = -38, - SIAPI_SUICIDE = -39, - SIAPI_MENU = -40, - SIAPI_CLAW = -41, - SIAPI_PISTOL = -42, - SIAPI_SWORD = -43, - SIAPI_SHOTGUN = -44, - SIAPI_SMG = -45, - SIAPI_FLAMER = -46, - SIAPI_PLASMA = -47, - SIAPI_ZAPPER = -48, - SIAPI_RIFLE = -49, - SIAPI_CORRODER = -50, - SIAPI_GRENADE = -51, - SIAPI_MINE = -52, - SIAPI_ROCKET = -53, - SIAPI_MINIGUN = -54, - SIAPI_JETSAW = -55, - SIAPI_MELEE = -56, - // The menu code does not use the keymap system and doesn't allow you to - // rebind those controls. Because of this, it is safe to 'pretend' to be - // the other keys directly with no consequences. - SIAPI_MENU_SELECT = -1, // left mouse - SIAPI_MENU_CANCEL = -2, // right mouse - SIAPI_MENU_SCROLL_UP = -4, // scroll wheel -Y - SIAPI_MENU_SCROLL_DOWN = -5, // scroll wheel +Y + SIAPI_PRIMARY = -20, + SIAPI_SECONDARY = -21, + SIAPI_RELOAD = -22, + SIAPI_USE = -23, + SIAPI_JUMP = -24, + SIAPI_WALK = -25, + SIAPI_CROUCH = -26, + SIAPI_SPECIAL = -27, + SIAPI_DROP = -28, + SIAPI_AFFINITY = -29, + SIAPI_DASH = -30, + SIAPI_NEXT_WEAPON = -31, + SIAPI_PREVIOUS_WEAPON = -32, + SIAPI_PRIMARY_WEAPON = -33, + SIAPI_SECONDARY_WEAPON = -34, + SIAPI_LAST_WEAPON = -35, + SIAPI_WHEEL_SELECT = -36, + SIAPI_CHANGE_LOADOUT = -37, + SIAPI_SCOREBOARD = -38, + SIAPI_SUICIDE = -39, + SIAPI_MENU = -40, + SIAPI_CLAW = -41, + SIAPI_PISTOL = -42, + SIAPI_SWORD = -43, + SIAPI_SHOTGUN = -44, + SIAPI_SMG = -45, + SIAPI_FLAMER = -46, + SIAPI_PLASMA = -47, + SIAPI_ZAPPER = -48, + SIAPI_RIFLE = -49, + SIAPI_CORRODER = -50, + SIAPI_GRENADE = -51, + SIAPI_MINE = -52, + SIAPI_ROCKET = -53, + SIAPI_MINIGUN = -54, + SIAPI_JETSAW = -55, + SIAPI_MELEE = -56, + // The menu code does not use the keymap system and doesn't allow you to + // rebind those controls. Because of this, it is safe to 'pretend' to be + // the other keys directly with no consequences. + SIAPI_MENU_SELECT = -1, // left mouse + SIAPI_MENU_CANCEL = -2, // right mouse + SIAPI_MENU_SCROLL_UP = -4, // scroll wheel -Y + SIAPI_MENU_SCROLL_DOWN = -5, // scroll wheel +Y }; // This current controller implementation depends on Steam Input and is not @@ -86,70 +86,67 @@ InputHandle_t lastusedcontroller = 0; bool get_digital_action_state(int controlleridx, int siapi_digital_handle) { - InputDigitalActionData_t data = cdpi::steam::input->GetDigitalActionData(controllers[controlleridx], siapi_digital_handle); - if (data.bState) { - lastinputwassiapi = true; - lastusedcontroller = controllers[controlleridx]; - } - return data.bState; + InputDigitalActionData_t data = cdpi::steam::input->GetDigitalActionData(controllers[controlleridx], siapi_digital_handle); + if(data.bState) + { + lastinputwassiapi = true; + lastusedcontroller = controllers[controlleridx]; + } + return data.bState; } class digital_action_state { - bool input_last_frame[STEAM_INPUT_MAX_COUNT]; - bool input_this_frame[STEAM_INPUT_MAX_COUNT]; + bool input_last_frame[STEAM_INPUT_MAX_COUNT]; + bool input_this_frame[STEAM_INPUT_MAX_COUNT]; public: - InputDigitalActionHandle_t handle = -1; - int keymap_id = -1; - // Should be more than one origin eventually! - EInputActionOrigin origin = k_EInputActionOrigin_None; - - void update(int controlleridx) - { - this->input_last_frame[controlleridx] = this->input_this_frame[controlleridx]; - this->input_this_frame[controlleridx] = get_digital_action_state(controlleridx, this->handle); - } - - bool pressed(int controlleridx) - { - return this->input_this_frame[controlleridx]; - } - - bool released(int controlleridx) - { - return !this->input_this_frame[controlleridx]; - } - - bool just_pressed(int controlleridx) - { - return this->input_this_frame[controlleridx] && !this->input_last_frame[controlleridx]; - } - - bool just_released(int controlleridx) - { - return !this->input_this_frame[controlleridx] && this->input_last_frame[controlleridx]; - } - - void ingame_process(int controlleridx) - { - this->update(controlleridx); - - if (this->just_pressed(controlleridx)) - processkey(this->keymap_id, true); - else if (this->just_released(controlleridx)) - processkey(this->keymap_id, false); - } - - void menu_process(int controlleridx) - { - this->update(controlleridx); - - if (this->just_pressed(controlleridx)) - UI::keypress(this->keymap_id, true); - else if (this->just_released(controlleridx)) - UI::keypress(this->keymap_id, false); - } + InputDigitalActionHandle_t handle = -1; + int keymap_id = -1; + // Should be more than one origin eventually! + EInputActionOrigin origin = k_EInputActionOrigin_None; + + void update(int controlleridx) + { + this->input_last_frame[controlleridx] = this->input_this_frame[controlleridx]; + this->input_this_frame[controlleridx] = get_digital_action_state(controlleridx, this->handle); + } + + bool pressed(int controlleridx) + { + return this->input_this_frame[controlleridx]; + } + + bool released(int controlleridx) + { + return !this->input_this_frame[controlleridx]; + } + + bool just_pressed(int controlleridx) + { + return this->input_this_frame[controlleridx] && !this->input_last_frame[controlleridx]; + } + + bool just_released(int controlleridx) + { + return !this->input_this_frame[controlleridx] && this->input_last_frame[controlleridx]; + } + + void ingame_process(int controlleridx) + { + this->update(controlleridx); + + if(this->just_pressed(controlleridx)) processkey(this->keymap_id, true); + else if(this->just_released(controlleridx)) processkey(this->keymap_id, false); + } + + void menu_process(int controlleridx) + { + this->update(controlleridx); + + if(this->just_pressed(controlleridx)) UI::keypress(this->keymap_id, true); + else if(this->just_released(controlleridx)) UI::keypress(this->keymap_id, false); + } }; DEF_ACTION_SET(InGameControls); @@ -212,144 +209,144 @@ vector textkeyvec; void init_siapi_handles() { - SET_ACTION_SET(InGameControls); + SET_ACTION_SET(InGameControls); - SET_ANALOG_ACTION(move); - SET_ANALOG_ACTION(camera); - SET_ANALOG_ACTION(pie_cursor); + SET_ANALOG_ACTION(move); + SET_ANALOG_ACTION(camera); + SET_ANALOG_ACTION(pie_cursor); - SET_DIGITAL_ACTION(primary); - primary.keymap_id = SIAPI_PRIMARY; + SET_DIGITAL_ACTION(primary); + primary.keymap_id = SIAPI_PRIMARY; - SET_DIGITAL_ACTION(secondary); - secondary.keymap_id = SIAPI_SECONDARY; + SET_DIGITAL_ACTION(secondary); + secondary.keymap_id = SIAPI_SECONDARY; - SET_DIGITAL_ACTION(reload); - reload.keymap_id = SIAPI_RELOAD; + SET_DIGITAL_ACTION(reload); + reload.keymap_id = SIAPI_RELOAD; - SET_DIGITAL_ACTION(use); - use.keymap_id = SIAPI_USE; + SET_DIGITAL_ACTION(use); + use.keymap_id = SIAPI_USE; - SET_DIGITAL_ACTION(jump); - jump.keymap_id = SIAPI_JUMP; + SET_DIGITAL_ACTION(jump); + jump.keymap_id = SIAPI_JUMP; - SET_DIGITAL_ACTION(walk); - walk.keymap_id = SIAPI_WALK; + SET_DIGITAL_ACTION(walk); + walk.keymap_id = SIAPI_WALK; - SET_DIGITAL_ACTION(crouch); - crouch.keymap_id = SIAPI_CROUCH; + SET_DIGITAL_ACTION(crouch); + crouch.keymap_id = SIAPI_CROUCH; - SET_DIGITAL_ACTION(special); - special.keymap_id = SIAPI_SPECIAL; + SET_DIGITAL_ACTION(special); + special.keymap_id = SIAPI_SPECIAL; - SET_DIGITAL_ACTION(drop); - drop.keymap_id = SIAPI_DROP; + SET_DIGITAL_ACTION(drop); + drop.keymap_id = SIAPI_DROP; - SET_DIGITAL_ACTION(affinity); - affinity.keymap_id = SIAPI_AFFINITY; + SET_DIGITAL_ACTION(affinity); + affinity.keymap_id = SIAPI_AFFINITY; - SET_DIGITAL_ACTION(dash); - dash.keymap_id = SIAPI_DASH; + SET_DIGITAL_ACTION(dash); + dash.keymap_id = SIAPI_DASH; - SET_DIGITAL_ACTION(next_weapon); - next_weapon.keymap_id = SIAPI_NEXT_WEAPON; + SET_DIGITAL_ACTION(next_weapon); + next_weapon.keymap_id = SIAPI_NEXT_WEAPON; - SET_DIGITAL_ACTION(previous_weapon); - previous_weapon.keymap_id = SIAPI_PREVIOUS_WEAPON; + SET_DIGITAL_ACTION(previous_weapon); + previous_weapon.keymap_id = SIAPI_PREVIOUS_WEAPON; - SET_DIGITAL_ACTION(primary_weapon); - primary_weapon.keymap_id = SIAPI_PRIMARY_WEAPON; + SET_DIGITAL_ACTION(primary_weapon); + primary_weapon.keymap_id = SIAPI_PRIMARY_WEAPON; - SET_DIGITAL_ACTION(secondary_weapon); - secondary_weapon.keymap_id = SIAPI_SECONDARY_WEAPON; + SET_DIGITAL_ACTION(secondary_weapon); + secondary_weapon.keymap_id = SIAPI_SECONDARY_WEAPON; - SET_DIGITAL_ACTION(last_weapon); - last_weapon.keymap_id = SIAPI_LAST_WEAPON; + SET_DIGITAL_ACTION(last_weapon); + last_weapon.keymap_id = SIAPI_LAST_WEAPON; - SET_DIGITAL_ACTION(claw); - claw.keymap_id = SIAPI_CLAW; + SET_DIGITAL_ACTION(claw); + claw.keymap_id = SIAPI_CLAW; - SET_DIGITAL_ACTION(pistol); - pistol.keymap_id = SIAPI_PISTOL; + SET_DIGITAL_ACTION(pistol); + pistol.keymap_id = SIAPI_PISTOL; - SET_DIGITAL_ACTION(sword); - sword.keymap_id = SIAPI_SWORD; + SET_DIGITAL_ACTION(sword); + sword.keymap_id = SIAPI_SWORD; - SET_DIGITAL_ACTION(shotgun); - shotgun.keymap_id = SIAPI_SHOTGUN; + SET_DIGITAL_ACTION(shotgun); + shotgun.keymap_id = SIAPI_SHOTGUN; - SET_DIGITAL_ACTION(smg); - smg.keymap_id = SIAPI_SMG; + SET_DIGITAL_ACTION(smg); + smg.keymap_id = SIAPI_SMG; - SET_DIGITAL_ACTION(flamer); - flamer.keymap_id = SIAPI_FLAMER; + SET_DIGITAL_ACTION(flamer); + flamer.keymap_id = SIAPI_FLAMER; - SET_DIGITAL_ACTION(plasma); - plasma.keymap_id = SIAPI_PLASMA; + SET_DIGITAL_ACTION(plasma); + plasma.keymap_id = SIAPI_PLASMA; - SET_DIGITAL_ACTION(zapper); - zapper.keymap_id = SIAPI_ZAPPER; + SET_DIGITAL_ACTION(zapper); + zapper.keymap_id = SIAPI_ZAPPER; - SET_DIGITAL_ACTION(rifle); - rifle.keymap_id = SIAPI_RIFLE; + SET_DIGITAL_ACTION(rifle); + rifle.keymap_id = SIAPI_RIFLE; - SET_DIGITAL_ACTION(corroder); - corroder.keymap_id = SIAPI_CORRODER; + SET_DIGITAL_ACTION(corroder); + corroder.keymap_id = SIAPI_CORRODER; - SET_DIGITAL_ACTION(grenade); - grenade.keymap_id = SIAPI_GRENADE; + SET_DIGITAL_ACTION(grenade); + grenade.keymap_id = SIAPI_GRENADE; - SET_DIGITAL_ACTION(mine); - mine.keymap_id = SIAPI_MINE; + SET_DIGITAL_ACTION(mine); + mine.keymap_id = SIAPI_MINE; - SET_DIGITAL_ACTION(rocket); - rocket.keymap_id = SIAPI_ROCKET; + SET_DIGITAL_ACTION(rocket); + rocket.keymap_id = SIAPI_ROCKET; - SET_DIGITAL_ACTION(minigun); - minigun.keymap_id = SIAPI_MINIGUN; + SET_DIGITAL_ACTION(minigun); + minigun.keymap_id = SIAPI_MINIGUN; - SET_DIGITAL_ACTION(jetsaw); - jetsaw.keymap_id = SIAPI_JETSAW; + SET_DIGITAL_ACTION(jetsaw); + jetsaw.keymap_id = SIAPI_JETSAW; - SET_DIGITAL_ACTION(melee); - melee.keymap_id = SIAPI_MELEE; + SET_DIGITAL_ACTION(melee); + melee.keymap_id = SIAPI_MELEE; - SET_DIGITAL_ACTION(wheel_select); - wheel_select.keymap_id = SIAPI_WHEEL_SELECT; + SET_DIGITAL_ACTION(wheel_select); + wheel_select.keymap_id = SIAPI_WHEEL_SELECT; - SET_DIGITAL_ACTION(change_loadout); - change_loadout.keymap_id = SIAPI_CHANGE_LOADOUT; + SET_DIGITAL_ACTION(change_loadout); + change_loadout.keymap_id = SIAPI_CHANGE_LOADOUT; - SET_DIGITAL_ACTION(scoreboard); - scoreboard.keymap_id = SIAPI_SCOREBOARD; + SET_DIGITAL_ACTION(scoreboard); + scoreboard.keymap_id = SIAPI_SCOREBOARD; - SET_DIGITAL_ACTION(suicide); - suicide.keymap_id = SIAPI_SUICIDE; + SET_DIGITAL_ACTION(suicide); + suicide.keymap_id = SIAPI_SUICIDE; - SET_DIGITAL_ACTION(menu); - menu.keymap_id = SIAPI_MENU; + SET_DIGITAL_ACTION(menu); + menu.keymap_id = SIAPI_MENU; - SET_DIGITAL_ACTION(recenter_camera); + SET_DIGITAL_ACTION(recenter_camera); - SET_ACTION_SET(MenuControls); - SET_ANALOG_ACTION(menu_cursor); + SET_ACTION_SET(MenuControls); + SET_ANALOG_ACTION(menu_cursor); - SET_DIGITAL_ACTION(menu_select); - menu_select.keymap_id = SIAPI_MENU_SELECT; + SET_DIGITAL_ACTION(menu_select); + menu_select.keymap_id = SIAPI_MENU_SELECT; - SET_DIGITAL_ACTION(menu_cancel); - menu_cancel.keymap_id = SIAPI_MENU_CANCEL; + SET_DIGITAL_ACTION(menu_cancel); + menu_cancel.keymap_id = SIAPI_MENU_CANCEL; - SET_DIGITAL_ACTION(menu_scroll_up); - menu_scroll_up.keymap_id = SIAPI_MENU_SCROLL_UP; + SET_DIGITAL_ACTION(menu_scroll_up); + menu_scroll_up.keymap_id = SIAPI_MENU_SCROLL_UP; - SET_DIGITAL_ACTION(menu_scroll_down); - menu_scroll_down.keymap_id = SIAPI_MENU_SCROLL_DOWN; + SET_DIGITAL_ACTION(menu_scroll_down); + menu_scroll_down.keymap_id = SIAPI_MENU_SCROLL_DOWN; - SET_ACTION_SET(EditingControls); + SET_ACTION_SET(EditingControls); - // Also initialize textkey glyph return buffer to its maximum size - textkeyvec.growbuf(STEAM_INPUT_MAX_ORIGINS); + // Also initialize textkey glyph return buffer to its maximum size + textkeyvec.growbuf(STEAM_INPUT_MAX_ORIGINS); } void update_ingame_actions(int controlleridx); @@ -357,208 +354,217 @@ void update_menu_actions(int controlleridx); void update_from_controller() { - // Steamworks ( https://partner.steamgames.com/doc/api/ISteamInput#RunFrame ) says that - // > Synchronize API state with the latest Steam Controller inputs - // > available. This is performed automatically by - // > SteamAPI_RunCallbacks, but for the absolute lowest possible - // > latency, you can call this directly before reading controller - // > state. - // which appears to be necessary here, otherwise we seem to drop some - // gamepad inputs - cdpi::steam::input->RunFrame(); - - int connected_count = cdpi::steam::input->GetConnectedControllers(controllers); - - if (connected_count == 0) return; - - // Initialize handles if needed - if(!(InGameControls_handle && MenuControls_handle && EditingControls_handle)) controller::init_siapi_handles(); - - for (int i = 0; i < connected_count; i++) { - if (editmode) { - // TODO: We currently don't have SIAPI actions for - // editing mode, but we do provide an action set for - // convenience - cdpi::steam::input->ActivateActionSet(STEAM_INPUT_HANDLE_ALL_CONTROLLERS, EditingControls_handle); - continue; - } - - if (UI::hasinput() && !UI::menuisgameplay()) { - cdpi::steam::input->ActivateActionSet(STEAM_INPUT_HANDLE_ALL_CONTROLLERS, MenuControls_handle); - update_menu_actions(i); - continue; - } - - cdpi::steam::input->ActivateActionSet(STEAM_INPUT_HANDLE_ALL_CONTROLLERS, InGameControls_handle); - update_ingame_actions(i); - } + // Steamworks ( https://partner.steamgames.com/doc/api/ISteamInput#RunFrame + // ) says that + + // > Synchronize API state with the latest Steam Controller inputs + // > available. This is performed automatically by SteamAPI_RunCallbacks, + // > but for the absolute lowest possible latency, you can call this + // > directly before reading controller state. + + // which appears to be necessary here, otherwise we seem to drop some + // gamepad inputs + cdpi::steam::input->RunFrame(); + + int connected_count = cdpi::steam::input->GetConnectedControllers(controllers); + + if(connected_count == 0) return; + + // Initialize handles if needed + if(!(InGameControls_handle && MenuControls_handle && EditingControls_handle)) controller::init_siapi_handles(); + + for (int i = 0; i < connected_count; i++) + { + if(editmode) + { + // TODO: We currently don't have SIAPI actions for + // editing mode, but we do provide an action set for + // convenience + cdpi::steam::input->ActivateActionSet(STEAM_INPUT_HANDLE_ALL_CONTROLLERS, EditingControls_handle); + continue; + } + + if(UI::hasinput() && !UI::menuisgameplay()) + { + cdpi::steam::input->ActivateActionSet(STEAM_INPUT_HANDLE_ALL_CONTROLLERS, MenuControls_handle); + update_menu_actions(i); + continue; + } + + cdpi::steam::input->ActivateActionSet(STEAM_INPUT_HANDLE_ALL_CONTROLLERS, InGameControls_handle); + update_ingame_actions(i); + } } void update_ingame_actions(int controlleridx) { - InputAnalogActionData_t move_data = cdpi::steam::input->GetAnalogActionData( - controllers[controlleridx], - move_handle - ); - - if (lastusedcontroller == controllers[controlleridx]) { - //game::player1->move = move_data.y; - - if (move_data.y < -0.5f) - game::player1->move = -1; - else if (move_data.y > 0.5f) - game::player1->move = 1; - else if (!lastmovementwaskeyboard) - game::player1->move = 0; - - //game::player1->strafe = -move_data.x; - - if (move_data.x < -0.5f) - game::player1->strafe = 1; - else if (move_data.x > 0.5f) - game::player1->strafe = -1; - else if (!lastmovementwaskeyboard) - game::player1->strafe = 0; - } - - if (move_data.x != 0 || move_data.y != 0) { - lastinputwassiapi = true; - lastusedcontroller = controllers[controlleridx]; - lastmovementwaskeyboard = false; - } - - // We have to read the camera delta every frame even if we don't intend - // on doing anything with it, otherwise it will 'build up', which is not - // what we want in the cases where we are going to deliberately ignore - // it. - InputAnalogActionData_t camera_delta = cdpi::steam::input->GetAnalogActionData( - controllers[controlleridx], - camera_handle - ); - - recenter_camera.update(controlleridx); - if (recenter_camera.pressed(controlleridx)) { - game::resetplayerpitch(); - } else { - game::mousemove(camera_delta.x, camera_delta.y, 0, 0, screenw, screenh, true); - if (camera_delta.x != 0 || camera_delta.y != 0) { - lastinputwassiapi = true; - lastusedcontroller = controllers[controlleridx]; - } - - if (UI::menuispie()) { - // For pie menus (like the weapon select wheel), we have - // a special joystick-like mode that's also available to - // use. It will override the standard camera action if - // in use (good for not needing to disable a gyro, for - // example). The expectation is that the controller - // config will modeshift the stick/pad when a button - // bound to a pie menu is hit. - InputAnalogActionData_t pie_pos = cdpi::steam::input->GetAnalogActionData( - controllers[controlleridx], - pie_cursor_handle - ); - - if (pie_pos.x != 0.0f || pie_pos.y != 0.0f) { - resetcursor(true, true); - game::mousemove( - // screenh is not a typo - pie_pos.x * (screenh / 4), - -pie_pos.y * (screenh / 4), - 0, 0, - screenw, screenh, - true - ); - } - } - } - - primary.ingame_process(controlleridx); - secondary.ingame_process(controlleridx); - reload.ingame_process(controlleridx); - use.ingame_process(controlleridx); - jump.ingame_process(controlleridx); - walk.ingame_process(controlleridx); - crouch.ingame_process(controlleridx); - special.ingame_process(controlleridx); - drop.ingame_process(controlleridx); - affinity.ingame_process(controlleridx); - dash.ingame_process(controlleridx); - next_weapon.ingame_process(controlleridx); - previous_weapon.ingame_process(controlleridx); - primary_weapon.ingame_process(controlleridx); - secondary_weapon.ingame_process(controlleridx); - last_weapon.ingame_process(controlleridx); - claw.ingame_process(controlleridx); - pistol.ingame_process(controlleridx); - sword.ingame_process(controlleridx); - shotgun.ingame_process(controlleridx); - smg.ingame_process(controlleridx); - flamer.ingame_process(controlleridx); - plasma.ingame_process(controlleridx); - zapper.ingame_process(controlleridx); - rifle.ingame_process(controlleridx); - corroder.ingame_process(controlleridx); - grenade.ingame_process(controlleridx); - mine.ingame_process(controlleridx); - rocket.ingame_process(controlleridx); - minigun.ingame_process(controlleridx); - jetsaw.ingame_process(controlleridx); - melee.ingame_process(controlleridx); - wheel_select.ingame_process(controlleridx); - change_loadout.ingame_process(controlleridx); - scoreboard.ingame_process(controlleridx); - suicide.ingame_process(controlleridx); - menu.ingame_process(controlleridx); + InputAnalogActionData_t move_data = cdpi::steam::input->GetAnalogActionData( + controllers[controlleridx], + move_handle + ); + + if(lastusedcontroller == controllers[controlleridx]) + { + //game::player1->move = move_data.y; + + if(move_data.y < -0.5f) + game::player1->move = -1; + else if(move_data.y > 0.5f) + game::player1->move = 1; + else if(!lastmovementwaskeyboard) + game::player1->move = 0; + + //game::player1->strafe = -move_data.x; + + if(move_data.x < -0.5f) + game::player1->strafe = 1; + else if(move_data.x > 0.5f) + game::player1->strafe = -1; + else if(!lastmovementwaskeyboard) + game::player1->strafe = 0; + } + + if(move_data.x != 0 || move_data.y != 0) + { + lastinputwassiapi = true; + lastusedcontroller = controllers[controlleridx]; + lastmovementwaskeyboard = false; + } + + // We have to read the camera delta every frame even if we don't intend + // on doing anything with it, otherwise it will 'build up', which is not + // what we want in the cases where we are going to deliberately ignore + // it. + InputAnalogActionData_t camera_delta = cdpi::steam::input->GetAnalogActionData( + controllers[controlleridx], + camera_handle + ); + + recenter_camera.update(controlleridx); + if(recenter_camera.pressed(controlleridx)) + { + game::resetplayerpitch(); + } else { + game::mousemove(camera_delta.x, camera_delta.y, 0, 0, screenw, screenh, true); + if(camera_delta.x != 0 || camera_delta.y != 0) + { + lastinputwassiapi = true; + lastusedcontroller = controllers[controlleridx]; + } + if(UI::menuispie()) + { + // For pie menus (like the weapon select wheel), we have + // a special joystick-like mode that's also available to + // use. It will override the standard camera action if + // in use (good for not needing to disable a gyro, for + // example). The expectation is that the controller + // config will modeshift the stick/pad when a button + // bound to a pie menu is hit. + InputAnalogActionData_t pie_pos = cdpi::steam::input->GetAnalogActionData( + controllers[controlleridx], + pie_cursor_handle + ); + + if(pie_pos.x != 0.0f || pie_pos.y != 0.0f) + { + resetcursor(true, true); + game::mousemove( + // screenh is not a typo + pie_pos.x * (screenh / 4), + -pie_pos.y * (screenh / 4), + 0, 0, + screenw, screenh, + true + ); + } + } + } + + primary.ingame_process(controlleridx); + secondary.ingame_process(controlleridx); + reload.ingame_process(controlleridx); + use.ingame_process(controlleridx); + jump.ingame_process(controlleridx); + walk.ingame_process(controlleridx); + crouch.ingame_process(controlleridx); + special.ingame_process(controlleridx); + drop.ingame_process(controlleridx); + affinity.ingame_process(controlleridx); + dash.ingame_process(controlleridx); + next_weapon.ingame_process(controlleridx); + previous_weapon.ingame_process(controlleridx); + primary_weapon.ingame_process(controlleridx); + secondary_weapon.ingame_process(controlleridx); + last_weapon.ingame_process(controlleridx); + claw.ingame_process(controlleridx); + pistol.ingame_process(controlleridx); + sword.ingame_process(controlleridx); + shotgun.ingame_process(controlleridx); + smg.ingame_process(controlleridx); + flamer.ingame_process(controlleridx); + plasma.ingame_process(controlleridx); + zapper.ingame_process(controlleridx); + rifle.ingame_process(controlleridx); + corroder.ingame_process(controlleridx); + grenade.ingame_process(controlleridx); + mine.ingame_process(controlleridx); + rocket.ingame_process(controlleridx); + minigun.ingame_process(controlleridx); + jetsaw.ingame_process(controlleridx); + melee.ingame_process(controlleridx); + wheel_select.ingame_process(controlleridx); + change_loadout.ingame_process(controlleridx); + scoreboard.ingame_process(controlleridx); + suicide.ingame_process(controlleridx); + menu.ingame_process(controlleridx); } void update_menu_actions(int controlleridx) { - InputAnalogActionData_t cursor_data = cdpi::steam::input->GetAnalogActionData( - controllers[controlleridx], - menu_cursor_handle - ); + InputAnalogActionData_t cursor_data = cdpi::steam::input->GetAnalogActionData( + controllers[controlleridx], + menu_cursor_handle + ); - game::mousemove(cursor_data.x, cursor_data.y, 0, 0, screenw, screenh, true); + game::mousemove(cursor_data.x, cursor_data.y, 0, 0, screenw, screenh, true); - if (cursor_data.x != 0 || cursor_data.y != 0) - lastinputwassiapi = true; + if(cursor_data.x != 0 || cursor_data.y != 0) lastinputwassiapi = true; - menu_select.menu_process(controlleridx); - menu_cancel.menu_process(controlleridx); - menu_scroll_up.menu_process(controlleridx); - menu_scroll_down.menu_process(controlleridx); + menu_select.menu_process(controlleridx); + menu_cancel.menu_process(controlleridx); + menu_scroll_up.menu_process(controlleridx); + menu_scroll_down.menu_process(controlleridx); } bool is_siapi_textkey(const char *str) { - return !strncmp(str, "SIAPI_", 6); + return !strncmp(str, "SIAPI_", 6); } digital_action_state *get_das_for_keymap_name(const char *str) { - // This function is awful, redo to be smarter - if (!strcmp(str, "SIAPI_PRIMARY")) return &primary; - if (!strcmp(str, "SIAPI_SECONDARY")) return &secondary; - if (!strcmp(str, "SIAPI_RELOAD")) return &reload; - if (!strcmp(str, "SIAPI_USE")) return &use; - if (!strcmp(str, "SIAPI_JUMP")) return &jump; - if (!strcmp(str, "SIAPI_WALK")) return &walk; - if (!strcmp(str, "SIAPI_CROUCH")) return &crouch; - if (!strcmp(str, "SIAPI_SPECIAL")) return &special; - if (!strcmp(str, "SIAPI_DROP")) return &drop; - if (!strcmp(str, "SIAPI_AFFINITY")) return &affinity; - if (!strcmp(str, "SIAPI_DASH")) return ‐ - if (!strcmp(str, "SIAPI_NEXT_WEAPON")) return &next_weapon; - if (!strcmp(str, "SIAPI_PREVIOUS_WEAPON")) return &previous_weapon; - if (!strcmp(str, "SIAPI_PRIMARY_WEAPON")) return &primary_weapon; - if (!strcmp(str, "SIAPI_SECONDARY_WEAPON")) return &secondary_weapon; - if (!strcmp(str, "SIAPI_WHEEL_SELECT")) return &wheel_select; - if (!strcmp(str, "SIAPI_CHANGE_LOADOUT")) return &change_loadout; - if (!strcmp(str, "SIAPI_SCOREBOARD")) return &scoreboard; - if (!strcmp(str, "SIAPI_SUICIDE")) return &suicide; - if (!strcmp(str, "SIAPI_MENU")) return &menu; - return NULL; // Should never happen + // This function is awful, redo to be smarter + if(!strcmp(str, "SIAPI_PRIMARY")) return &primary; + if(!strcmp(str, "SIAPI_SECONDARY")) return &secondary; + if(!strcmp(str, "SIAPI_RELOAD")) return &reload; + if(!strcmp(str, "SIAPI_USE")) return &use; + if(!strcmp(str, "SIAPI_JUMP")) return &jump; + if(!strcmp(str, "SIAPI_WALK")) return &walk; + if(!strcmp(str, "SIAPI_CROUCH")) return &crouch; + if(!strcmp(str, "SIAPI_SPECIAL")) return &special; + if(!strcmp(str, "SIAPI_DROP")) return &drop; + if(!strcmp(str, "SIAPI_AFFINITY")) return &affinity; + if(!strcmp(str, "SIAPI_DASH")) return ‐ + if(!strcmp(str, "SIAPI_NEXT_WEAPON")) return &next_weapon; + if(!strcmp(str, "SIAPI_PREVIOUS_WEAPON")) return &previous_weapon; + if(!strcmp(str, "SIAPI_PRIMARY_WEAPON")) return &primary_weapon; + if(!strcmp(str, "SIAPI_SECONDARY_WEAPON")) return &secondary_weapon; + if(!strcmp(str, "SIAPI_WHEEL_SELECT")) return &wheel_select; + if(!strcmp(str, "SIAPI_CHANGE_LOADOUT")) return &change_loadout; + if(!strcmp(str, "SIAPI_SCOREBOARD")) return &scoreboard; + if(!strcmp(str, "SIAPI_SUICIDE")) return &suicide; + if(!strcmp(str, "SIAPI_MENU")) return &menu; + return NULL; // Should never happen } EInputActionOrigin *origins = NULL; @@ -567,42 +573,43 @@ vector textkeys; vector get_siapi_textkeys(const char *str) { - digital_action_state *das = get_das_for_keymap_name(str); + digital_action_state *das = get_das_for_keymap_name(str); - if(!origins) origins = new EInputActionOrigin[STEAM_INPUT_MAX_ORIGINS]; + if(!origins) origins = new EInputActionOrigin[STEAM_INPUT_MAX_ORIGINS]; - // We have to clear the buffer ourselves between calls - memset(origins, 0, STEAM_INPUT_MAX_ORIGINS * sizeof(EInputActionOrigin)); - cdpi::steam::input->GetDigitalActionOrigins( - lastusedcontroller, - hud::hasinput(true) ? MenuControls_handle : InGameControls_handle, - das->handle, - origins + // We have to clear the buffer ourselves between calls + memset(origins, 0, STEAM_INPUT_MAX_ORIGINS * sizeof(EInputActionOrigin)); + cdpi::steam::input->GetDigitalActionOrigins( + lastusedcontroller, + hud::hasinput(true) ? MenuControls_handle : InGameControls_handle, + das->handle, + origins ); - for (int i = 0; i < STEAM_INPUT_MAX_ORIGINS; i++) + for (int i = 0; i < STEAM_INPUT_MAX_ORIGINS; i++) + { + if(origins[i] == k_EInputActionOrigin_None + || origins[i] > k_EInputActionOrigin_MaximumPossibleValue) { - if (origins[i] == k_EInputActionOrigin_None - || origins[i] > k_EInputActionOrigin_MaximumPossibleValue) { - // textkeyvec is always STEAM_INPUT_MAX_ORIGINS items long, - // but we adjust the reported length so that the draw code - // doesn't try to draw origins we don't actually have - textkeyvec.setsize(i); - break; - } - const char *siapi_origin_glyph = cdpi::steam::input->GetGlyphPNGForActionOrigin( - origins[i], - k_ESteamInputGlyphSize_Medium, - ESteamInputGlyphStyle_Dark + // textkeyvec is always STEAM_INPUT_MAX_ORIGINS items long, + // but we adjust the reported length so that the draw code + // doesn't try to draw origins we don't actually have + textkeyvec.setsize(i); + break; + } + const char *siapi_origin_glyph = cdpi::steam::input->GetGlyphPNGForActionOrigin( + origins[i], + k_ESteamInputGlyphSize_Medium, + ESteamInputGlyphStyle_Dark ); - char origin_enum_string[13]; - snprintf(origin_enum_string, 13, "origin_%d", origins[0]); + char origin_enum_string[13]; + snprintf(origin_enum_string, 13, "origin_%d", origins[0]); - textkeyvec[i] = findtextkey_common(origin_enum_string, textkeys, siapi_origin_glyph); - } + textkeyvec[i] = findtextkey_common(origin_enum_string, textkeys, siapi_origin_glyph); + } - return textkeyvec; + return textkeyvec; } ICOMMAND(0, showsiapibindpanel, "", (), { cdpi::steam::input->ShowBindingPanel(lastusedcontroller); }); diff --git a/src/game/controller.h b/src/game/controller.h index 517939cf4..49c7be110 100644 --- a/src/game/controller.h +++ b/src/game/controller.h @@ -5,10 +5,10 @@ namespace controller { - extern bool lastinputwassiapi; - extern bool lastmovementwaskeyboard; - extern void update_from_controller(); - extern bool is_siapi_textkey(const char *str); - extern vector get_siapi_textkeys(const char *str); + extern bool lastinputwassiapi; + extern bool lastmovementwaskeyboard; + extern void update_from_controller(); + extern bool is_siapi_textkey(const char *str); + extern vector get_siapi_textkeys(const char *str); } #endif From f152889a7e157602424624171a990cdd95e2a88a Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Sat, 14 Feb 2026 13:39:58 -0800 Subject: [PATCH 24/26] Add game/controller.o to CLIENT_OBJS --- src/Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Makefile b/src/Makefile index d5b2e9626..756a3c5ed 100644 --- a/src/Makefile +++ b/src/Makefile @@ -344,7 +344,8 @@ CLIENT_OBJS = \ game/scoreboard.o \ game/server.o \ game/waypoint.o \ - game/weapons.o + game/weapons.o \ + game/controller.o # Build tests ifneq (,$(findstring -D_DEBUG,$(CXXFLAGS))) From 5d3c6900f5cc9a42dfe1d050fcb95e0d427d207d Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Sat, 14 Feb 2026 13:53:17 -0800 Subject: [PATCH 25/26] Fix missed formatting adjustment --- src/game/controller.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/controller.cpp b/src/game/controller.cpp index b2e5359d6..dfa6d58a0 100644 --- a/src/game/controller.cpp +++ b/src/game/controller.cpp @@ -601,7 +601,7 @@ vector get_siapi_textkeys(const char *str) origins[i], k_ESteamInputGlyphSize_Medium, ESteamInputGlyphStyle_Dark - ); + ); char origin_enum_string[13]; snprintf(origin_enum_string, 13, "origin_%d", origins[0]); From dcf79328a09773abedf3bf662f45af9d50c5a547 Mon Sep 17 00:00:00 2001 From: Robert Cochran Date: Sat, 14 Feb 2026 13:55:13 -0800 Subject: [PATCH 26/26] Better return value for dummy get_siapi_textkeys --- src/game/controller.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/game/controller.cpp b/src/game/controller.cpp index dfa6d58a0..b7837c277 100644 --- a/src/game/controller.cpp +++ b/src/game/controller.cpp @@ -77,6 +77,9 @@ enum siapi_keycodes { SIAPI_MENU_SCROLL_DOWN = -5, // scroll wheel +Y }; +// For returning SIAPI glyphs as a list +vector textkeyvec; + // This current controller implementation depends on Steam Input and is not // available outside of Steam #if defined(USE_STEAM) @@ -204,9 +207,6 @@ class digital_action_state menu_scroll_down; DEF_ACTION_SET(EditingControls); -// For returning SIAPI glyphs as a list -vector textkeyvec; - void init_siapi_handles() { SET_ACTION_SET(InGameControls); @@ -626,7 +626,7 @@ bool is_siapi_textkey(const char *str) vector get_siapi_textkeys(const char *str) { - return NULL; + return textkeyvec; } ICOMMAND(0, showsiapibindpanel, "", (), { return; });