yquake2remaster/src/client/cl_input.c
Yamagi Burmeister 4ae8706d22 Make the client asynchronous, e.g. decouble net and refresh frames.
This is largely based upon the cl_async 1 mode from KMQuake2, which in
turn is based upon r1q2. The origins of this code may be even older...
Different to KMQuake2 the asynchonous mode is not optional, the client
is always asynchonous. Since we're mainly integrating this rather
fundamental change to simplify the complex internal timing between
client, server and refresh, there's no point in keeping it optional.

The old cl_maxfps cvar controls the network frames. 30 frames should be
enough, even Q3A hasn't more. The new gl_maxfps cvar controls the render
frames. It's set to 95 fps by default to avoid possible remnant of the
famous 125hz bug.
2016-08-04 17:36:42 +02:00

855 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 "../backends/generic/header/input.h"
cvar_t *cl_nodelta;
extern unsigned sys_frame_time;
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 (cmd->buttons && (cl.cinematictime > 0) && !cl.attractloop &&
(cls.realtime - cl.cinematictime > 1000))
{
/* 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));
}