diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b83e9099e..4f65b3cef 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -146,6 +146,7 @@ set(WOOF_SOURCES w_zip.c wi_stuff.c wi_stuff.h wi_interlvl.c wi_interlvl.h + ws_stuff.c ws_stuff.h z_zone.c z_zone.h) # Standard target definition diff --git a/src/am_map.c b/src/am_map.c index b4557f325..51bd00722 100644 --- a/src/am_map.c +++ b/src/am_map.c @@ -48,6 +48,7 @@ #include "v_flextran.h" #include "v_fmt.h" #include "v_video.h" +#include "ws_stuff.h" #include "z_zone.h" //jff 1/7/98 default automap colors added @@ -794,7 +795,7 @@ boolean AM_Responder if (!automapactive) { - if (M_InputActivated(input_map)) + if (M_InputActivated(input_map) && !WS_Override()) { AM_Start (); viewactive = false; @@ -808,22 +809,22 @@ boolean AM_Responder rc = true; // phares if (M_InputActivated(input_map_right)) // | - if (!followplayer) // V + if (!followplayer && !WS_HoldOverride()) // V buttons_state[PAN_RIGHT] = 1; else rc = false; else if (M_InputActivated(input_map_left)) - if (!followplayer) + if (!followplayer && !WS_HoldOverride()) buttons_state[PAN_LEFT] = 1; else rc = false; else if (M_InputActivated(input_map_up)) - if (!followplayer) + if (!followplayer && !WS_HoldOverride()) buttons_state[PAN_UP] = 1; else rc = false; else if (M_InputActivated(input_map_down)) - if (!followplayer) + if (!followplayer && !WS_HoldOverride()) buttons_state[PAN_DOWN] = 1; else rc = false; @@ -851,9 +852,16 @@ boolean AM_Responder } else if (M_InputActivated(input_map)) { - bigstate = 0; - viewactive = true; - AM_Stop (); + if (!WS_Override()) + { + bigstate = 0; + viewactive = true; + AM_Stop (); + } + else + { + rc = false; + } } else if (M_InputActivated(input_map_gobig)) { diff --git a/src/d_main.c b/src/d_main.c index 3c7f0666a..59ff075db 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -84,6 +84,7 @@ #include "v_video.h" #include "w_wad.h" #include "wi_stuff.h" +#include "ws_stuff.h" #include "z_zone.h" // DEHacked support - Ty 03/09/97 @@ -2439,6 +2440,7 @@ void D_DoomMain(void) G_UpdateGamepadVariables(); G_UpdateMouseVariables(); R_UpdateViewAngleFunction(); + WS_Init(); MN_ResetTimeScale(); diff --git a/src/doomkeys.h b/src/doomkeys.h index f94fe0e6b..751057d63 100644 --- a/src/doomkeys.h +++ b/src/doomkeys.h @@ -24,6 +24,7 @@ // This is the stuff configured by Setup.Exe. // Most key data are simple ascii (uppercased). // +#define NUMKEYS 256 #define KEY_RIGHTARROW 0xae #define KEY_LEFTARROW 0xac #define KEY_UPARROW 0xad diff --git a/src/g_game.c b/src/g_game.c index 9ba6929e5..9143f4d0b 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -90,6 +90,7 @@ #include "version.h" #include "w_wad.h" #include "wi_stuff.h" +#include "ws_stuff.h" #include "z_zone.h" #define SAVEGAMESIZE 0x20000 @@ -188,7 +189,6 @@ static boolean dclick_use; #define TURBOTHRESHOLD 0x32 #define SLOWTURNTICS 6 #define QUICKREVERSE 32768 // 180 degree reverse // phares -#define NUMKEYS 256 fixed_t forwardmove[2] = {0x19, 0x32}; fixed_t default_sidemove[2] = {0x18, 0x28}; @@ -857,6 +857,10 @@ void G_BuildTiccmd(ticcmd_t* cmd) boom_weapon_state_injection = false; newweapon = P_SwitchWeapon(&players[consoleplayer]); // phares } + else if (WS_SlotSelected()) + { + newweapon = WS_SlotWeapon(); + } else if (M_InputGameActive(input_lastweapon)) { newweapon = LastWeapon(); @@ -899,6 +903,7 @@ void G_BuildTiccmd(ticcmd_t* cmd) // [FG] prev/next weapon keys and buttons next_weapon = 0; + WS_ClearSharedEvent(); // [FG] double click acts as "use" if (dclick) @@ -946,6 +951,7 @@ void G_ClearInput(void) memset(&basecmd, 0, sizeof(basecmd)); I_ResetRelativeMouseState(); I_ResetAllRumbleChannels(); + WS_Reset(); } // @@ -1316,6 +1322,23 @@ boolean G_MovementResponder(event_t *ev) boolean G_Responder(event_t* ev) { + WS_UpdateState(ev); + + // killough 9/29/98: reformatted + if (gamestate == GS_LEVEL + && (HU_Responder(ev) || // chat ate the event + ST_Responder(ev) || // status window ate it + AM_Responder(ev) || // automap ate it + WS_Responder(ev))) // weapon slots ate it + { + return true; + } + + if (M_ShortcutResponder(ev)) + { + return true; + } + // allow spy mode changes even during the demo // killough 2/22/98: even during DM demo // @@ -1346,12 +1369,6 @@ boolean G_Responder(event_t* ev) return true; } - // killough 9/29/98: reformatted - if (gamestate == GS_LEVEL && (HU_Responder(ev) || // chat ate the event - ST_Responder(ev) || // status window ate it - AM_Responder(ev))) // automap ate it - return true; - // any other key pops up menu if in demos // // killough 8/2/98: enable automap in -timedemo demos diff --git a/src/i_input.c b/src/i_input.c index 8d471fc41..d7dc4ac38 100644 --- a/src/i_input.c +++ b/src/i_input.c @@ -35,6 +35,7 @@ static SDL_GameController *gamepad; static boolean gyro_supported; +static joy_platform_t platform; // [FG] adapt joystick button and axis handling from Chocolate Doom 3.0 @@ -265,9 +266,27 @@ static joy_platform_t GetSwitchSubPlatform(void) return PLATFORM_SWITCH_PRO; } +void I_GetFaceButtons(int *buttons) +{ + if (platform < PLATFORM_SWITCH) + { + buttons[0] = GAMEPAD_Y; + buttons[1] = GAMEPAD_A; + buttons[2] = GAMEPAD_X; + buttons[3] = GAMEPAD_B; + } + else + { + buttons[0] = GAMEPAD_X; + buttons[1] = GAMEPAD_B; + buttons[2] = GAMEPAD_Y; + buttons[3] = GAMEPAD_A; + } +} + static void UpdatePlatform(void) { - joy_platform_t platform = joy_platform; + platform = joy_platform; if (platform == PLATFORM_AUTO) { diff --git a/src/i_input.h b/src/i_input.h index 710d1fc97..d4fcdc3b4 100644 --- a/src/i_input.h +++ b/src/i_input.h @@ -26,6 +26,7 @@ enum evtype_e; int I_GetAxisState(int axis); boolean I_UseGamepad(void); boolean I_GyroSupported(void); +void I_GetFaceButtons(int *buttons); void I_FlushGamepadSensorEvents(void); void I_FlushGamepadEvents(void); void I_SetSensorEventState(boolean condition); diff --git a/src/m_cheat.c b/src/m_cheat.c index a8ef67eb5..66f95a78f 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -52,6 +52,7 @@ #include "tables.h" #include "u_mapinfo.h" #include "w_wad.h" +#include "ws_stuff.h" #define plyr (players+consoleplayer) /* the console player */ @@ -1312,6 +1313,9 @@ boolean M_CheatResponder(event_t *ev) if (ev->type == ev_keydown && M_FindCheats(ev->data1.i)) return true; + if (WS_Override()) + return false; + for (i = 0; i < arrlen(cheat_input); ++i) { if (M_InputActivated(cheat_input[i].input)) diff --git a/src/m_config.c b/src/m_config.c index 73b5e71ee..a37866e77 100644 --- a/src/m_config.c +++ b/src/m_config.c @@ -55,6 +55,7 @@ #include "r_main.h" #include "st_stuff.h" #include "w_wad.h" +#include "ws_stuff.h" #include "z_zone.h" // @@ -133,6 +134,7 @@ void M_InitConfig(void) G_BindEnemVariables(); G_BindCompVariables(); G_BindWeapVariables(); + WS_BindVariables(); HU_BindHUDVariables(); ST_BindSTSVariables(); diff --git a/src/mn_menu.c b/src/mn_menu.c index 9b20fecf4..b36f62be7 100644 --- a/src/mn_menu.c +++ b/src/mn_menu.c @@ -2108,7 +2108,7 @@ void M_Ticker(void) // action based on the state of the system. // -static boolean ShortcutResponder(const event_t *ev) +boolean M_ShortcutResponder(const event_t *ev) { // If there is no active menu displayed... @@ -2823,11 +2823,6 @@ boolean M_Responder(event_t *ev) G_ScreenShot(); } - if (ShortcutResponder(ev)) - { - return true; - } - // Pop-up Main menu? if (!menuactive) diff --git a/src/mn_menu.h b/src/mn_menu.h index f4ccb5760..b425088ef 100644 --- a/src/mn_menu.h +++ b/src/mn_menu.h @@ -35,6 +35,8 @@ struct event_s; boolean M_Responder(struct event_s *ev); +boolean M_ShortcutResponder(const struct event_s *ev); + // Called by main loop, // only used for menu (skull cursor) animation. diff --git a/src/mn_setup.c b/src/mn_setup.c index e58b55e3d..b82382311 100644 --- a/src/mn_setup.c +++ b/src/mn_setup.c @@ -57,6 +57,7 @@ #include "v_fmt.h" #include "v_video.h" #include "w_wad.h" +#include "ws_stuff.h" #include "z_zone.h" static int M_GetKeyString(int c, int offset); @@ -132,6 +133,9 @@ static boolean default_reset; #define MI_GAP \ {NULL, S_SKIP, 0, M_SPC} +#define MI_GAP_HALF \ + {NULL, S_SKIP, 0, M_SPC / 2} + static void DisableItem(boolean condition, setup_menu_t *menu, const char *item) { while (!(menu->m_flags & S_END)) @@ -322,6 +326,9 @@ enum str_overlay, str_automap_preset, str_automap_keyed_door, + str_weapon_slots_activation, + str_weapon_slots_selection, + str_weapon_slots, str_resolution_scale, str_midi_player, @@ -1508,6 +1515,7 @@ void MN_DrawKeybnd(void) static setup_tab_t weap_tabs[] = { {"cosmetic"}, + {"slots"}, {"preferences"}, {NULL} }; @@ -1542,7 +1550,160 @@ static setup_menu_t weap_settings1[] = { MI_END }; +static const char *weapon_slots_activation_strings[] = { + "Off", "Hold \"Last\"", "Always On" +}; + +static const char *weapon_slots_selection_strings[] = { + "D-Pad", "Face Buttons", "1-4 Keys" +}; + +static const char **GetWeaponSlotStrings(void) +{ + static const char *vanilla_doom_strings[] = { + "--", "Chainsaw/Fist", "Pistol", "Shotgun", "Chaingun", + "Rocket", "Plasma", "BFG", "Chainsaw/Fist", "Shotgun" + }; + static const char *vanilla_doom2_strings[] = { + "--", "Chainsaw/Fist", "Pistol", "SSG/Shotgun", "Chaingun", + "Rocket", "Plasma", "BFG", "Chainsaw/Fist", "SSG/Shotgun" + }; + static const char *full_doom2_strings[] = { + "--", "Fist", "Pistol", "Shotgun", "Chaingun", + "Rocket", "Plasma", "BFG", "Chainsaw", "SSG" + }; + + if (force_complevel == CL_VANILLA || default_complevel == CL_VANILLA) + { + return (ALLOW_SSG ? vanilla_doom2_strings : vanilla_doom_strings); + } + else + { + return full_doom2_strings; + } +} + +#define WS_BUF_SiZE 80 +static char slot_labels[NUM_WS_SLOTS * NUM_WS_WEAPS][WS_BUF_SiZE]; + +static void UpdateWeaponSlotLabels(void) +{ + const char *keys[NUM_WS_SLOTS]; + int buttons[NUM_WS_SLOTS]; + + switch (WS_Selection()) + { + case WS_SELECT_DPAD: + keys[0] = M_GetPlatformName(GAMEPAD_DPAD_UP); + keys[1] = M_GetPlatformName(GAMEPAD_DPAD_DOWN); + keys[2] = M_GetPlatformName(GAMEPAD_DPAD_LEFT); + keys[3] = M_GetPlatformName(GAMEPAD_DPAD_RIGHT); + break; + + case WS_SELECT_FACE_BUTTONS: + I_GetFaceButtons(buttons); + keys[0] = M_GetPlatformName(buttons[0]); + keys[1] = M_GetPlatformName(buttons[1]); + keys[2] = M_GetPlatformName(buttons[2]); + keys[3] = M_GetPlatformName(buttons[3]); + break; + + default: // WS_SELECT_1234 + keys[0] = "1-Key"; + keys[1] = "2-Key"; + keys[2] = "3-Key"; + keys[3] = "4-Key"; + break; + } + + const char *pos[NUM_WS_WEAPS] = {"1st", "2nd", "3rd"}; + int num = 0; + + for (int i = 0; i < NUM_WS_SLOTS; i++) + { + M_snprintf(slot_labels[num++], WS_BUF_SiZE, "%s %s", keys[i], pos[0]); + + for (int j = 1; j < NUM_WS_WEAPS; j++) + { + M_snprintf(slot_labels[num++], WS_BUF_SiZE, "%s", pos[j]); + } + } +} + +static void UpdateWeaponSlotItems(void); + +static void UpdateWeaponSlotActivation(void) +{ + WS_Reset(); + UpdateWeaponSlotItems(); +} + +static void UpdateWeaponSlotSelection(void) +{ + WS_UpdateSelection(); + WS_Reset(); + UpdateWeaponSlotLabels(); +} + +static void UpdateWeaponSlots(void) +{ + WS_UpdateSlots(); + WS_Reset(); +} + +#define MI_WEAPON_SLOT(i, s) \ + {slot_labels[i], S_CHOICE, CNTR_X, M_SPC, {s}, \ + .strings_id = str_weapon_slots, .action = UpdateWeaponSlots} + static setup_menu_t weap_settings2[] = { + + {"Enable Slots", S_CHOICE, CNTR_X, M_SPC, {"weapon_slots_activation"}, + .strings_id = str_weapon_slots_activation, + .action = UpdateWeaponSlotActivation}, + + {"Select Slots", S_CHOICE, CNTR_X, M_SPC, {"weapon_slots_selection"}, + .strings_id = str_weapon_slots_selection, + .action = UpdateWeaponSlotSelection}, + + MI_GAP_HALF, + MI_WEAPON_SLOT(0, "weapon_slots_1_1"), + MI_WEAPON_SLOT(1, "weapon_slots_1_2"), + MI_WEAPON_SLOT(2, "weapon_slots_1_3"), + MI_GAP_HALF, + MI_WEAPON_SLOT(3, "weapon_slots_2_1"), + MI_WEAPON_SLOT(4, "weapon_slots_2_2"), + MI_WEAPON_SLOT(5, "weapon_slots_2_3"), + MI_GAP_HALF, + MI_WEAPON_SLOT(6, "weapon_slots_3_1"), + MI_WEAPON_SLOT(7, "weapon_slots_3_2"), + MI_WEAPON_SLOT(8, "weapon_slots_3_3"), + MI_GAP_HALF, + MI_WEAPON_SLOT(9, "weapon_slots_4_1"), + MI_WEAPON_SLOT(10, "weapon_slots_4_2"), + MI_WEAPON_SLOT(11, "weapon_slots_4_3"), + MI_END +}; + +static void UpdateWeaponSlotItems(void) +{ + const boolean condition = !WS_Enabled(); + + DisableItem(condition, weap_settings2, "weapon_slots_selection"); + DisableItem(condition, weap_settings2, "weapon_slots_1_1"); + DisableItem(condition, weap_settings2, "weapon_slots_1_2"); + DisableItem(condition, weap_settings2, "weapon_slots_1_3"); + DisableItem(condition, weap_settings2, "weapon_slots_2_1"); + DisableItem(condition, weap_settings2, "weapon_slots_2_2"); + DisableItem(condition, weap_settings2, "weapon_slots_2_3"); + DisableItem(condition, weap_settings2, "weapon_slots_3_1"); + DisableItem(condition, weap_settings2, "weapon_slots_3_2"); + DisableItem(condition, weap_settings2, "weapon_slots_3_3"); + DisableItem(condition, weap_settings2, "weapon_slots_4_1"); + DisableItem(condition, weap_settings2, "weapon_slots_4_2"); + DisableItem(condition, weap_settings2, "weapon_slots_4_3"); +} + +static setup_menu_t weap_settings3[] = { {"1St Choice Weapon", S_WEAP | S_BOOM, M_X, M_SPC, {"weapon_choice_1"}}, {"2Nd Choice Weapon", S_WEAP | S_BOOM, M_X, M_SPC, {"weapon_choice_2"}}, {"3Rd Choice Weapon", S_WEAP | S_BOOM, M_X, M_SPC, {"weapon_choice_3"}}, @@ -1561,7 +1722,9 @@ static setup_menu_t weap_settings2[] = { MI_END }; -static setup_menu_t *weap_settings[] = {weap_settings1, weap_settings2, NULL}; +static setup_menu_t *weap_settings[] = { + weap_settings1, weap_settings2, weap_settings3, NULL +}; static void UpdateCenteredWeaponItem(void) { @@ -2013,12 +2176,19 @@ static const char *default_complevel_strings[] = { }; static void UpdateInterceptsEmuItem(void); +static void UpdateWeaponSlotStrings(void); + +static void UpdateDefaultCompatibilityLevel(void) +{ + UpdateInterceptsEmuItem(); + UpdateWeaponSlotStrings(); +} setup_menu_t comp_settings1[] = { {"Default Compatibility Level", S_CHOICE | S_LEVWARN, M_X, M_SPC, {"default_complevel"}, .strings_id = str_default_complevel, - .action = UpdateInterceptsEmuItem}, + .action = UpdateDefaultCompatibilityLevel}, {"Strict Mode", S_ONOFF | S_LEVWARN, M_X, M_SPC, {"strictmode"}}, @@ -2383,18 +2553,17 @@ static const char *equalizer_preset_strings[] = { #define M_THRM_SPC_EQ (M_THRM_HEIGHT - 1) #define M_SPC_EQ 8 -#define MI_GAP_EQ {NULL, S_SKIP, 0, 4} static setup_menu_t eq_settings1[] = { {"Preset", S_CHOICE, CNTR_X, M_SPC_EQ, {"snd_equalizer"}, .strings_id = str_equalizer_preset, .action = I_OAL_EqualizerPreset}, - MI_GAP_EQ, + MI_GAP_HALF, {"Preamp dB", S_THERMO, CNTR_X, M_THRM_SPC_EQ, {"snd_eq_preamp"}, .action = I_OAL_EqualizerPreset}, - MI_GAP_EQ, + MI_GAP_HALF, {"Low Gain dB", S_THERMO, CNTR_X, M_THRM_SPC_EQ, {"snd_eq_low_gain"}, .action = I_OAL_EqualizerPreset}, @@ -2408,7 +2577,7 @@ static setup_menu_t eq_settings1[] = { {"High Gain dB", S_THERMO, CNTR_X, M_THRM_SPC_EQ, {"snd_eq_high_gain"}, .action = I_OAL_EqualizerPreset}, - MI_GAP_EQ, + MI_GAP_HALF, {"Low Cutoff Hz", S_THERMO, CNTR_X, M_THRM_SPC_EQ, {"snd_eq_low_cutoff"}, .action = I_OAL_EqualizerPreset}, @@ -2863,6 +3032,7 @@ static void UpdateGyroItems(void) void MN_UpdateAllGamepadItems(void) { + UpdateWeaponSlotSelection(); UpdateGamepadItems(); UpdateGyroItems(); } @@ -4488,6 +4658,9 @@ static const char **selectstrings[] = { overlay_strings, automap_preset_strings, automap_keyed_door_strings, + weapon_slots_activation_strings, + weapon_slots_selection_strings, + NULL, // str_weapon_slots NULL, // str_resolution_scale NULL, // str_midi_player gamma_strings, @@ -4524,6 +4697,11 @@ static void UpdateHUDModeStrings(void) selectstrings[str_hudmode] = GetHUDModeStrings(); } +static void UpdateWeaponSlotStrings(void) +{ + selectstrings[str_weapon_slots] = GetWeaponSlotStrings(); +} + static const char **GetMidiPlayerStrings(void) { return I_DeviceList(); @@ -4532,6 +4710,8 @@ static const char **GetMidiPlayerStrings(void) void MN_InitMenuStrings(void) { UpdateHUDModeStrings(); + UpdateWeaponSlotLabels(); + UpdateWeaponSlotStrings(); selectstrings[str_resolution_scale] = GetResolutionScaleStrings(); selectstrings[str_midi_player] = GetMidiPlayerStrings(); selectstrings[str_mouse_accel] = GetMouseAccelStrings(); @@ -4554,7 +4734,9 @@ void MN_SetupResetMenu(void) UpdateInterceptsEmuItem(); UpdateCrosshairItems(); UpdateCenteredWeaponItem(); - MN_UpdateAllGamepadItems(); + UpdateGamepadItems(); + UpdateGyroItems(); + UpdateWeaponSlotItems(); MN_UpdateEqualizerItems(); } diff --git a/src/ws_stuff.c b/src/ws_stuff.c new file mode 100644 index 000000000..a9ad8c19c --- /dev/null +++ b/src/ws_stuff.c @@ -0,0 +1,774 @@ +// +// Copyright(C) 2024 ceski +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Weapon slots. +// + +#include "SDL.h" + +#include "g_game.h" +#include "i_input.h" +#include "i_timer.h" +#include "m_config.h" +#include "m_input.h" +#include "r_main.h" +#include "ws_stuff.h" + +typedef enum +{ + WS_OFF, + WS_HOLD_LAST, + WS_ALWAYS_ON, + NUM_WS_ACTIVATION +} weapon_slots_activation_t; + +typedef enum +{ + WS_TYPE_NONE, + WS_TYPE_FIST, + WS_TYPE_PISTOL, + WS_TYPE_SHOTGUN, + WS_TYPE_CHAINGUN, + WS_TYPE_ROCKET, + WS_TYPE_PLASMA, + WS_TYPE_BFG, + WS_TYPE_CHAINSAW, + WS_TYPE_SSG, + NUM_WS_TYPES +} weapon_slots_type_t; + +static weapon_slots_activation_t weapon_slots_activation; +static weapon_slots_selection_t weapon_slots_selection; +static weapon_slots_type_t weapon_slots_1_1; +static weapon_slots_type_t weapon_slots_1_2; +static weapon_slots_type_t weapon_slots_1_3; +static weapon_slots_type_t weapon_slots_2_1; +static weapon_slots_type_t weapon_slots_2_2; +static weapon_slots_type_t weapon_slots_2_3; +static weapon_slots_type_t weapon_slots_3_1; +static weapon_slots_type_t weapon_slots_3_2; +static weapon_slots_type_t weapon_slots_3_3; +static weapon_slots_type_t weapon_slots_4_1; +static weapon_slots_type_t weapon_slots_4_2; +static weapon_slots_type_t weapon_slots_4_3; + +typedef struct +{ + int time; // Time when activator was pressed (tics). + int input_key; // Key that triggered this event (doomkeys.h). + boolean *state; // Pointer to input key's on/off state. + boolean restored; // Was this event restored? +} shared_event_t; + +typedef struct +{ + weapontype_t weapons[NUM_WS_WEAPS]; // Weapons for this slot. + int num_weapons; // Number of weapons in this slot. + int input_key; // Key that selects slot (doomkeys.h). +} weapon_slot_t; + +typedef struct +{ + boolean enabled; // State initially updated in WS_UpdateState. + boolean key_match; // Event input key matches any slot's input key? + int index; // Index of the slot that matches the event. + boolean override; // Give gamepad higher priority. + boolean hold_override; // Give gamepad higher priority with hold "last". + + boolean activated; // Activated weapon slots? + boolean pressed; // Pressed a weapon slot while activated? + boolean selected; // Selected a weapon slot while activated? + int input_key; // Input key for selected slot, tracked separately. + const weapon_slot_t *current_slot; // Slot selected while activated. +} weapon_slots_state_t; + +static shared_event_t shared_event; // Event shared with activator. +static weapon_slot_t slots[NUM_WS_SLOTS]; // Parameters for each slot. +static weapon_slots_state_t state; // Weapon slots state. + +static void ResetState(void) +{ + state.enabled = false; + state.key_match = false; + state.index = -1; + state.override = false; + state.hold_override = false; + + state.activated = false; + state.pressed = false; + state.selected = false; + state.input_key = -1; + state.current_slot = NULL; +} + +static void ResetSharedEvent(void) +{ + if (shared_event.state != NULL) + { + *shared_event.state = false; + shared_event.state = NULL; + } + + shared_event.time = 0; + shared_event.input_key = -1; + shared_event.restored = false; +} + +// +// WS_Reset +// +void WS_Reset(void) +{ + ResetSharedEvent(); + ResetState(); +} + +// +// WS_Enabled +// +boolean WS_Enabled(void) +{ + return (weapon_slots_activation != WS_OFF); +} + +// +// WS_Selection +// +weapon_slots_selection_t WS_Selection(void) +{ + return weapon_slots_selection; +} + +// +// WS_UpdateSelection +// +void WS_UpdateSelection(void) +{ + int buttons[NUM_WS_SLOTS]; + const int translate[] = SCANCODE_TO_KEYS_ARRAY; + + switch (weapon_slots_selection) + { + case WS_SELECT_DPAD: + slots[0].input_key = GAMEPAD_DPAD_UP; + slots[1].input_key = GAMEPAD_DPAD_DOWN; + slots[2].input_key = GAMEPAD_DPAD_LEFT; + slots[3].input_key = GAMEPAD_DPAD_RIGHT; + break; + + case WS_SELECT_FACE_BUTTONS: + I_GetFaceButtons(buttons); + slots[0].input_key = buttons[0]; + slots[1].input_key = buttons[1]; + slots[2].input_key = buttons[2]; + slots[3].input_key = buttons[3]; + break; + + default: // WS_SELECT_1234 + slots[0].input_key = translate[SDL_SCANCODE_1]; + slots[1].input_key = translate[SDL_SCANCODE_2]; + slots[2].input_key = translate[SDL_SCANCODE_3]; + slots[3].input_key = translate[SDL_SCANCODE_4]; + break; + } +} + +// +// WS_UpdateSlots +// +void WS_UpdateSlots(void) +{ + const weapon_slots_type_t types[NUM_WS_SLOTS][NUM_WS_WEAPS] = { + {weapon_slots_1_1, weapon_slots_1_2, weapon_slots_1_3}, + {weapon_slots_2_1, weapon_slots_2_2, weapon_slots_2_3}, + {weapon_slots_3_1, weapon_slots_3_2, weapon_slots_3_3}, + {weapon_slots_4_1, weapon_slots_4_2, weapon_slots_4_3}, + }; + + for (int i = 0; i < NUM_WS_SLOTS; i++) + { + int count[NUMWEAPONS] = {0}; + slots[i].num_weapons = 0; + + for (int j = 0; j < NUM_WS_WEAPS; j++) + { + if (types[i][j] == WS_TYPE_NONE) + { + continue; // Skip "none" weapon type. + } + + const weapontype_t weapon = types[i][j] - WS_TYPE_FIST; + + if (++count[weapon] > 1) + { + continue; // Skip duplicate weapons. + } + + slots[i].weapons[slots[i].num_weapons++] = weapon; + } + } +} + +// +// WS_Init +// +void WS_Init(void) +{ + WS_UpdateSelection(); + WS_UpdateSlots(); + WS_Reset(); +} + +// +// Weapon slots responder functions. +// + +static boolean SearchSlotMatch(const event_t *ev, int *index) +{ + for (int i = 0; i < NUM_WS_SLOTS; i++) + { + if (ev->data1.i == slots[i].input_key) + { + *index = i; + return true; + } + } + + *index = -1; + return false; +} + +static boolean InputKeyMatch(const event_t *ev, int *index) +{ + switch (ev->type) + { + case ev_joyb_down: + case ev_joyb_up: + if (weapon_slots_selection != WS_SELECT_1234) + { + return SearchSlotMatch(ev, index); + } + break; + + case ev_keydown: + case ev_keyup: + if (weapon_slots_selection == WS_SELECT_1234) + { + return SearchSlotMatch(ev, index); + } + break; + + default: + break; + } + + *index = -1; + return false; +} + +// +// WS_UpdateState +// +void WS_UpdateState(const event_t *ev) +{ + if (WS_Enabled() + // If using gamepad weapon slots, then also using a gamepad. + && (weapon_slots_selection == WS_SELECT_1234 || I_UseGamepad()) + // Playing the game. + && (!menuactive && !demoplayback && gamestate == GS_LEVEL && !paused)) + { + state.enabled = true; + state.key_match = (InputKeyMatch(ev, &state.index) + && slots[state.index].num_weapons > 0); + + const boolean override = ( + // Only a gamepad can override other responders. + (I_UseGamepad() && weapon_slots_selection != WS_SELECT_1234) + // The current input key matches a weapon slot key. + && state.key_match); + + state.override = + (override + // Weapon slots selected directly or hold "last" is activated. + && (weapon_slots_activation == WS_ALWAYS_ON || state.activated)); + + state.hold_override = + (override + // Weapon slots are activated using hold "last" only. + && (weapon_slots_activation == WS_HOLD_LAST && state.activated)); + } + else if (state.enabled) + { + WS_Reset(); + } +} + +// +// WS_Override +// +boolean WS_Override(void) +{ + return state.override; +} + +// +// WS_HoldOverride +// +boolean WS_HoldOverride(void) +{ + return state.hold_override; +} + +// +// WS_ClearSharedEvent +// +void WS_ClearSharedEvent(void) +{ + if (shared_event.restored) + { + ResetSharedEvent(); + } +} + +static void RestoreSharedEvent(void) +{ +// Restore the shared event if the activator was pressed and then quickly +// released without pressing a weapon slot. +#define MAX_RESTORE_TICS 21 // 600 ms + + if ((state.activated && !state.pressed) + && (!shared_event.restored && shared_event.state != NULL) + && (I_GetTime() - shared_event.time <= MAX_RESTORE_TICS)) + { + shared_event.time = 0; + shared_event.input_key = -1; + *shared_event.state = true; + shared_event.restored = true; + } + else + { + ResetSharedEvent(); + } +} + +static void SetSharedEvent(const event_t *ev, boolean *buttons, int32_t size) +{ + if (ev->data1.i >= 0 && ev->data1.i < size) + { + shared_event.time = I_GetTime(); + shared_event.input_key = ev->data1.i; + shared_event.state = &buttons[ev->data1.i]; + shared_event.restored = false; + } + else + { + ResetSharedEvent(); + } +} + +static void BackupSharedEvent(const event_t *ev) +{ + switch (ev->type) + { + case ev_joyb_down: + SetSharedEvent(ev, joybuttons, NUM_GAMEPAD_BUTTONS); + break; + + case ev_keydown: + SetSharedEvent(ev, gamekeydown, NUMKEYS); + break; + + case ev_mouseb_down: + SetSharedEvent(ev, mousebuttons, NUM_MOUSE_BUTTONS); + break; + + default: + ResetSharedEvent(); + break; + } +} + +static boolean UpdateSelection(const event_t *ev) +{ + if (!state.key_match) + { + return false; + } + + switch (ev->type) + { + case ev_joyb_down: + case ev_keydown: + if (!state.selected) + { + state.pressed = true; + state.selected = true; + state.input_key = ev->data1.i; + state.current_slot = &slots[state.index]; + } + break; + + case ev_joyb_up: + case ev_keyup: + if (state.selected && ev->data1.i == state.input_key) + { + state.selected = false; + state.input_key = -1; + state.current_slot = NULL; + } + break; + + default: + break; + } + + return true; +} + +// +// WS_Responder +// +boolean WS_Responder(const event_t *ev) +{ + if (!state.enabled) + { + return false; + } + + switch (weapon_slots_activation) + { + case WS_ALWAYS_ON: + return UpdateSelection(ev); + + case WS_HOLD_LAST: + if (M_InputActivated(input_lastweapon)) + { + if (!state.activated) + { + BackupSharedEvent(ev); + ResetState(); + state.activated = true; + } + return true; + } + else if (M_InputDeactivated(input_lastweapon)) + { + if (state.activated && ev->data1.i == shared_event.input_key) + { + RestoreSharedEvent(); + ResetState(); + return true; + } + return false; + } + else if (state.activated) + { + return UpdateSelection(ev); + } + break; + + default: + break; + } + + return false; +} + +// +// Weapon slots switching functions. +// + +static weapontype_t FinalWeapon(const player_t *player, + weapontype_t current_weapon, + weapontype_t final_slot_weapon) +{ + switch (final_slot_weapon) + { + case wp_fist: + case wp_chainsaw: + if (player->weaponowned[wp_chainsaw] + && current_weapon != wp_chainsaw && current_weapon == wp_fist) + { + return wp_chainsaw; + } + break; + + case wp_shotgun: + case wp_supershotgun: + if (ALLOW_SSG && player->weaponowned[wp_supershotgun] + && player->weaponowned[wp_shotgun] + && current_weapon == wp_shotgun) + { + return wp_shotgun; // wp_supershotgun + } + break; + + default: + break; + } + + return wp_nochange; +} + +static boolean Selectable(const player_t *player, weapontype_t slot_weapon) +{ + if ((slot_weapon == wp_plasma || slot_weapon == wp_bfg) + && gamemission == doom && gamemode == shareware) + { + return false; + } + + if (!player->weaponowned[slot_weapon]) + { + return false; + } + + return true; +} + +static boolean NextWeapon(const player_t *player, weapontype_t current_weapon, + weapontype_t slot_weapon, weapontype_t *next_weapon) +{ + switch (slot_weapon) + { + case wp_fist: + case wp_chainsaw: + if (player->weaponowned[wp_chainsaw] + && current_weapon != wp_chainsaw && current_weapon != wp_fist) + { + *next_weapon = wp_chainsaw; + return true; + } + else if (current_weapon != wp_fist + && (!player->weaponowned[wp_chainsaw] + || (current_weapon == wp_chainsaw + && player->powers[pw_strength]))) + { + *next_weapon = wp_fist; + return true; + } + break; + + case wp_shotgun: + case wp_supershotgun: + if (ALLOW_SSG && player->weaponowned[wp_supershotgun] + && current_weapon != wp_supershotgun + && current_weapon != wp_shotgun) + { + *next_weapon = wp_shotgun; // wp_supershotgun + return true; + } + else if (player->weaponowned[wp_shotgun] + && current_weapon != wp_shotgun + && (current_weapon == wp_supershotgun || !ALLOW_SSG + || !player->weaponowned[wp_supershotgun])) + { + *next_weapon = wp_shotgun; + return true; + } + break; + + default: + if (Selectable(player, slot_weapon)) + { + *next_weapon = slot_weapon; + return true; + } + break; + } + + return false; +} + +static int CurrentPosition(weapontype_t current_weapon, + const weapontype_t *slot_weapons, int num_weapons) +{ + for (int pos = 0; pos < num_weapons; pos++) + { + const int next_pos = (pos + 1) % num_weapons; + const weapontype_t next_weapon = slot_weapons[next_pos]; + + switch (slot_weapons[pos]) + { + case wp_fist: + case wp_chainsaw: + if (next_weapon == wp_fist || next_weapon == wp_chainsaw) + { + continue; // Skip duplicates. + } + else if (current_weapon == wp_fist + || current_weapon == wp_chainsaw) + { + return pos; // Start in current position. + } + break; + + case wp_shotgun: + case wp_supershotgun: + if (next_weapon == wp_shotgun || next_weapon == wp_supershotgun) + { + continue; // Skip duplicates. + } + else if (current_weapon == wp_shotgun + || current_weapon == wp_supershotgun) + { + return pos; // Start in current position. + } + break; + + default: + if (current_weapon == slot_weapons[pos]) + { + return next_pos; // Start in next position. + } + break; + } + } + + return 0; // Start in first position. +} + +static weapontype_t SlotWeaponVanilla(const player_t *player, + weapontype_t current_weapon, + const weapontype_t *slot_weapons, + int num_weapons) +{ + int pos = CurrentPosition(current_weapon, slot_weapons, num_weapons) - 1; + weapontype_t next_weapon; + + for (int i = 0; i < num_weapons; i++) + { + pos = (pos + 1) % num_weapons; + + if (NextWeapon(player, current_weapon, slot_weapons[pos], &next_weapon)) + { + return next_weapon; + } + } + + return FinalWeapon(player, current_weapon, slot_weapons[num_weapons - 1]); +} + +static weapontype_t SlotWeapon(const player_t *player, + weapontype_t current_weapon, + const weapontype_t *slot_weapons, + int num_weapons) +{ + int pos = -1; + + for (int i = 0; i < num_weapons; i++) + { + if (current_weapon == slot_weapons[i]) + { + pos = i; + break; + } + } + + for (int i = 0; i < num_weapons; i++) + { + pos = (pos + 1) % num_weapons; + + if (slot_weapons[pos] == wp_supershotgun && !ALLOW_SSG) + { + continue; + } + + if (Selectable(player, slot_weapons[pos])) + { + return slot_weapons[pos]; + } + } + + return wp_nochange; +} + +static weapontype_t CurrentWeapon(const player_t *player) +{ + if (player->pendingweapon == wp_nochange) + { + return player->readyweapon; + } + else + { + return player->pendingweapon; + } +} + +// +// WS_SlotSelected +// +boolean WS_SlotSelected(void) +{ + return (state.current_slot != NULL); +} + +// +// WS_SlotWeapon +// +weapontype_t WS_SlotWeapon(void) +{ + const player_t *player = &players[consoleplayer]; + const weapontype_t current_weapon = CurrentWeapon(player); + const weapontype_t *slot_weapons = state.current_slot->weapons; + const int num_weapons = state.current_slot->num_weapons; + state.current_slot = NULL; + + if (!demo_compatibility) + { + return SlotWeapon(player, current_weapon, slot_weapons, num_weapons); + } + else + { + return SlotWeaponVanilla(player, current_weapon, slot_weapons, + num_weapons); + } +} + +// +// WS_BindVariables +// + +#define BIND_NUM_WEAP(name, v, a, b, help) \ + M_BindNum(#name, &name, NULL, (v), (a), (b), ss_weap, wad_no, help) + +#define BIND_SLOT(name, v, help) \ + BIND_NUM_WEAP(name, (v), WS_TYPE_NONE, NUM_WS_TYPES - 1, help) + +void WS_BindVariables(void) +{ + BIND_NUM_WEAP(weapon_slots_activation, + WS_OFF, WS_OFF, NUM_WS_ACTIVATION - 1, + "Weapon slots activation (0 = Off; 1 = Hold \"Last\"; 2 = Always On)"); + + BIND_NUM_WEAP(weapon_slots_selection, + WS_SELECT_DPAD, WS_SELECT_DPAD, NUM_WS_SELECT - 1, + "Weapon slots selection (0 = D-Pad; 1 = Face Buttons; 2 = 1-4 Keys)"); + + BIND_SLOT(weapon_slots_1_1, WS_TYPE_SSG, "Slot 1, weapon 1"); + BIND_SLOT(weapon_slots_1_2, WS_TYPE_SHOTGUN, "Slot 1, weapon 2"); + BIND_SLOT(weapon_slots_1_3, WS_TYPE_NONE, "Slot 1, weapon 3"); + + BIND_SLOT(weapon_slots_2_1, WS_TYPE_ROCKET, "Slot 2, weapon 1"); + BIND_SLOT(weapon_slots_2_2, WS_TYPE_CHAINSAW, "Slot 2, weapon 2"); + BIND_SLOT(weapon_slots_2_3, WS_TYPE_FIST, "Slot 2, weapon 3"); + + BIND_SLOT(weapon_slots_3_1, WS_TYPE_PLASMA, "Slot 3, weapon 1"); + BIND_SLOT(weapon_slots_3_2, WS_TYPE_BFG, "Slot 3, weapon 2"); + BIND_SLOT(weapon_slots_3_3, WS_TYPE_NONE, "Slot 3, weapon 3"); + + BIND_SLOT(weapon_slots_4_1, WS_TYPE_CHAINGUN, "Slot 4, weapon 1"); + BIND_SLOT(weapon_slots_4_2, WS_TYPE_PISTOL, "Slot 4, weapon 2"); + BIND_SLOT(weapon_slots_4_3, WS_TYPE_NONE, "Slot 4, weapon 3"); +} diff --git a/src/ws_stuff.h b/src/ws_stuff.h new file mode 100644 index 000000000..d9f476e20 --- /dev/null +++ b/src/ws_stuff.h @@ -0,0 +1,56 @@ +// +// Copyright(C) 2024 ceski +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// DESCRIPTION: +// Weapon slots. +// + +#ifndef __WS_STUFF__ +#define __WS_STUFF__ + +#include "doomdef.h" +#include "doomtype.h" + +struct event_s; + +#define NUM_WS_SLOTS 4 +#define NUM_WS_WEAPS 3 + +typedef enum +{ + WS_SELECT_DPAD, + WS_SELECT_FACE_BUTTONS, + WS_SELECT_1234, + NUM_WS_SELECT +} weapon_slots_selection_t; + +void WS_Reset(void); +boolean WS_Enabled(void); +weapon_slots_selection_t WS_Selection(void); +void WS_UpdateSelection(void); +void WS_UpdateSlots(void); +void WS_Init(void); + +void WS_UpdateState(const struct event_s *ev); +boolean WS_Override(void); +boolean WS_HoldOverride(void); + +void WS_ClearSharedEvent(void); +boolean WS_Responder(const struct event_s *ev); + +boolean WS_SlotSelected(void); +weapontype_t WS_SlotWeapon(void); + +void WS_BindVariables(void); + +#endif