Completely refactor G_BuildTiccmd into many smaller pieces

This commit is contained in:
James R 2023-08-13 16:00:23 -07:00
parent 368ffb79b8
commit c6db634635
3 changed files with 240 additions and 316 deletions

View file

@ -41,154 +41,161 @@
namespace
{
struct joystickvector2_t
{
INT32 xaxis;
INT32 yaxis;
};
// Take a magnitude of two axes, and adjust it to take out the deadzone
// Will return a value between 0 and JOYAXISRANGE
INT32 G_BasicDeadZoneCalculation(INT32 magnitude, fixed_t deadZone)
{
const INT32 jdeadzone = (JOYAXISRANGE * deadZone) / FRACUNIT;
INT32 deadzoneAppliedValue = 0;
INT32 adjustedMagnitude = std::abs(magnitude);
if (jdeadzone >= JOYAXISRANGE && adjustedMagnitude >= JOYAXISRANGE) // If the deadzone and magnitude are both 100%...
return JOYAXISRANGE; // ...return 100% input directly, to avoid dividing by 0
else if (adjustedMagnitude > jdeadzone) // Otherwise, calculate how much the magnitude exceeds the deadzone
{
adjustedMagnitude = std::min(adjustedMagnitude, JOYAXISRANGE);
adjustedMagnitude -= jdeadzone;
deadzoneAppliedValue = (adjustedMagnitude * JOYAXISRANGE) / (JOYAXISRANGE - jdeadzone);
return JOYAXISRANGE; // ...return 100% input directly, to avoid dividing by 0
}
return deadzoneAppliedValue;
if (adjustedMagnitude <= jdeadzone)
{
return 0; // Magnitude is within deadzone, so do nothing
}
// Calculate how much the magnitude exceeds the deadzone
adjustedMagnitude = std::min(adjustedMagnitude, JOYAXISRANGE) - jdeadzone;
return (adjustedMagnitude * JOYAXISRANGE) / (JOYAXISRANGE - jdeadzone);
}
// Get the actual sensible radial value for a joystick axis when accounting for a deadzone
void G_HandleAxisDeadZone(UINT8 splitnum, joystickvector2_t *joystickvector)
class TiccmdBuilder
{
INT32 gamepadStyle = Joystick[splitnum].bGamepadStyle;
fixed_t deadZone = cv_deadzone[splitnum].value;
// When gamepadstyle is "true" the values are just -1, 0, or 1. This is done in the interface code.
if (!gamepadStyle)
struct JoyStickVector2
{
INT32 xaxis;
INT32 yaxis;
};
ticcmd_t* cmd;
INT32 realtics;
UINT8 ssplayer;
UINT8 viewnum;
JoyStickVector2 joystickvector;
UINT8 forplayer() const { return ssplayer - 1; }
player_t* player() const { return &players[g_localplayers[forplayer()]]; }
// Get the actual sensible radial value for a joystick axis when accounting for a deadzone
void handle_axis_deadzone()
{
INT32 gamepadStyle = Joystick[forplayer()].bGamepadStyle;
fixed_t deadZone = cv_deadzone[forplayer()].value;
// When gamepadstyle is "true" the values are just -1, 0, or 1. This is done in the interface code.
if (gamepadStyle)
{
return;
}
// Get the total magnitude of the 2 axes
INT32 magnitude = (joystickvector->xaxis * joystickvector->xaxis) + (joystickvector->yaxis * joystickvector->yaxis);
INT32 normalisedXAxis;
INT32 normalisedYAxis;
INT32 normalisedMagnitude;
double dMagnitude = std::sqrt((double)magnitude);
magnitude = (INT32)dMagnitude;
INT32 magnitude = std::sqrt(static_cast<double>(
(joystickvector.xaxis * joystickvector.xaxis) + (joystickvector.yaxis * joystickvector.yaxis)
));
// Get the normalised xy values from the magnitude
normalisedXAxis = (joystickvector->xaxis * magnitude) / JOYAXISRANGE;
normalisedYAxis = (joystickvector->yaxis * magnitude) / JOYAXISRANGE;
INT32 normalisedXAxis = (joystickvector.xaxis * magnitude) / JOYAXISRANGE;
INT32 normalisedYAxis = (joystickvector.yaxis * magnitude) / JOYAXISRANGE;
// Apply the deadzone to the magnitude to give a correct value between 0 and JOYAXISRANGE
normalisedMagnitude = G_BasicDeadZoneCalculation(magnitude, deadZone);
INT32 normalisedMagnitude = G_BasicDeadZoneCalculation(magnitude, deadZone);
// Apply the deadzone to the xy axes
joystickvector->xaxis = (normalisedXAxis * normalisedMagnitude) / JOYAXISRANGE;
joystickvector->yaxis = (normalisedYAxis * normalisedMagnitude) / JOYAXISRANGE;
joystickvector.xaxis = (normalisedXAxis * normalisedMagnitude) / JOYAXISRANGE;
joystickvector.yaxis = (normalisedYAxis * normalisedMagnitude) / JOYAXISRANGE;
// Cap the values so they don't go above the correct maximum
joystickvector->xaxis = std::min(joystickvector->xaxis, JOYAXISRANGE);
joystickvector->xaxis = std::max(joystickvector->xaxis, -JOYAXISRANGE);
joystickvector->yaxis = std::min(joystickvector->yaxis, JOYAXISRANGE);
joystickvector->yaxis = std::max(joystickvector->yaxis, -JOYAXISRANGE);
joystickvector.xaxis = std::min(joystickvector.xaxis, JOYAXISRANGE);
joystickvector.xaxis = std::max(joystickvector.xaxis, -JOYAXISRANGE);
joystickvector.yaxis = std::min(joystickvector.yaxis, JOYAXISRANGE);
joystickvector.yaxis = std::max(joystickvector.yaxis, -JOYAXISRANGE);
}
}
// Turning was removed from G_BuildTiccmd to prevent easy client hacking.
// This brings back the camera prediction that was lost.
void G_DoAnglePrediction(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer, UINT8 viewnum, player_t *player)
{
angle_t angleChange = 0;
// Chasecam stops in these situations, so local cam should stop too.
// Otherwise it'll jerk when it resumes.
if (player->playerstate == PST_DEAD)
return;
if (player->mo != NULL && !P_MobjWasRemoved(player->mo) && player->mo->hitlag > 0)
return;
while (realtics > 0)
void hook()
{
localsteering[ssplayer - 1] = K_UpdateSteeringValue(localsteering[ssplayer - 1], cmd->turning);
angleChange = K_GetKartTurnValue(player, localsteering[ssplayer - 1]) << TICCMD_REDUCE;
/*
Lua: Allow this hook to overwrite ticcmd.
We check if we're actually in a level because for some reason this Hook would run in menus and on the titlescreen otherwise.
Be aware that within this hook, nothing but this player's cmd can be edited (otherwise we'd run in some pretty bad synching problems since this is clientsided, or something)
realtics--;
Possible usages for this are:
-Forcing the player to perform an action, which could otherwise require terrible, terrible hacking to replicate.
-Preventing the player to perform an action, which would ALSO require some weirdo hacks.
-Making some galaxy brain autopilot Lua if you're a masochist
-Making a Mario Kart 8 Deluxe tier baby mode that steers you away from walls and whatnot. You know what, do what you want!
*/
if (!addedtogame || gamestate != GS_LEVEL)
{
return;
}
LUA_HookTiccmd(player(), cmd, HOOK(PlayerCmd));
auto clamp = [](auto val, int range) { return std::clamp(static_cast<int>(val), -(range), range); };
cmd->forwardmove = clamp(cmd->forwardmove, MAXPLMOVE);
cmd->turning = clamp(cmd->turning, KART_FULLTURN);
cmd->throwdir = clamp(cmd->throwdir, KART_FULLTURN);
// Send leveltime when this tic was generated to the server for control lag calculations.
// Only do this when in a level. Also do this after the hook, so that it can't overwrite this.
cmd->latency = (leveltime & TICCMD_LATENCYMASK);
}
// Turning was removed from G_BuildTiccmd to prevent easy client hacking.
// This brings back the camera prediction that was lost.
void angle_prediction()
{
// Chasecam stops in these situations, so local cam should stop too.
// Otherwise it'll jerk when it resumes.
if (player()->playerstate == PST_DEAD)
{
return;
}
if (player()->mo != NULL && !P_MobjWasRemoved(player()->mo) && player()->mo->hitlag > 0)
{
return;
}
angle_t angleChange = 0;
while (realtics > 0)
{
INT32& steering = localsteering[forplayer()];
steering = K_UpdateSteeringValue(steering, cmd->turning);
angleChange = K_GetKartTurnValue(player(), steering) << TICCMD_REDUCE;
realtics--;
}
#if 0
// Left here in case it needs unsealing later. This tried to replicate an old localcam function, but this behavior was unpopular in tests.
//if (player->pflags & PF_DRIFTEND)
{
localangle[ssplayer - 1] = player->mo->angle;
}
else
// Left here in case it needs unsealing later. This tried to replicate an old localcam function, but this behavior was unpopular in tests.
//if (player()->pflags & PF_DRIFTEND)
{
localangle[forplayer()] = player()->mo->angle;
}
else
#endif
{
localangle[viewnum] += angleChange;
}
}
}; // namespace
void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
{
const UINT8 forplayer = ssplayer-1;
const UINT8 viewnum = G_PartyPosition(g_localplayers[forplayer]);
INT32 forward;
joystickvector2_t joystickvector;
player_t *player = &players[g_localplayers[forplayer]];
//camera_t *thiscam = &camera[forplayer];
//boolean *kbl = &keyboard_look[forplayer];
//boolean *rd = &resetdown[forplayer];
//const boolean mouseaiming = player->spectator;
if (demo.playback) return;
// Is there any reason this can't just be I_BaseTiccmd?
switch (ssplayer)
{
case 2:
G_CopyTiccmd(cmd, I_BaseTiccmd2(), 1);
break;
case 3:
G_CopyTiccmd(cmd, I_BaseTiccmd3(), 1);
break;
case 4:
G_CopyTiccmd(cmd, I_BaseTiccmd4(), 1);
break;
case 1:
default:
G_CopyTiccmd(cmd, I_BaseTiccmd(), 1); // empty, or external driver
break;
{
localangle[viewnum] += angleChange;
}
}
cmd->angle = localangle[viewnum] >> TICCMD_REDUCE;
// why build a ticcmd if we're paused?
// Or, for that matter, if we're being reborn.
if (paused || P_AutoPause() || (gamestate == GS_LEVEL && player->playerstate == PST_REBORN))
bool typing_input()
{
return;
}
if (!menuactive && !chat_on && !CON_Ready())
{
return false;
}
cmd->flags = 0;
if (menuactive || chat_on || CON_Ready())
{
cmd->flags |= TICCMD_TYPING;
if (hu_keystrokes)
@ -196,83 +203,63 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
cmd->flags |= TICCMD_KEYSTROKE;
}
goto aftercmdinput;
return true;
}
if (G_IsPartyLocal(displayplayers[forplayer]) == false)
bool director_input()
{
if (M_MenuButtonPressed(forplayer, MBT_A))
if (G_IsPartyLocal(displayplayers[forplayer()]) == true)
{
return false;
}
if (M_MenuButtonPressed(forplayer(), MBT_A))
{
G_AdjustView(ssplayer, 1, true);
K_ToggleDirector(false);
}
if (M_MenuButtonPressed(forplayer, MBT_X))
if (M_MenuButtonPressed(forplayer(), MBT_X))
{
G_AdjustView(ssplayer, -1, true);
K_ToggleDirector(false);
}
if (player->spectator == true)
if (player()->spectator == true)
{
// duplication of fire
if (G_PlayerInputDown(forplayer, gc_item, 0))
if (G_PlayerInputDown(forplayer(), gc_item, 0))
{
cmd->buttons |= BT_ATTACK;
}
if (M_MenuButtonPressed(forplayer, MBT_R))
if (M_MenuButtonPressed(forplayer(), MBT_R))
{
K_ToggleDirector(true);
}
}
goto aftercmdinput;
return true;
}
if (K_PlayerUsesBotMovement(player))
bool spectator_analog_input()
{
// Bot ticcmd is generated by K_BuildBotTiccmd
return;
}
if (!player()->spectator && !objectplacing)
{
return false;
}
joystickvector.xaxis = G_PlayerInputAnalog(forplayer, gc_right, 0) - G_PlayerInputAnalog(forplayer, gc_left, 0);
joystickvector.yaxis = 0;
G_HandleAxisDeadZone(forplayer, &joystickvector);
// For kart, I've turned the aim axis into a digital axis because we only
// use it for aiming to throw items forward/backward and the vote screen
// This mean that the turn axis will still be gradient but up/down will be 0
// until the stick is pushed far enough
joystickvector.yaxis = G_PlayerInputAnalog(forplayer, gc_down, 0) - G_PlayerInputAnalog(forplayer, gc_up, 0);
if (encoremode)
{
joystickvector.xaxis = -joystickvector.xaxis;
}
forward = 0;
cmd->turning = 0;
cmd->aiming = 0;
if (joystickvector.xaxis != 0)
{
cmd->turning -= (joystickvector.xaxis * KART_FULLTURN) / JOYAXISRANGE;
}
if (player->spectator || objectplacing) // SRB2Kart: spectators need special controls
{
if (G_PlayerInputDown(forplayer, gc_accel, 0))
if (G_PlayerInputDown(forplayer(), gc_accel, 0))
{
cmd->buttons |= BT_ACCELERATE;
}
if (G_PlayerInputDown(forplayer, gc_brake, 0))
if (G_PlayerInputDown(forplayer(), gc_brake, 0))
{
cmd->buttons |= BT_BRAKE;
}
if (G_PlayerInputDown(forplayer, gc_lookback, 0))
if (G_PlayerInputDown(forplayer(), gc_lookback, 0))
{
cmd->aiming -= joystickvector.yaxis;
}
@ -280,30 +267,33 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
{
if (joystickvector.yaxis < 0)
{
forward += MAXPLMOVE;
cmd->forwardmove += MAXPLMOVE;
}
if (joystickvector.yaxis > 0)
{
forward -= MAXPLMOVE;
cmd->forwardmove -= MAXPLMOVE;
}
}
return true;
}
else
void kart_analog_input()
{
// forward with key or button // SRB2kart - we use an accel/brake instead of forward/backward.
INT32 value = G_PlayerInputAnalog(forplayer, gc_accel, 0);
INT32 value = G_PlayerInputAnalog(forplayer(), gc_accel, 0);
if (value != 0)
{
cmd->buttons |= BT_ACCELERATE;
forward += ((value * MAXPLMOVE) / JOYAXISRANGE);
cmd->forwardmove += ((value * MAXPLMOVE) / JOYAXISRANGE);
}
value = G_PlayerInputAnalog(forplayer, gc_brake, 0);
value = G_PlayerInputAnalog(forplayer(), gc_brake, 0);
if (value != 0)
{
cmd->buttons |= BT_BRAKE;
forward -= ((value * MAXPLMOVE) / JOYAXISRANGE);
cmd->forwardmove -= ((value * MAXPLMOVE) / JOYAXISRANGE);
}
// But forward/backward IS used for aiming.
@ -313,120 +303,109 @@ void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
}
}
// drift
if (G_PlayerInputDown(forplayer, gc_drift, 0))
void analog_input()
{
cmd->buttons |= BT_DRIFT;
}
joystickvector.xaxis = G_PlayerInputAnalog(forplayer(), gc_right, 0) - G_PlayerInputAnalog(forplayer(), gc_left, 0);
joystickvector.yaxis = 0;
handle_axis_deadzone();
// C
if (G_PlayerInputDown(forplayer, gc_spindash, 0))
{
cmd->buttons |= BT_SPINDASHMASK;
}
// For kart, I've turned the aim axis into a digital axis because we only
// use it for aiming to throw items forward/backward and the vote screen
// This mean that the turn axis will still be gradient but up/down will be 0
// until the stick is pushed far enough
joystickvector.yaxis = G_PlayerInputAnalog(forplayer(), gc_down, 0) - G_PlayerInputAnalog(forplayer(), gc_up, 0);
// fire
if (G_PlayerInputDown(forplayer, gc_item, 0))
{
cmd->buttons |= BT_ATTACK;
}
// rear view
if (G_PlayerInputDown(forplayer, gc_lookback, 0))
{
cmd->buttons |= BT_LOOKBACK;
}
// respawn
if (G_PlayerInputDown(forplayer, gc_respawn, 0))
{
cmd->buttons |= (BT_RESPAWN | BT_EBRAKEMASK);
}
// mp general function button
if (G_PlayerInputDown(forplayer, gc_vote, 0))
{
cmd->buttons |= BT_VOTE;
}
// lua buttons a thru c
if (G_PlayerInputDown(forplayer, gc_luaa, 0)) { cmd->buttons |= BT_LUAA; }
if (G_PlayerInputDown(forplayer, gc_luab, 0)) { cmd->buttons |= BT_LUAB; }
if (G_PlayerInputDown(forplayer, gc_luac, 0)) { cmd->buttons |= BT_LUAC; }
// spectator aiming shit, ahhhh...
/*
{
INT32 player_invert = invertmouse ? -1 : 1;
INT32 screen_invert =
(player->mo && (player->mo->eflags & MFE_VERTICALFLIP)
&& (!thiscam->chase)) //because chasecam's not inverted
? -1 : 1; // set to -1 or 1 to multiply
axis = PlayerJoyAxis(ssplayer, AXISLOOK);
if (analogjoystickmove && axis != 0 && lookaxis && player->spectator)
cmd->aiming += (axis<<16) * screen_invert;
// spring back if not using keyboard neither mouselookin'
if (*kbl == false && !lookaxis && !mouseaiming)
cmd->aiming = 0;
if (player->spectator)
if (encoremode)
{
if (PlayerInputDown(ssplayer, gc_lookup) || (gamepadjoystickmove && axis < 0))
{
cmd->aiming += KB_LOOKSPEED * screen_invert;
*kbl = true;
}
else if (PlayerInputDown(ssplayer, gc_lookdown) || (gamepadjoystickmove && axis > 0))
{
cmd->aiming -= KB_LOOKSPEED * screen_invert;
*kbl = true;
}
joystickvector.xaxis = -joystickvector.xaxis;
}
if (PlayerInputDown(ssplayer, gc_centerview)) // No need to put a spectator limit on this one though :V
cmd->aiming = 0;
if (joystickvector.xaxis != 0)
{
cmd->turning -= (joystickvector.xaxis * KART_FULLTURN) / JOYAXISRANGE;
}
if (spectator_analog_input())
{
return;
}
kart_analog_input();
}
*/
cmd->forwardmove += (SINT8)forward;
aftercmdinput:
/* Lua: Allow this hook to overwrite ticcmd.
We check if we're actually in a level because for some reason this Hook would run in menus and on the titlescreen otherwise.
Be aware that within this hook, nothing but this player's cmd can be edited (otherwise we'd run in some pretty bad synching problems since this is clientsided, or something)
Possible usages for this are:
-Forcing the player to perform an action, which could otherwise require terrible, terrible hacking to replicate.
-Preventing the player to perform an action, which would ALSO require some weirdo hacks.
-Making some galaxy brain autopilot Lua if you're a masochist
-Making a Mario Kart 8 Deluxe tier baby mode that steers you away from walls and whatnot. You know what, do what you want!
*/
if (addedtogame && gamestate == GS_LEVEL)
void common_button_input()
{
LUA_HookTiccmd(player, cmd, HOOK(PlayerCmd));
auto map = [this](INT32 gamecontrol, UINT32 button)
{
if (G_PlayerInputDown(forplayer(), gamecontrol, 0))
{
cmd->buttons |= button;
}
};
// Send leveltime when this tic was generated to the server for control lag calculations.
// Only do this when in a level. Also do this after the hook, so that it can't overwrite this.
cmd->latency = (leveltime & TICCMD_LATENCYMASK);
map(gc_drift, BT_DRIFT); // drift
map(gc_spindash, BT_SPINDASHMASK); // C
map(gc_item, BT_ATTACK); // fire
map(gc_lookback, BT_LOOKBACK); // rear view
map(gc_respawn, BT_RESPAWN | BT_EBRAKEMASK); // respawn
map(gc_vote, BT_VOTE); // mp general function button
// lua buttons a thru c
map(gc_luaa, BT_LUAA);
map(gc_luab, BT_LUAB);
map(gc_luac, BT_LUAC);
}
if (cmd->forwardmove > MAXPLMOVE)
cmd->forwardmove = MAXPLMOVE;
else if (cmd->forwardmove < -MAXPLMOVE)
cmd->forwardmove = -MAXPLMOVE;
public:
explicit TiccmdBuilder(ticcmd_t* cmd_, INT32 realtics_, UINT8 ssplayer_) :
cmd(cmd_), realtics(realtics_), ssplayer(ssplayer_), viewnum(G_PartyPosition(g_localplayers[forplayer()]))
{
*cmd = {}; // blank ticcmd
if (cmd->turning > KART_FULLTURN)
cmd->turning = KART_FULLTURN;
else if (cmd->turning < -KART_FULLTURN)
cmd->turning = -KART_FULLTURN;
if (demo.playback)
{
return;
}
if (cmd->throwdir > KART_FULLTURN)
cmd->throwdir = KART_FULLTURN;
else if (cmd->throwdir < -KART_FULLTURN)
cmd->throwdir = -KART_FULLTURN;
if (paused || P_AutoPause())
{
return;
}
G_DoAnglePrediction(cmd, realtics, ssplayer, viewnum, player);
if (gamestate == GS_LEVEL && player()->playerstate == PST_REBORN)
{
return;
}
// A human player can turn into a bot at the end of
// a race, so the director controls have higher
// priority.
bool overlay = typing_input() || director_input();
if (K_PlayerUsesBotMovement(player()))
{
// Bot ticcmd is generated by K_BuildBotTiccmd
return;
}
if (!overlay)
{
analog_input();
common_button_input();
}
cmd->angle = localangle[viewnum] >> TICCMD_REDUCE;
hook();
angle_prediction();
}
};
}; // namespace
void G_BuildTiccmd(ticcmd_t *cmd, INT32 realtics, UINT8 ssplayer)
{
TiccmdBuilder(cmd, realtics, ssplayer);
}

