yquake2remaster/src/client/cl_input.c
Yamagi bb0fc01e38 Another try to fix the problem of spurious aborts of cinematics.
Until this commit a cinematic was aborted as soon as any key were
marked down when finishing the user command and sending it to the
server. The whole logic to detect if a key is down is broken, for
example `vid_restart` may leave keys marked down that are in fact
up. And there's the possibility to inject fake key events from
nearly everywhere. I'm not really sure but I suspect that even the
server may be able to inject key events.

Therefore untangle the cinematic abort code from the user command
processing, it should depend only on real key strokes:

1. Introduce a new global variable `abort_cinamatic` and set it to
   `cls.realtime` as soon as a key down event is detected. The only
   exceptions are Escape and Shift, because opening the menu and
   toggeling the console should never abort a cinematic.
2. When starting a cinematic `abort_cinamatic` is set to INT_MAX,
   because it needs to be higher than the current `cls.realtime`.
3. When a cinematic is running, `cls.key_dest` is set to `key_game`
   (`key_menu` and `key_console` are ignored, keys send to the menu
   or the console should never abort a cinematic; `key_message`
   can / should never happen while a cinematic is running) and
   `abort_cinamatic` is less than `cls.realtime` the cinematic is
   aborted.

`abort_cinamatic` less than `cls.realtime` is necessary because the
client needs one frame to pop up the menu or toggle the console and set
the `cls.key_dest` accordingly. `abort_cinamatic == cls.realtime - 1`
is not possible because not every frame finishes a user command.

This closes #502.
2020-02-22 18:48:47 +01:00

854 lines
15 KiB
C

