Maintain an input journal so that CSQC can implement custom player prediction, if it wants.

This commit is contained in:
Shpoike 2020-09-04 12:18:05 +01:00
parent d4094cbaa0
commit d18b907e43
7 changed files with 187 additions and 60 deletions

View File

@ -308,11 +308,13 @@ Send the intended movement message to the server
*/
void CL_BaseMove (usercmd_t *cmd)
{
Q_memset (cmd, 0, sizeof(*cmd));
VectorCopy(cl.viewangles, cmd->viewangles);
if (cls.signon != SIGNONS)
return;
Q_memset (cmd, 0, sizeof(*cmd));
if (in_strafe.state & 1)
{
cmd->sidemove += cl_sidespeed.value * CL_KeyState (&in_right);
@ -342,6 +344,51 @@ void CL_BaseMove (usercmd_t *cmd)
}
}
void CL_FinishMove(usercmd_t *cmd)
{
unsigned int bits;
//
// send button bits
//
bits = 0;
if ( in_attack.state & 3 )
bits |= 1;
in_attack.state &= ~2;
if (in_jump.state & 3)
bits |= 2;
in_jump.state &= ~2;
if (in_button3.state & 3)
bits |= 4;
in_button3.state &= ~2;
if (in_button4.state & 3)
bits |= 8;
in_button4.state &= ~2;
if (in_button5.state & 3)
bits |= 16;
in_button5.state &= ~2;
if (in_button6.state & 3)
bits |= 32;
in_button6.state &= ~2;
if (in_button7.state & 3)
bits |= 64;
in_button7.state &= ~2;
if (in_button8.state & 3)
bits |= 128;
in_button8.state &= ~2;
cmd->buttons = bits;
cmd->impulse = in_impulse;
in_impulse = 0;
}
/*
==============
@ -351,7 +398,6 @@ CL_SendMove
void CL_SendMove (const usercmd_t *cmd)
{
unsigned int i;
int bits;
sizebuf_t buf;
byte data[1024];
@ -369,7 +415,6 @@ void CL_SendMove (const usercmd_t *cmd)
if (cmd)
{
int dump = buf.cursize;
cl.cmd = *cmd;
//
// send the movement message
@ -378,14 +423,24 @@ void CL_SendMove (const usercmd_t *cmd)
if (cl.protocol == PROTOCOL_VERSION_DP7)
{
if (1)
if (0)
{
MSG_WriteLong(&buf, 0);
MSG_WriteFloat (&buf, cl.mtime[0]); // so server can get ping times
}
else
{
MSG_WriteLong(&buf, cl.movemessages);
MSG_WriteFloat (&buf, cmd->servertime); // for input timing
}
}
else if (cl.protocol_pext2 & PEXT2_PREDINFO)
{
MSG_WriteShort(&buf, cl.movemessages&0xffff); //server will ack this once it has been applied to the player's entity state
MSG_WriteFloat (&buf, cl.mtime[0]); // so server can get ping times
MSG_WriteFloat (&buf, cmd->servertime); // so server can get cmd timing (pings will be calculated by entframe acks).
}
else
MSG_WriteFloat (&buf, cl.mtime[0]); // so server can get ping times
for (i=0 ; i<3 ; i++)
//johnfitz -- 16-bit angles for PROTOCOL_FITZQUAKE
@ -402,47 +457,11 @@ void CL_SendMove (const usercmd_t *cmd)
MSG_WriteShort (&buf, cmd->sidemove);
MSG_WriteShort (&buf, cmd->upmove);
//
// send button bits
//
bits = 0;
if ( in_attack.state & 3 )
bits |= 1;
in_attack.state &= ~2;
if (in_jump.state & 3)
bits |= 2;
in_jump.state &= ~2;
if (in_button3.state & 3)
bits |= 4;
in_button3.state &= ~2;
if (in_button4.state & 3)
bits |= 8;
in_button4.state &= ~2;
if (in_button5.state & 3)
bits |= 16;
in_button5.state &= ~2;
if (in_button6.state & 3)
bits |= 32;
in_button6.state &= ~2;
if (in_button7.state & 3)
bits |= 64;
in_button7.state &= ~2;
if (in_button8.state & 3)
bits |= 128;
in_button8.state &= ~2;
if (cl.protocol == PROTOCOL_VERSION_DP7)
{
MSG_WriteLong (&buf, bits);
MSG_WriteByte (&buf, in_impulse);
MSG_WriteLong (&buf, cmd->buttons);
MSG_WriteByte (&buf, cmd->impulse);
MSG_WriteShort(&buf, 32767);//cursor x
MSG_WriteShort(&buf, 32767);//cursor y
MSG_WriteFloat(&buf, r_refdef.vieworg[0]); //start (view pos)
@ -455,11 +474,13 @@ void CL_SendMove (const usercmd_t *cmd)
}
else
{
MSG_WriteByte (&buf, bits);
MSG_WriteByte (&buf, in_impulse);
MSG_WriteByte (&buf, cmd->buttons);
MSG_WriteByte (&buf, cmd->impulse);
}
in_impulse = 0;
cl.movecmds[cl.movemessages&MOVECMDS_MASK] = *cmd;
//
// allways dump the first two message, because it may contain leftover inputs
// from the last level

View File

@ -417,6 +417,8 @@ void CL_DecayLights (void)
float time;
time = cl.time - cl.oldtime;
if (time < 0)
return;
dl = cl_dlights;
for (i=0 ; i<MAX_DLIGHTS ; i++, dl++)
@ -1175,6 +1177,46 @@ void CL_AccumulateCmd (void)
//accumulate movement from other devices
IN_Move (&cl.pendingcmd);
}
cl.pendingcmd.seconds = cl.mtime[0] - cl.pendingcmd.servertime;
}
void CL_CSQC_SetInputs(usercmd_t *cmd, qboolean set)
{
if (set)
{
if (qcvm->extglobals.input_timelength)
*qcvm->extglobals.input_timelength = cmd->seconds;
if (qcvm->extglobals.input_angles)
VectorCopy(cmd->viewangles, qcvm->extglobals.input_angles);
if (qcvm->extglobals.input_movevalues)
{
qcvm->extglobals.input_movevalues[0] = cmd->forwardmove;
qcvm->extglobals.input_movevalues[1] = cmd->sidemove;
qcvm->extglobals.input_movevalues[2] = cmd->upmove;
}
if (qcvm->extglobals.input_buttons)
*qcvm->extglobals.input_buttons = cmd->buttons;
if (qcvm->extglobals.input_impulse)
*qcvm->extglobals.input_impulse = cmd->impulse;
}
else
{
if (qcvm->extglobals.input_timelength)
cmd->seconds = *qcvm->extglobals.input_timelength;
if (qcvm->extglobals.input_angles)
VectorCopy(qcvm->extglobals.input_angles, cmd->viewangles);
if (qcvm->extglobals.input_movevalues)
{
cmd->forwardmove = qcvm->extglobals.input_movevalues[0];
cmd->sidemove = qcvm->extglobals.input_movevalues[1];
cmd->upmove = qcvm->extglobals.input_movevalues[2];
}
if (qcvm->extglobals.input_buttons)
cmd->buttons = *qcvm->extglobals.input_buttons;
if (qcvm->extglobals.input_impulse)
cmd->impulse = *qcvm->extglobals.input_impulse;
}
}
/*
@ -1189,22 +1231,34 @@ void CL_SendCmd (void)
if (cls.state != ca_connected)
return;
if (cls.signon == SIGNONS)
{
// get basic movement from keyboard
CL_BaseMove (&cmd);
CL_BaseMove (&cmd);
// allow mice or other external controllers to add to the move
cmd.forwardmove += cl.pendingcmd.forwardmove;
cmd.sidemove += cl.pendingcmd.sidemove;
cmd.upmove += cl.pendingcmd.upmove;
cmd.forwardmove += cl.pendingcmd.forwardmove;
cmd.sidemove += cl.pendingcmd.sidemove;
cmd.upmove += cl.pendingcmd.upmove;
cmd.sequence = cl.movemessages;
cmd.servertime = cl.time;
cmd.seconds = cmd.servertime - cl.pendingcmd.servertime;
// send the unreliable message
CL_SendMove (&cmd);
CL_FinishMove(&cmd);
if (cl.qcvm.extfuncs.CSQC_Input_Frame && !cl.qcvm.nogameaccess)
{
PR_SwitchQCVM(&cl.qcvm);
CL_CSQC_SetInputs(&cmd, true);
PR_ExecuteProgram(cl.qcvm.extfuncs.CSQC_Input_Frame);
// CL_CSQC_SetInputs(&cmd, false);
PR_SwitchQCVM(NULL);
}
if (cls.signon == SIGNONS)
CL_SendMove (&cmd); // send the unreliable message
else
CL_SendMove (NULL);
memset(&cl.pendingcmd, 0, sizeof(cl.pendingcmd));
cl.pendingcmd.servertime = cmd.servertime;
if (cls.demoplayback)
{

View File

@ -652,7 +652,15 @@ static void CLFTE_ParseEntitiesUpdate(void)
cl.ackframes[cl.ackframes_count++] = NET_QSocketGetSequenceIn(cls.netcon);
if (cl.protocol_pext2 & PEXT2_PREDINFO)
MSG_ReadShort(); //an ack from our input sequences. strictly ascending-or-equal
{
int seq = (cl.movemessages&0xffff0000) | (unsigned short)MSG_ReadShort(); //an ack from our input sequences. strictly ascending-or-equal
if (seq > cl.movemessages)
seq -= 0x10000; //check for cl.movemessages overflowing the low 16 bits, and compensate.
cl.ackedmovemessages = seq;
if (cl.qcvm.extglobals.servercommandframe)
*cl.qcvm.extglobals.servercommandframe = cl.ackedmovemessages;
}
newtime = MSG_ReadFloat ();
if (newtime != cl.mtime[0])
@ -1065,7 +1073,9 @@ static void CLDP_ParseEntitiesUpdate(void)
ack = MSG_ReadLong(); //delta sequence number (must be acked)
if (cl.ackframes_count < sizeof(cl.ackframes)/sizeof(cl.ackframes[0]))
cl.ackframes[cl.ackframes_count++] = ack;
MSG_ReadLong(); //input sequence ack
cl.ackedmovemessages = MSG_ReadLong(); //input sequence ack
if (cl.qcvm.extglobals.servercommandframe)
*cl.qcvm.extglobals.servercommandframe = cl.ackedmovemessages;
for(;;)
{

View File

@ -164,7 +164,9 @@ typedef struct
// throw out the first couple, so the player
// doesn't accidentally do something the
// first frame
usercmd_t cmd; // last command sent to the server
int ackedmovemessages; // echo of movemessages from the server.
usercmd_t movecmds[64]; // ringbuffer of previous movement commands (journal for prediction)
#define MOVECMDS_MASK (countof(cl.movecmds)-1)
usercmd_t pendingcmd; // accumulated state from mice+joysticks.
// information for local display
@ -406,6 +408,7 @@ void CL_SendMove (const usercmd_t *cmd);
int CL_ReadFromServer (void);
void CL_AdjustAngles (void);
void CL_BaseMove (usercmd_t *cmd);
void CL_FinishMove(usercmd_t *cmd);
void CL_Download_Data(void);
qboolean CL_CheckDownloads(void);

View File

@ -6407,6 +6407,38 @@ static void PF_cs_setlistener(void)
VectorCopy(up, cl.listener_axis[2]);
}
void CL_CSQC_SetInputs(usercmd_t *cmd, qboolean set);
static void PF_cs_getinputstate(void)
{
unsigned int seq = G_FLOAT(OFS_PARM0);
if (seq == cl.movemessages)
{ //the partial/pending frame!
G_FLOAT(OFS_RETURN) = 1;
CL_CSQC_SetInputs(&cl.pendingcmd, true);
}
else if (cl.movecmds[seq&MOVECMDS_MASK].sequence == seq)
{ //valid sequence slot.
G_FLOAT(OFS_RETURN) = 1;
CL_CSQC_SetInputs(&cl.movecmds[seq&MOVECMDS_MASK], true);
}
else
G_FLOAT(OFS_RETURN) = 0; //invalid
}
static void PF_touchtriggers (void)
{
edict_t *e;
float *org;
e = (qcvm->argc > 0)?G_EDICT(OFS_PARM0):G_EDICT(pr_global_struct->self);
if (qcvm->argc > 1)
{
org = G_VECTOR(OFS_PARM1);
VectorCopy (org, e->v.origin);
}
SV_LinkEdict (e, true);
}
//A quick note on number ranges.
//0: automatically assigned. more complicated, but no conflicts over numbers, just names...
// NOTE: #0 is potentially ambiguous - vanilla will interpret it as instruction 0 (which is normally reserved) rather than a builtin.
@ -6556,7 +6588,7 @@ static struct
// {"processmodelevents",PF_processmodelevents,PF_processmodelevents,0,PF_NoMenu, D("void(float modidx, float framenum, __inout float basetime, float targettime, void(float timestamp, int code, string data) callback)", "Calls a callback for each event that has been reached. Basetime is set to targettime.")},
// {"getnextmodelevent",PF_getnextmodelevent,PF_getnextmodelevent,0, PF_NoMenu, D("float(float modidx, float framenum, __inout float basetime, float targettime, __out int code, __out string data)", "Reports the next event within a model's animation. Returns a boolean if an event was found between basetime and targettime. Writes to basetime,code,data arguments (if an event was found, basetime is set to the event's time, otherwise to targettime).\nWARNING: this builtin cannot deal with multiple events with the same timestamp (only the first will be reported).")},
// {"getmodeleventidx",PF_getmodeleventidx,PF_getmodeleventidx,0, PF_NoMenu, D("float(float modidx, float framenum, int eventidx, __out float timestamp, __out int code, __out string data)", "Reports an indexed event within a model's animation. Writes to timestamp,code,data arguments on success. Returns false if the animation/event/model was out of range/invalid. Does not consider looping animations (retry from index 0 if it fails and you know that its a looping animation). This builtin is more annoying to use than getnextmodelevent, but can be made to deal with multiple events with the exact same timestamp.")},
/// {"touchtriggers", PF_touchtriggers, PF_touchtriggers, 279, PF_NoMenu, D("void(optional entity ent, optional vector neworigin)", "Triggers a touch events between self and every SOLID_TRIGGER entity that it is in contact with. This should typically just be the triggers touch functions. Also optionally updates the origin of the moved entity.")},//
{"touchtriggers", PF_touchtriggers, PF_touchtriggers, 279, PF_NoMenu, D("void(optional entity ent, optional vector neworigin)", "Triggers a touch events between self and every SOLID_TRIGGER entity that it is in contact with. This should typically just be the triggers touch functions. Also optionally updates the origin of the moved entity.")},//
{"WriteFloat", PF_WriteFloat, PF_NoCSQC, 280, PF_NoMenu, "void(float buf, float fl)"},
// {"skel_ragupdate", PF_skel_ragedit, PF_skel_ragedit, 281, PF_NoMenu, D("float(entity skelent, string dollcmd, float animskel)", "Updates the skeletal object attached to the entity according to its origin and other properties.\nif animskel is non-zero, the ragdoll will animate towards the bone state in the animskel skeletal object, otherwise they will pick up the model's base pose which may not give nice results.\nIf dollcmd is not set, the ragdoll will update (this should be done each frame).\nIf the doll is updated without having a valid doll, the model's default .doll will be instanciated.\ncommands:\n doll foo.doll : sets up the entity to use the named doll file\n dollstring TEXT : uses the doll file directly embedded within qc, with that extra prefix.\n cleardoll : uninstanciates the doll without destroying the skeletal object.\n animate 0.5 : specifies the strength of the ragdoll as a whole \n animatebody somebody 0.5 : specifies the strength of the ragdoll on a specific body (0 will disable ragdoll animations on that body).\n enablejoint somejoint 1 : enables (or disables) a joint. Disabling joints will allow the doll to shatter.")}, // (FTE_CSQC_RAGDOLL)
// {"skel_mmap", PF_skel_mmap, PF_skel_mmap, 282, PF_NoMenu, D("float*(float skel)", "Map the bones in VM memory. They can then be accessed via pointers. Each bone is 12 floats, the four vectors interleaved (sadly).")},// (FTE_QC_RAGDOLL)
@ -6628,7 +6660,7 @@ static struct
{"setcursormode", PF_NoSSQC, PF_cl_setcursormode,343, PF_cl_setcursormode,343, D("void(float usecursor, optional string cursorimage, optional vector hotspot, optional float scale)", "Pass TRUE if you want the engine to release the mouse cursor (absolute input events + touchscreen mode). Pass FALSE if you want the engine to grab the cursor (relative input events + standard looking). If the image name is specified, the engine will use that image for a cursor (use an empty string to clear it again), in a way that will not conflict with the console. Images specified this way will be hardware accelerated, if supported by the platform/port.")},
{"getcursormode", PF_NoSSQC, PF_cl_getcursormode,0, PF_cl_getcursormode,0, D("float(float effective)", "Reports the cursor mode this module previously attempted to use. If 'effective' is true, reports the cursor mode currently active (if was overriden by a different module which has precidence, for instance, or if there is only a touchscreen and no mouse).")},
{"getmousepos", PF_NoSSQC, PF_NoCSQC, 344, PF_m_getmousepos,66, D("vector()", "Nasty convoluted DP extension. Typically returns deltas instead of positions. Use CSQC_InputEvent for such things in csqc mods.")}, // #344 This is a DP extension
// {"getinputstate", PF_NoSSQC, PF_FullCSQCOnly, 345, PF_NoMenu, D("float(float inputsequencenum)", "Looks up an input frame from the log, setting the input_* globals accordingly.\nThe sequence number range used for prediction should normally be servercommandframe < sequence <= clientcommandframe.\nThe sequence equal to clientcommandframe will change between input frames.")},// (EXT_CSQC)
{"getinputstate", PF_NoSSQC, PF_cs_getinputstate,345, PF_NoMenu, D("float(float inputsequencenum)", "Looks up an input frame from the log, setting the input_* globals accordingly.\nThe sequence number range used for prediction should normally be servercommandframe < sequence <= clientcommandframe.\nThe sequence equal to clientcommandframe will change between input frames.")},// (EXT_CSQC)
{"setsensitivityscaler",PF_NoSSQC, PF_cl_setsensitivity,346, PF_NoMenu, D("void(float sens)", "Temporarily scales the player's mouse sensitivity based upon something like zoom, avoiding potential cvar saving and thus corruption.")},// (EXT_CSQC)
// {"runstandardplayerphysics",NULL, PF_FullCSQCOnly, 347, PF_NoMenu, D("void(entity ent)", "Perform the engine's standard player movement prediction upon the given entity using the input_* globals to describe movement.")},
{"getplayerkeyvalue",NULL, PF_cl_playerkey_s, 348, PF_NoMenu, D("string(float playernum, string keyname)", "Look up a player's userinfo, to discover things like their name, topcolor, bottomcolor, skin, team, *ver.\nAlso includes scoreboard info like frags, ping, pl, userid, entertime, as well as voipspeaking and voiploudness.")},// (EXT_CSQC)

View File

@ -461,12 +461,19 @@ extern entity_state_t nullentitystate; //note: not all null.
typedef struct
{
float servertime;
float seconds; //servertime-previous->servertime
vec3_t viewangles;
// intended velocities
float forwardmove;
float sidemove;
float upmove;
unsigned int buttons;
unsigned int impulse;
unsigned int sequence;
} usercmd_t;
#endif /* _QUAKE_PROTOCOL_H */

View File

@ -201,7 +201,7 @@ void V_DriftPitch (void)
// don't count small mouse motion
if (cl.nodrift)
{
if ( fabs(cl.cmd.forwardmove) < cl_forwardspeed.value)
if ( fabs(cl.movecmds[(cl.movemessages-1)&MOVECMDS_MASK].forwardmove) < cl_forwardspeed.value)
cl.driftmove = 0;
else
cl.driftmove += host_frametime;