Add custom weapon slots feature (#1923)

* Lower priority of shortcut responder

* Raise priority of chat, cheat, automap responders

* Add custom weapon slots

* Disable weapon slots by default
This commit is contained in:
ceski 2024-09-25 06:29:54 -07:00 committed by GitHub
parent d225e33c3f
commit 796c0d01c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 1093 additions and 29 deletions

View File

@ -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

View File

@ -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))
{

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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)
{

View File

@ -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);

View File

@ -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))

View File

@ -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();

View File

@ -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)

View File

@ -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.

View File

@ -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();
}

774
src/ws_stuff.c Normal file
View File

@ -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");
}

56
src/ws_stuff.h Normal file
View File

@ -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