/*
* Copyright (C) 1997-2001 Id Software, Inc.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* =======================================================================
*
* This file implements the input handling like mouse events and
* keyboard strokes.
*
* =======================================================================
*/
#include "header/client.h"
#include "input/header/input.h"
cvar_t *cl_nodelta;
unsigned frame_msec;
unsigned old_sys_frame_time;
/*
* KEY BUTTONS
*
* Continuous button event tracking is complicated by the fact that two different
* input sources (say, mouse button 1 and the control key) can both press the
* same button, but the button should only be released when both of the
* pressing key have been released.
*
* When a key event issues a button command (+forward, +attack, etc), it appends
* its key number as a parameter to the command so it can be matched up with
* the release.
*
* state bit 0 is the current state of the key
* state bit 1 is edge triggered on the up to down transition
* state bit 2 is edge triggered on the down to up transition
*
*
* Key_Event (int key, qboolean down, unsigned time);
*
* +mlook src time
*/
kbutton_t in_klook;
kbutton_t in_left, in_right, in_forward, in_back;
kbutton_t in_lookup, in_lookdown, in_moveleft, in_moveright;
kbutton_t in_strafe, in_speed, in_use, in_attack;
kbutton_t in_up, in_down;
int in_impulse;
void
KeyDown(kbutton_t *b)
{
int k;
char *c;
c = Cmd_Argv(1);
if (c[0])
{
k = (int)strtol(c, (char **)NULL, 10);
}
else
{
k = -1; /* typed manually at the console for continuous down */
}
if ((k == b->down[0]) || (k == b->down[1]))
{
return; /* repeating key */
}
if (!b->down[0])
{
b->down[0] = k;
}
else if (!b->down[1])
{
b->down[1] = k;
}
else
{
Com_Printf("Three keys down for a button!\n");
return;
}
if (b->state & 1)
{
return; /* still down */
}
/* save timestamp */
c = Cmd_Argv(2);
b->downtime = (int)strtol(c, (char **)NULL, 10);
if (!b->downtime)
{
b->downtime = sys_frame_time - 100;
}
b->state |= 1 + 2; /* down + impulse down */
}
void
KeyUp(kbutton_t *b)
{
int k;
char *c;
unsigned uptime;
c = Cmd_Argv(1);
if (c[0])
{
k = (int)strtol(c, (char **)NULL, 10);
}
else
{
/* typed manually at the console, assume for unsticking, so clear all */
b->down[0] = b->down[1] = 0;
b->state = 4; /* impulse up */
return;
}
if (b->down[0] == k)
{
b->down[0] = 0;
}
else if (b->down[1] == k)
{
b->down[1] = 0;
}
else
{
return; /* key up without coresponding down (menu pass through) */
}
if (b->down[0] || b->down[1])
{
return; /* some other key is still holding it down */
}
if (!(b->state & 1))
{
return; /* still up (this should not happen) */
}
/* save timestamp */
c = Cmd_Argv(2);
uptime = (int)strtol(c, (char **)NULL, 10);
if (uptime)
{
b->msec += uptime - b->downtime;
}
else
{
b->msec += 10;
}
b->state &= ~1; /* now up */
b->state |= 4; /* impulse up */
}
void
IN_KLookDown(void)
{
KeyDown(&in_klook);
}
void
IN_KLookUp(void)
{
KeyUp(&in_klook);
}
void
IN_UpDown(void)
{
KeyDown(&in_up);
}
void
IN_UpUp(void)
{
KeyUp(&in_up);
}
void
IN_DownDown(void)
{
KeyDown(&in_down);
}
void
IN_DownUp(void)
{
KeyUp(&in_down);
}
void
IN_LeftDown(void)
{
KeyDown(&in_left);
}
void
IN_LeftUp(void)
{
KeyUp(&in_left);
}
void
IN_RightDown(void)
{
KeyDown(&in_right);
}
void
IN_RightUp(void)
{
KeyUp(&in_right);
}
void
IN_ForwardDown(void)
{
KeyDown(&in_forward);
}
void
IN_ForwardUp(void)
{
KeyUp(&in_forward);
}
void
IN_BackDown(void)
{
KeyDown(&in_back);
}
void
IN_BackUp(void)
{
KeyUp(&in_back);
}
void
IN_LookupDown(void)
{
KeyDown(&in_lookup);
}
void
IN_LookupUp(void)
{
KeyUp(&in_lookup);
}
void
IN_LookdownDown(void)
{
KeyDown(&in_lookdown);
}
void
IN_LookdownUp(void)
{
KeyUp(&in_lookdown);
}
void
IN_MoveleftDown(void)
{
KeyDown(&in_moveleft);
}
void
IN_MoveleftUp(void)
{
KeyUp(&in_moveleft);
}
void
IN_MoverightDown(void)
{
KeyDown(&in_moveright);
}
void
IN_MoverightUp(void)
{
KeyUp(&in_moveright);
}
void
IN_SpeedDown(void)
{
KeyDown(&in_speed);
}
void
IN_SpeedUp(void)
{
KeyUp(&in_speed);
}
void
IN_StrafeDown(void)
{
KeyDown(&in_strafe);
}
void
IN_StrafeUp(void)
{
KeyUp(&in_strafe);
}
void
IN_AttackDown(void)
{
KeyDown(&in_attack);
}
void
IN_AttackUp(void)
{
KeyUp(&in_attack);
}
void
IN_UseDown(void)
{
KeyDown(&in_use);
}
void
IN_UseUp(void)
{
KeyUp(&in_use);
}
void
IN_Impulse(void)
{
in_impulse = (int)strtol(Cmd_Argv(1), (char **)NULL, 10);
}
/*
* Returns the fraction of the
* frame that the key was down
*/
float
CL_KeyState(kbutton_t *key)
{
float val;
int msec;
key->state &= 1; /* clear impulses */
msec = key->msec;
key->msec = 0;
if (key->state)
{
/* still down */
msec += sys_frame_time - key->downtime;
key->downtime = sys_frame_time;
}
val = (float)msec / frame_msec;
if (val < 0)
{
val = 0;
}
if (val > 1)
{
val = 1;
}
return val;
}
cvar_t *cl_upspeed;
cvar_t *cl_forwardspeed;
cvar_t *cl_sidespeed;
cvar_t *cl_yawspeed;
cvar_t *cl_pitchspeed;
cvar_t *cl_run;
cvar_t *cl_anglespeedkey;
/*
* Moves the local angle positions
*/
void
CL_AdjustAngles(void)
{
float speed;
float up, down;
if (in_speed.state & 1)
{
speed = cls.nframetime * cl_anglespeedkey->value;
}
else
{
speed = cls.nframetime;
}
if (!(in_strafe.state & 1))
{
cl.viewangles[YAW] -= speed * cl_yawspeed->value * CL_KeyState(&in_right);
cl.viewangles[YAW] += speed * cl_yawspeed->value * CL_KeyState(&in_left);
}
if (in_klook.state & 1)
{
cl.viewangles[PITCH] -= speed * cl_pitchspeed->value * CL_KeyState(&in_forward);
cl.viewangles[PITCH] += speed * cl_pitchspeed->value * CL_KeyState(&in_back);
}
up = CL_KeyState(&in_lookup);
down = CL_KeyState(&in_lookdown);
cl.viewangles[PITCH] -= speed * cl_pitchspeed->value * up;
cl.viewangles[PITCH] += speed * cl_pitchspeed->value * down;
}
/*
* Send the intended movement message to the server
*/
void
CL_BaseMove(usercmd_t *cmd)
{
CL_AdjustAngles();
memset(cmd, 0, sizeof(*cmd));
VectorCopy(cl.viewangles, cmd->angles);
if (in_strafe.state & 1)
{
cmd->sidemove += cl_sidespeed->value * CL_KeyState(&in_right);
cmd->sidemove -= cl_sidespeed->value * CL_KeyState(&in_left);
}
cmd->sidemove += cl_sidespeed->value * CL_KeyState(&in_moveright);
cmd->sidemove -= cl_sidespeed->value * CL_KeyState(&in_moveleft);
cmd->upmove += cl_upspeed->value * CL_KeyState(&in_up);
cmd->upmove -= cl_upspeed->value * CL_KeyState(&in_down);
if (!(in_klook.state & 1))
{
cmd->forwardmove += cl_forwardspeed->value * CL_KeyState(&in_forward);
cmd->forwardmove -= cl_forwardspeed->value * CL_KeyState(&in_back);
}
/* adjust for speed key / running */
if ((in_speed.state & 1) ^ (int)(cl_run->value))
{
cmd->forwardmove *= 2;
cmd->sidemove *= 2;
cmd->upmove *= 2;
}
}
void
CL_ClampPitch(void)
{
float pitch;
pitch = SHORT2ANGLE(cl.frame.playerstate.pmove.delta_angles[PITCH]);
if (pitch > 180)
{
pitch -= 360;
}
if (cl.viewangles[PITCH] + pitch < -360)
{
cl.viewangles[PITCH] += 360; /* wrapped */
}
if (cl.viewangles[PITCH] + pitch > 360)
{
cl.viewangles[PITCH] -= 360; /* wrapped */
}
if (cl.viewangles[PITCH] + pitch > 89)
{
cl.viewangles[PITCH] = 89 - pitch;
}
if (cl.viewangles[PITCH] + pitch < -89)
{
cl.viewangles[PITCH] = -89 - pitch;
}
}
void
CL_FinishMove(usercmd_t *cmd)
{
int ms;
int i;
/* figure button bits */
if (in_attack.state & 3)
{
cmd->buttons |= BUTTON_ATTACK;
}
in_attack.state &= ~2;
if (in_use.state & 3)
{
cmd->buttons |= BUTTON_USE;
}
in_use.state &= ~2;
if (anykeydown && (cls.key_dest == key_game))
{
cmd->buttons |= BUTTON_ANY;
}
/* send milliseconds of time to apply the move */
ms = cls.nframetime * 1000;
if (ms > 250)
{
ms = 100; /* time was unreasonable */
}
cmd->msec = ms;
CL_ClampPitch();
for (i = 0; i < 3; i++)
{
cmd->angles[i] = ANGLE2SHORT(cl.viewangles[i]);
}
cmd->impulse = in_impulse;
in_impulse = 0;
/* send the ambient light level at the player's current position */
cmd->lightlevel = (byte)cl_lightlevel->value;
}
void
IN_CenterView(void)
{
cl.viewangles[PITCH] = -SHORT2ANGLE(cl.frame.playerstate.pmove.delta_angles[PITCH]);
}
/*
* Centers the view
*/
static void
IN_ForceCenterView(void)
{
cl.viewangles[PITCH] = 0;
}
void
CL_InitInput(void)
{
Cmd_AddCommand("centerview", IN_CenterView);
Cmd_AddCommand("force_centerview", IN_ForceCenterView);
Cmd_AddCommand("+moveup", IN_UpDown);
Cmd_AddCommand("-moveup", IN_UpUp);
Cmd_AddCommand("+movedown", IN_DownDown);
Cmd_AddCommand("-movedown", IN_DownUp);
Cmd_AddCommand("+left", IN_LeftDown);
Cmd_AddCommand("-left", IN_LeftUp);
Cmd_AddCommand("+right", IN_RightDown);
Cmd_AddCommand("-right", IN_RightUp);
Cmd_AddCommand("+forward", IN_ForwardDown);
Cmd_AddCommand("-forward", IN_ForwardUp);
Cmd_AddCommand("+back", IN_BackDown);
Cmd_AddCommand("-back", IN_BackUp);
Cmd_AddCommand("+lookup", IN_LookupDown);
Cmd_AddCommand("-lookup", IN_LookupUp);
Cmd_AddCommand("+lookdown", IN_LookdownDown);
Cmd_AddCommand("-lookdown", IN_LookdownUp);
Cmd_AddCommand("+strafe", IN_StrafeDown);
Cmd_AddCommand("-strafe", IN_StrafeUp);
Cmd_AddCommand("+moveleft", IN_MoveleftDown);
Cmd_AddCommand("-moveleft", IN_MoveleftUp);
Cmd_AddCommand("+moveright", IN_MoverightDown);
Cmd_AddCommand("-moveright", IN_MoverightUp);
Cmd_AddCommand("+speed", IN_SpeedDown);
Cmd_AddCommand("-speed", IN_SpeedUp);
Cmd_AddCommand("+attack", IN_AttackDown);
Cmd_AddCommand("-attack", IN_AttackUp);
Cmd_AddCommand("+use", IN_UseDown);
Cmd_AddCommand("-use", IN_UseUp);
Cmd_AddCommand("impulse", IN_Impulse);
Cmd_AddCommand("+klook", IN_KLookDown);
Cmd_AddCommand("-klook", IN_KLookUp);
cl_nodelta = Cvar_Get("cl_nodelta", "0", 0);
}
void
CL_RefreshCmd(void)
{
int ms;
usercmd_t *cmd;
// CMD to fill
cmd = &cl.cmds[cls.netchan.outgoing_sequence & (CMD_BACKUP - 1)];
// Calculate delta
frame_msec = sys_frame_time - old_sys_frame_time;
// Check bounds
if (frame_msec < 1)
{
return;
}
else if (frame_msec > 200)
{
frame_msec = 200;
}
// Add movement
CL_BaseMove(cmd);
IN_Move(cmd);
// Clamp angels for prediction
CL_ClampPitch();
cmd->angles[0] = ANGLE2SHORT(cl.viewangles[0]);
cmd->angles[1] = ANGLE2SHORT(cl.viewangles[1]);
cmd->angles[2] = ANGLE2SHORT(cl.viewangles[2]);
// Update time for prediction
ms = (int)(cls.nframetime * 1000.0f);
if (ms > 250)
{
ms = 100;
}
cmd->msec = ms;
// Update frame time for the next call
old_sys_frame_time = sys_frame_time;
// Important events are send immediately
if (((in_attack.state & 2)) || (in_use.state & 2))
{
cls.forcePacket = true;
}
}
void
CL_RefreshMove(void)
{
usercmd_t *cmd;
// CMD to fill
cmd = &cl.cmds[cls.netchan.outgoing_sequence & (CMD_BACKUP - 1)];
// Calculate delta
frame_msec = sys_frame_time - old_sys_frame_time;
// Check bounds
if (frame_msec < 1)
{
return;
}
else if (frame_msec > 200)
{
frame_msec = 200;
}
// Add movement
CL_BaseMove(cmd);
IN_Move(cmd);
old_sys_frame_time = sys_frame_time;
}
void
CL_FinalizeCmd(void)
{
usercmd_t *cmd;
// CMD to fill
cmd = &cl.cmds[cls.netchan.outgoing_sequence & (CMD_BACKUP - 1)];
// Mouse button events
if (in_attack.state & 3)
{
cmd->buttons |= BUTTON_ATTACK;
}
in_attack.state &= ~2;
if (in_use.state & 3)
{
cmd->buttons |= BUTTON_USE;
}
in_use.state &= ~2;
// Keyboard events
if (anykeydown && cls.key_dest == key_game)
{
cmd->buttons |= BUTTON_ANY;
}
cmd->impulse = in_impulse;
in_impulse = 0;
// Set light level for muzzle flash
cmd->lightlevel = (byte)cl_lightlevel->value;
}
void
CL_SendCmd(void)
{
sizebuf_t buf;
byte data[128];
int i;
usercmd_t *cmd, *oldcmd;
usercmd_t nullcmd;
int checksumIndex;
memset(&buf, 0, sizeof(buf));
/* save this command off for prediction */
i = cls.netchan.outgoing_sequence & (CMD_BACKUP - 1);
cmd = &cl.cmds[i];
cl.cmd_time[i] = cls.realtime; /* for netgraph ping calculation */
CL_FinalizeCmd();
cl.cmd = *cmd;
if ((cls.state == ca_disconnected) || (cls.state == ca_connecting))
{
return;
}
if (cls.state == ca_connected)
{
if (cls.netchan.message.cursize || (curtime - cls.netchan.last_sent > 1000))
{
Netchan_Transmit(&cls.netchan, 0, buf.data);
}
return;
}
/* send a userinfo update if needed */
if (userinfo_modified)
{
CL_FixUpGender();
userinfo_modified = false;
MSG_WriteByte(&cls.netchan.message, clc_userinfo);
MSG_WriteString(&cls.netchan.message, Cvar_Userinfo());
}
SZ_Init(&buf, data, sizeof(data));
if ((cls.realtime > abort_cinematic) && (cl.cinematictime > 0) &&
!cl.attractloop && (cls.realtime - cl.cinematictime > 1000) &&
(cls.key_dest == key_game))
{
/* skip the rest of the cinematic */
SCR_FinishCinematic();
}
/* begin a client move command */
MSG_WriteByte(&buf, clc_move);
/* save the position for a checksum byte */
checksumIndex = buf.cursize;
MSG_WriteByte(&buf, 0);
/* let the server know what the last frame we
got was, so the next message can be delta
compressed */
if (cl_nodelta->value || !cl.frame.valid || cls.demowaiting)
{
MSG_WriteLong(&buf, -1); /* no compression */
}
else
{
MSG_WriteLong(&buf, cl.frame.serverframe);
}
/* send this and the previous cmds in the message, so
if the last packet was dropped, it can be recovered */
i = (cls.netchan.outgoing_sequence - 2) & (CMD_BACKUP - 1);
cmd = &cl.cmds[i];
memset(&nullcmd, 0, sizeof(nullcmd));
MSG_WriteDeltaUsercmd(&buf, &nullcmd, cmd);
oldcmd = cmd;
i = (cls.netchan.outgoing_sequence - 1) & (CMD_BACKUP - 1);
cmd = &cl.cmds[i];
MSG_WriteDeltaUsercmd(&buf, oldcmd, cmd);
oldcmd = cmd;
i = (cls.netchan.outgoing_sequence) & (CMD_BACKUP - 1);
cmd = &cl.cmds[i];
MSG_WriteDeltaUsercmd(&buf, oldcmd, cmd);
/* calculate a checksum over the move commands */
buf.data[checksumIndex] = COM_BlockSequenceCRCByte(
buf.data + checksumIndex + 1, buf.cursize - checksumIndex - 1,
cls.netchan.outgoing_sequence);
/* deliver the message */
Netchan_Transmit(&cls.netchan, buf.cursize, buf.data);
/* Reinit the current cmd buffer */
cmd = &cl.cmds[cls.netchan.outgoing_sequence & (CMD_BACKUP - 1)];
memset(cmd, 0, sizeof(*cmd));
}