View file

@ -90,27 +90,6 @@ void I_GetEvent(void);
*/
void I_OsPolling(void);
// Either returns a null ticcmd,
// or calls a loadable driver to build it.
// This ticcmd will then be modified by the gameloop
// for normal input.
/** \brief Input for the first player
*/
ticcmd_t *I_BaseTiccmd(void);
/** \brief Input for the second player
*/
ticcmd_t *I_BaseTiccmd2(void);
/** \brief Input for the third player
*/
ticcmd_t *I_BaseTiccmd3(void);
/** \brief Input for the fourth player
*/
ticcmd_t *I_BaseTiccmd4(void);
/** \brief Called by M_Responder when quit is selected, return exit code 0
*/
void I_Quit(void) FUNCNORETURN;

View file

@ -1416,40 +1416,6 @@ void I_Tactile4(FFType pFFType, const JoyFF_t *FFEffect)
(void)FFEffect;
}
static ticcmd_t emptycmd[MAXSPLITSCREENPLAYERS];
/** \brief empty ticcmd for player 1
*/
ticcmd_t *I_BaseTiccmd(void)
{
return &emptycmd[0];
}
/** \brief empty ticcmd for player 2
*/
ticcmd_t *I_BaseTiccmd2(void)
{
return &emptycmd[1];
}
/** \brief empty ticcmd for player 3
*/
ticcmd_t *I_BaseTiccmd3(void)
{
return &emptycmd[2];
}
/** \brief empty ticcmd for player 4
*/
ticcmd_t *I_BaseTiccmd4(void)
{
return &emptycmd[3];
}
//
// I_GetTime
// returns time in 1/TICRATE second tics