diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 5c7e872cc..e4440b008 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1235,7 +1235,7 @@ enum { WP_KICKSTARTACCEL = 1<<0, WP_SHRINKME = 1<<1, WP_AUTOROULETTE = 1<<2, - WP_LITESTEER = 1<<3, + WP_ANALOGSTICK = 1<<3, }; void WeaponPref_Send(UINT8 ssplayer) @@ -1248,12 +1248,12 @@ void WeaponPref_Send(UINT8 ssplayer) if (cv_autoroulette[ssplayer].value) prefs |= WP_AUTOROULETTE; - if (cv_litesteer[ssplayer].value) - prefs |= WP_LITESTEER; - if (cv_shrinkme[ssplayer].value) prefs |= WP_SHRINKME; + if (gamecontrolflags[ssplayer] & GCF_ANALOGSTICK) + prefs |= WP_ANALOGSTICK; + SendNetXCmdForPlayer(ssplayer, XD_WEAPONPREF, &prefs, 1); } @@ -1269,12 +1269,12 @@ void WeaponPref_Save(UINT8 **cp, INT32 playernum) if (player->pflags & PF_AUTOROULETTE) prefs |= WP_AUTOROULETTE; - if (player->pflags & PF_LITESTEER) - prefs |= WP_LITESTEER; - if (player->pflags & PF_SHRINKME) prefs |= WP_SHRINKME; + if (player->pflags & PF_ANALOGSTICK) + prefs |= WP_ANALOGSTICK; + WRITEUINT8(*cp, prefs); } @@ -1296,6 +1296,11 @@ size_t WeaponPref_Parse(const UINT8 *bufstart, INT32 playernum) if (prefs & WP_SHRINKME) player->pflags |= PF_SHRINKME; + if (prefs & WP_ANALOGSTICK) + player->pflags |= PF_ANALOGSTICK; + else + player->pflags &= ~PF_ANALOGSTICK; + if (leveltime < 2) { // BAD HACK: No other place I tried to slot this in diff --git a/src/d_player.h b/src/d_player.h index 8411fd80f..e164e1afe 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -103,7 +103,7 @@ typedef enum PF_RINGLOCK = 1<<13, // Prevent picking up rings while SPB is locked on - PF_LITESTEER = 1<<14, // Hold Down to shallow turn (digital only) + PF_ANALOGSTICK = 1<<14, // This player is using an analog joystick //15-17 free, was previously itemflags stuff diff --git a/src/deh_tables.c b/src/deh_tables.c index c39d4fb67..d77a47cab 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -3994,7 +3994,7 @@ const char *const PLAYERFLAG_LIST[] = { "RINGLOCK", // Prevent picking up rings while SPB is locked on - "LITESTEER", // Shallow digital turn with DOWN + "ANALOGSTICK", // This player is using an analog joystick "\x01", // Free "\x01", // Free "\x01", // Free diff --git a/src/g_demo.c b/src/g_demo.c index 462482bdd..f3321cc90 100644 --- a/src/g_demo.c +++ b/src/g_demo.c @@ -125,7 +125,6 @@ demoghost *ghosts = NULL; #define DEMO_SHRINKME 0x04 #define DEMO_BOT 0x08 #define DEMO_AUTOROULETTE 0x10 -#define DEMO_LITESTEER 0x20 // For demos #define ZT_FWD 0x0001 @@ -2218,8 +2217,6 @@ void G_BeginRecording(void) i |= DEMO_KICKSTART; if (player->pflags & PF_AUTOROULETTE) i |= DEMO_AUTOROULETTE; - if (player->pflags & PF_LITESTEER) - i |= DEMO_LITESTEER; if (player->pflags & PF_SHRINKME) i |= DEMO_SHRINKME; if (player->bot == true) @@ -3172,11 +3169,6 @@ void G_DoPlayDemo(const char *defdemoname) else players[p].pflags &= ~PF_AUTOROULETTE; - if (flags & DEMO_LITESTEER) - players[p].pflags |= PF_LITESTEER; - else - players[p].pflags &= ~PF_LITESTEER; - if (flags & DEMO_SHRINKME) players[p].pflags |= PF_SHRINKME; else diff --git a/src/g_game.c b/src/g_game.c index e779ffa1b..1eeda32f9 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -2189,7 +2189,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) totalring = players[player].totalring; xtralife = players[player].xtralife; - pflags = (players[player].pflags & (PF_WANTSTOJOIN|PF_KICKSTARTACCEL|PF_SHRINKME|PF_SHRINKACTIVE|PF_AUTOROULETTE|PF_LITESTEER)); + pflags = (players[player].pflags & (PF_WANTSTOJOIN|PF_KICKSTARTACCEL|PF_SHRINKME|PF_SHRINKACTIVE|PF_AUTOROULETTE|PF_ANALOGSTICK)); // SRB2kart memcpy(&itemRoulette, &players[player].itemRoulette, sizeof (itemRoulette)); diff --git a/src/g_input.c b/src/g_input.c index 4bcd521fe..279cae0f3 100644 --- a/src/g_input.c +++ b/src/g_input.c @@ -31,6 +31,7 @@ INT32 gamekeydown[MAXDEVICES][NUMINPUTS]; // two key codes (or virtual key) per game control INT32 gamecontrol[MAXSPLITSCREENPLAYERS][num_gamecontrols][MAXINPUTMAPPING]; +UINT8 gamecontrolflags[MAXSPLITSCREENPLAYERS]; INT32 gamecontroldefault[num_gamecontrols][MAXINPUTMAPPING]; // default control storage INT32 menucontrolreserved[num_gamecontrols][MAXINPUTMAPPING]; @@ -963,19 +964,37 @@ void G_DefineDefaultControls(void) menucontrolreserved[gc_start][0] = KEY_ESCAPE; // Handled special } -void G_CopyControls(INT32 (*setupcontrols)[MAXINPUTMAPPING], INT32 (*fromcontrols)[MAXINPUTMAPPING], const INT32 *gclist, INT32 gclen) +static boolean G_ControlUsesAxis(INT32 map[MAXINPUTMAPPING]) { - INT32 i, j, gc; - - for (i = 0; i < (gclist && gclen ? gclen : num_gamecontrols); i++) + for (INT32 i = 0; i < MAXINPUTMAPPING; i++) { - gc = (gclist && gclen) ? gclist[i] : i; - - for (j = 0; j < MAXINPUTMAPPING; j++) + INT32 key = map[i]; + if (key >= KEY_AXIS1 && key < JOYINPUTEND) { - setupcontrols[gc][j] = fromcontrols[gc][j]; + return true; } } + + return false; +} + +void G_ApplyControlScheme(UINT8 splitplayer, INT32 (*fromcontrols)[MAXINPUTMAPPING]) +{ + UINT8 flags = 0; + + if (G_ControlUsesAxis(fromcontrols[gc_up]) || + G_ControlUsesAxis(fromcontrols[gc_down]) || + G_ControlUsesAxis(fromcontrols[gc_left]) || + G_ControlUsesAxis(fromcontrols[gc_right])) + { + flags |= GCF_ANALOGSTICK; + } + + memcpy(gamecontrol[splitplayer], fromcontrols, sizeof gamecontrol[splitplayer]); + gamecontrolflags[splitplayer] = flags; + + if (Playing()) + WeaponPref_Send(splitplayer); // update PF_ANALOGSTICK } void G_SaveKeySetting(FILE *f, INT32 (*fromcontrolsa)[MAXINPUTMAPPING], INT32 (*fromcontrolsb)[MAXINPUTMAPPING], INT32 (*fromcontrolsc)[MAXINPUTMAPPING], INT32 (*fromcontrolsd)[MAXINPUTMAPPING]) diff --git a/src/g_input.h b/src/g_input.h index f2b8e6ca8..84d9df55f 100644 --- a/src/g_input.h +++ b/src/g_input.h @@ -100,6 +100,11 @@ typedef enum gc_drift = gc_r, } gamecontrols_e; +typedef enum +{ + GCF_ANALOGSTICK = 1 << 0, +} gamecontrol_flags_e; + // mouse values are used once extern consvar_t cv_controlperkey; @@ -113,6 +118,7 @@ extern INT32 gamekeydown[MAXDEVICES][NUMINPUTS]; // several key codes (or virtual key) per game control extern INT32 gamecontrol[MAXSPLITSCREENPLAYERS][num_gamecontrols][MAXINPUTMAPPING]; +extern UINT8 gamecontrolflags[MAXSPLITSCREENPLAYERS]; extern INT32 gamecontroldefault[num_gamecontrols][MAXINPUTMAPPING]; // default control storage extern INT32 menucontrolreserved[num_gamecontrols][MAXINPUTMAPPING]; @@ -194,7 +200,7 @@ void Command_Setcontrol3_f(void); void Command_Setcontrol4_f(void); void G_DefineDefaultControls(void); INT32 G_GetControlScheme(INT32 (*fromcontrols)[MAXINPUTMAPPING], const INT32 *gclist, INT32 gclen); -void G_CopyControls(INT32 (*setupcontrols)[MAXINPUTMAPPING], INT32 (*fromcontrols)[MAXINPUTMAPPING], const INT32 *gclist, INT32 gclen); +void G_ApplyControlScheme(UINT8 splitplayer, INT32 (*fromcontrols)[MAXINPUTMAPPING]); void G_SaveKeySetting(FILE *f, INT32 (*fromcontrolsa)[MAXINPUTMAPPING], INT32 (*fromcontrolsb)[MAXINPUTMAPPING], INT32 (*fromcontrolsc)[MAXINPUTMAPPING], INT32 (*fromcontrolsd)[MAXINPUTMAPPING]); INT32 G_CheckDoubleUsage(INT32 keynum, INT32 playernum, boolean modify); diff --git a/src/g_party.cpp b/src/g_party.cpp index bb55c36d8..307250e5c 100644 --- a/src/g_party.cpp +++ b/src/g_party.cpp @@ -316,3 +316,10 @@ UINT8 G_PartyPosition(UINT8 player) return party.find(player) - party.begin(); } + +UINT8 G_LocalSplitscreenPartyPosition(UINT8 player) +{ + const Party& party = local_party[player]; + + return party.find(player) - party.begin(); +} diff --git a/src/g_party.h b/src/g_party.h index 8937545e8..cfda1755e 100644 --- a/src/g_party.h +++ b/src/g_party.h @@ -62,6 +62,9 @@ const UINT8 *G_PartyArray(UINT8 player); // Suitable index to G_PartyMember and G_PartyArray. UINT8 G_PartyPosition(UINT8 player); +// +UINT8 G_LocalSplitscreenPartyPosition(UINT8 player); + // // Globals // diff --git a/src/hud/CMakeLists.txt b/src/hud/CMakeLists.txt index 629b708f4..fcdc823c6 100644 --- a/src/hud/CMakeLists.txt +++ b/src/hud/CMakeLists.txt @@ -3,4 +3,5 @@ target_sources(SRB2SDL2 PRIVATE spectator.cpp timer.cpp emerald-win.cpp + input-display.cpp ) diff --git a/src/hud/input-display.cpp b/src/hud/input-display.cpp new file mode 100644 index 000000000..abc66752b --- /dev/null +++ b/src/hud/input-display.cpp @@ -0,0 +1,105 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2024 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include + +#include + +#include "../math/vec.hpp" + +#include "../g_input.h" +#include "../g_game.h" +#include "../i_joy.h" +#include "../k_hud.h" +#include "../k_kart.h" +#include "../v_draw.hpp" + +using srb2::Draw; +using srb2::math::Vec2; + +namespace +{ + +const char* dpad_suffix(const Vec2& v) +{ + if (v.y > 0) + { + if (v.x < 0) + return "UL"; + else if (v.x > 0) + return "UR"; + else + return "U"; + } + else if (v.y < 0) + { + if (v.x < 0) + return "DL"; + else if (v.x > 0) + return "DR"; + else + return "D"; + } + else + { + if (v.x < 0) + return "L"; + else if (v.x > 0) + return "R"; + else + return "N"; + } +} + +}; // namespace + +void K_DrawInputDisplay(INT32 x, INT32 y, INT32 flags, char mode, UINT8 pid, boolean local, boolean transparent) +{ + const ticcmd_t& cmd = players[displayplayers[pid]].cmd; + const std::string prefix = fmt::format("PR{}", mode); + auto gfx = [&](auto format, auto&&... args) { return prefix + fmt::format(format, args...); }; + auto but = [&](char key, INT32 gc, UINT32 bt) + { + bool press = local ? G_PlayerInputAnalog(pid, gc, 0) : ((cmd.buttons & bt) == bt); + return gfx(press ? "BT{}B" : "BT{}", key); + }; + + Draw box = Draw(x, y).flags(flags); + + box.flags(transparent ? V_TRANSLUCENT : 0).patch(gfx("CONT")); + + Vec2 dpad = local ? + Vec2 { + (G_PlayerInputAnalog(pid, gc_right, 0) - G_PlayerInputAnalog(pid, gc_left, 0)) / (float)JOYAXISRANGE, + (G_PlayerInputAnalog(pid, gc_up, 0) - G_PlayerInputAnalog(pid, gc_down, 0)) / (float)JOYAXISRANGE, + } : + Vec2 { + -cmd.turning / (float)KART_FULLTURN, + (float)cmd.throwdir, + }; + + box.patch(gfx("PAD{}", dpad_suffix(dpad))); + box.patch(but('A', gc_a, BT_ACCELERATE)); + box.patch(but('B', gc_b, BT_LOOKBACK)); + box.patch(but('C', gc_c, BT_SPINDASHMASK)); + box.patch(but('X', gc_x, BT_BRAKE)); + box.patch(but('Y', gc_y, BT_RESPAWN)); + box.patch(but('Z', gc_z, BT_VOTE)); + box.patch(but('L', gc_l, BT_ATTACK)); + box.patch(but('R', gc_r, BT_DRIFT)); + box.patch(but('S', gc_start, 0xFFFFFFFF)); + + if (mode == '4' || mode == '5') // Saturn 3D + { + float dist = (mode == '4') ? 3.f : 2.f; + + box.patch(gfx("JOY1")); + box.xy(dpad.x * dist, -dpad.y * dist).patch(gfx("JOY2")); + } +} diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 7eb39841f..16901f3f0 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -50,6 +50,7 @@ #include "k_rank.h" #include "g_party.h" #include "k_hitlag.h" +#include "g_input.h" //{ Patch Definitions static patch_t *kp_nodraw; @@ -5160,85 +5161,27 @@ static void K_drawKartFirstPerson(void) } } -// doesn't need to ever support 4p static void K_drawInput(void) { - static INT32 pn = 0; - INT32 target = 0, splitflags = (V_SNAPTOBOTTOM|V_SNAPTORIGHT); - INT32 x = BASEVIDWIDTH - 32, y = BASEVIDHEIGHT-24, offs, col; - const INT32 accent1 = splitflags | skincolors[stplyr->skincolor].ramp[5]; - const INT32 accent2 = splitflags | skincolors[stplyr->skincolor].ramp[9]; - ticcmd_t *cmd = &stplyr->cmd; - -#define BUTTW 8 -#define BUTTH 11 - -#define drawbutt(xoffs, butt, symb)\ - if (!stplyr->exiting && (cmd->buttons & butt))\ - {\ - offs = 2;\ - col = accent1;\ - }\ - else\ - {\ - offs = 0;\ - col = accent2;\ - V_DrawFill(x+(xoffs), y+BUTTH, BUTTW-1, 2, splitflags|31);\ - }\ - V_DrawFill(x+(xoffs), y+offs, BUTTW-1, BUTTH, col);\ - V_DrawFixedPatch((x+1+(xoffs))<exiting || !stplyr->steering) // no turn - target = 0; - else // turning of multiple strengths! - { - target = ((abs(stplyr->steering) - 1)/125)+1; - if (target > 4) - target = 4; - if (stplyr->steering < 0) - target = -target; - } - - if (pn != target) - { - if (abs(pn - target) == 1) - pn = target; - else if (pn < target) - pn += 2; - else //if (pn > target) - pn -= 2; - } - - if (pn < 0) - { - splitflags |= V_FLIP; // right turn - x--; - } - - target = abs(pn); - if (target > 4) - target = 4; - - if (!stplyr->skincolor) - V_DrawFixedPatch(x<(stplyr->skincolor), GTC_CACHE); - V_DrawFixedPatch(x<pflags & PF_ANALOGSTICK) ? '4' : '2') + (r_splitscreen > 1); + bool local = !demo.playback && P_IsMachineLocalPlayer(stplyr); + K_DrawInputDisplay( + def[k][0], + def[k][1], + flags, + mode, + (local ? G_LocalSplitscreenPartyPosition : G_PartyPosition)(stplyr - players), + local, + stplyr->speed > 0 + ); } static void K_drawChallengerScreen(void) diff --git a/src/k_hud.h b/src/k_hud.h index 95fd79931..422f3559f 100644 --- a/src/k_hud.h +++ b/src/k_hud.h @@ -63,6 +63,8 @@ void K_DrawKartPositionNumXY( boolean exit, boolean lastLap, boolean losing ); +void K_DrawInputDisplay(INT32 x, INT32 y, INT32 flags, char mode, UINT8 pid, boolean local, boolean transparent); + extern patch_t *kp_capsuletarget_arrow[2][2]; extern patch_t *kp_capsuletarget_icon[2]; extern patch_t *kp_capsuletarget_far[2][2]; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 091149e25..fcca6aad0 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -4742,52 +4742,6 @@ INT16 controlleroffsets[][2] = { {149, 187}, // gc_start }; -// Controller patches for button presses. -// reminder that lumpnames can only be 8 chars at most. (+1 for \0) - -static const char *controllerpresspatch[9][2] = { - {"PR_BTA", "PR_BTAB"}, // MBT_A - {"PR_BTB", "PR_BTBB"}, // MBT_B - {"PR_BTC", "PR_BTCB"}, // MBT_C - {"PR_BTX", "PR_BTXB"}, // MBT_X - {"PR_BTY", "PR_BTYB"}, // MBT_Y - {"PR_BTZ", "PR_BTZB"}, // MBT_Z - {"PR_BTL", "PR_BTLB"}, // MBT_L - {"PR_BTR", "PR_BTRB"}, // MBT_R - {"PR_BTS", "PR_BTSB"}, // MBT_START -}; - -static const char *M_GetDPadPatchName(SINT8 ud, SINT8 lr) -{ - if (ud < 0) - { - if (lr < 0) - return "PR_PADUL"; - else if (lr > 0) - return "PR_PADUR"; - else - return "PR_PADU"; - } - else if (ud > 0) - { - if (lr < 0) - return "PR_PADDL"; - else if (lr > 0) - return "PR_PADDR"; - else - return "PR_PADD"; - } - else - { - if (lr < 0) - return "PR_PADL"; - else if (lr > 0) - return "PR_PADR"; - else - return "PR_PADN"; - } -} - static void M_DrawBindBen(INT32 x, INT32 y, INT32 scroll_remaining) { // optionsmenu.bindben_swallow @@ -4896,26 +4850,7 @@ void M_DrawProfileControls(void) patch_t *hint = W_CachePatchName("MENUHINT", PU_CACHE); INT32 hintofs = 3; - V_DrawScaledPatch(BASEVIDWIDTH*2/3 - optionsmenu.contx, BASEVIDHEIGHT/2 -optionsmenu.conty, 0, W_CachePatchName("PR_CONT", PU_CACHE)); - - // Draw button presses... - V_DrawScaledPatch( - BASEVIDWIDTH*2/3 - optionsmenu.contx, - BASEVIDHEIGHT/2 - optionsmenu.conty, - 0, - W_CachePatchName(M_GetDPadPatchName(menucmd[pid].dpad_ud, menucmd[pid].dpad_lr), PU_CACHE) - ); - - for (i = 0; i < 9; i++) - { - INT32 bt = 1<rumble); // set controls... - memcpy(&gamecontrol[playernum], p->controls, sizeof(gamecontroldefault)); + G_ApplyControlScheme(playernum, p->controls); } static void PR_ApplyProfile_Memory(UINT8 profilenum, UINT8 playernum) diff --git a/src/m_misc.cpp b/src/m_misc.cpp index 4a494f335..ce4eda4c0 100644 --- a/src/m_misc.cpp +++ b/src/m_misc.cpp @@ -577,7 +577,7 @@ void Command_LoadConfig_f(void) for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) { - G_CopyControls(gamecontrol[i], gamecontroldefault, NULL, 0); + G_ApplyControlScheme(i, gamecontroldefault); } // temporarily reset execversion to default @@ -631,7 +631,7 @@ void M_FirstLoadConfig(void) for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) { - G_CopyControls(gamecontrol[i], gamecontroldefault, NULL, 0); + G_ApplyControlScheme(i, gamecontroldefault); } // register execversion here before we load any configs diff --git a/src/menus/options-profiles-edit-controls.c b/src/menus/options-profiles-edit-controls.c index 15064b7bb..9adac3dd1 100644 --- a/src/menus/options-profiles-edit-controls.c +++ b/src/menus/options-profiles-edit-controls.c @@ -219,7 +219,7 @@ void M_ProfileTryController(INT32 choice) optionsmenu.trycontroller = TICRATE*5; // Apply these controls right now on P1's end. - memcpy(&gamecontrol[0], optionsmenu.tempcontrols, sizeof(gamecontroldefault)); + G_ApplyControlScheme(0, optionsmenu.tempcontrols); } static void M_ProfileControlSaveResponse(INT32 choice) @@ -234,7 +234,7 @@ static void M_ProfileControlSaveResponse(INT32 choice) // Don't apply the profile itself as that would lead to issues mid-game. if (belongsto > -1 && belongsto < MAXSPLITSCREENPLAYERS) { - memcpy(&gamecontrol[belongsto], optionsmenu.tempcontrols, sizeof(gamecontroldefault)); + G_ApplyControlScheme(belongsto, optionsmenu.tempcontrols); } } else @@ -325,7 +325,7 @@ boolean M_ProfileControlsInputs(INT32 ch) profile_t *cpr = PR_GetProfile(cv_currprofile.value); if (cpr == NULL) cpr = PR_GetProfile(0); // Creating a profile at boot, revert to guest profile - memcpy(&gamecontrol[0], cpr->controls, sizeof(gamecontroldefault)); + G_ApplyControlScheme(0, cpr->controls); } return true; diff --git a/src/menus/play-char-select.c b/src/menus/play-char-select.c index 1fbdccbb0..80352f312 100644 --- a/src/menus/play-char-select.c +++ b/src/menus/play-char-select.c @@ -502,7 +502,7 @@ static boolean M_HandlePressStart(setup_player_t *p, UINT8 num) else if (num) { // For any player past player 1, set controls to default profile controls, otherwise it's generally awful to do any menuing... - memcpy(&gamecontrol[num], gamecontroldefault, sizeof(gamecontroldefault)); + G_ApplyControlScheme(num, gamecontroldefault); } G_SetDeviceForPlayer(num, device); diff --git a/src/v_draw.hpp b/src/v_draw.hpp index c81a7be45..2f6be5d25 100644 --- a/src/v_draw.hpp +++ b/src/v_draw.hpp @@ -180,6 +180,7 @@ public: void patch(patch_t* patch) const; void patch(const char* name) const { patch(Draw::cache_patch(name)); } + void patch(const std::string& name) const { patch(name.c_str()); } void thumbnail(UINT16 mapnum) const;