if (bits & QW_CM_UP)
MSG_WriteShort(buf, to->upmove);
if (bits & QW_CM_BUTTONS)
- MSG_WriteShort(buf, to->buttons);
+ MSG_WriteByte(buf, to->buttons);
if (bits & QW_CM_IMPULSE)
- MSG_WriteShort(buf, to->impulse);
+ MSG_WriteByte(buf, to->impulse);
MSG_WriteByte(buf, to->msec);
}
// PROTOCOL_DARKPLACES5 clc_move = 19 bytes total
// PROTOCOL_DARKPLACES6 clc_move = 52 bytes total
// PROTOCOL_DARKPLACES7 clc_move = 56 bytes total
+ // PROTOCOL_QUAKEWORLD clc_move = 34 bytes total (typically, but can reach 43 bytes, or even 49 bytes with roll)
if (cls.protocol == PROTOCOL_QUAKEWORLD)
{
int checksumindex;
// request delta compression if appropriate
if (cl.qw_validsequence && !cl_nodelta.integer && cls.state == ca_connected && !cls.demorecording)
{
+ cl.qw_deltasequence[cls.netcon->qw.outgoing_sequence & QW_UPDATE_MASK] = cl.qw_validsequence;
MSG_WriteByte(&buf, qw_clc_delta);
MSG_WriteByte(&buf, cl.qw_validsequence & 255);
}
+ else
+ cl.qw_deltasequence[cls.netcon->qw.outgoing_sequence & QW_UPDATE_MASK] = -1;
}
else if (cls.protocol == PROTOCOL_QUAKE || cls.protocol == PROTOCOL_QUAKEDP || cls.protocol == PROTOCOL_NEHAHRAMOVIE)
{
void CL_ClearState(void)
{
int i;
+ entity_t *ent;
if (cl_entities) Mem_Free(cl_entities);cl_entities = NULL;
if (cl_csqcentities) Mem_Free(cl_csqcentities);cl_csqcentities = NULL; //[515]: csqc
if (cl_brushmodel_entities) Mem_Free(cl_brushmodel_entities);cl_brushmodel_entities = NULL;
if (cl.entitydatabase) EntityFrame_FreeDatabase(cl.entitydatabase);cl.entitydatabase = NULL;
if (cl.entitydatabase4) EntityFrame4_FreeDatabase(cl.entitydatabase4);cl.entitydatabase4 = NULL;
+ if (cl.entitydatabaseqw) EntityFrameQW_FreeDatabase(cl.entitydatabaseqw);cl.entitydatabaseqw = NULL;
if (cl.scores) Mem_Free(cl.scores);cl.scores = NULL;
if (!sv.active)
// wipe the entire cl structure
memset (&cl, 0, sizeof(cl));
+
+ S_StopAllSounds();
+
// reset the view zoom interpolation
cl.mviewzoom[0] = cl.mviewzoom[1] = 1;
VectorSet(cl_playercrouchmaxs, 16, 16, 24);
}
+ // disable until we get textures for it
+ R_ResetSkyBox();
+
+ ent = &cl_entities[0];
+ // entire entity array was cleared, so just fill in a few fields
+ ent->state_current.active = true;
+ ent->render.model = cl.worldmodel = NULL; // no world model yet
+ ent->render.scale = 1; // some of the renderer still relies on scale
+ ent->render.alpha = 1;
+ ent->render.colormap = -1; // no special coloring
+ ent->render.flags = RENDER_SHADOW | RENDER_LIGHT;
+ Matrix4x4_CreateFromQuakeEntity(&ent->render.matrix, 0, 0, 0, 0, 0, 0, 1);
+ Matrix4x4_Invert_Simple(&ent->render.inversematrix, &ent->render.matrix);
+ CL_BoundingBoxForEntity(&ent->render);
+
+ // noclip is turned off at start
+ noclip_anglehack = false;
+
+ // mark all frames invalid for delta
+ memset(cl.qw_deltasequence, -1, sizeof(cl.qw_deltasequence));
+
CL_Screen_NewMap();
CL_Particles_Clear();
CL_CGVM_Clear();
if (cls.demorecording)
CL_Stop_f();
- // send clc_disconnect 3 times to improve chances of server receiving
- // it (but it still fails sometimes)
- Con_DPrint("Sending clc_disconnect\n");
+ // send disconnect message 3 times to improve chances of server
+ // receiving it (but it still fails sometimes)
memset(&buf, 0, sizeof(buf));
buf.data = bufdata;
buf.maxsize = sizeof(bufdata);
- MSG_WriteByte(&buf, clc_disconnect);
+ if (cls.protocol == PROTOCOL_QUAKEWORLD)
+ {
+ Con_DPrint("Sending drop command\n");
+ MSG_WriteByte(&buf, qw_clc_stringcmd);
+ MSG_WriteString(&buf, "drop");
+ }
+ else
+ {
+ Con_DPrint("Sending clc_disconnect\n");
+ MSG_WriteByte(&buf, clc_disconnect);
+ }
NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol);
NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol);
NetConn_SendUnreliableMessage(cls.netcon, &buf, cls.protocol);
VectorSet(flag->render.colormod, 1, 1, 1);
// attach the flag to the player matrix
Matrix4x4_CreateFromQuakeEntity(&flagmatrix, -f, -22, 0, 0, 0, -45, 1);
- Matrix4x4_Concat(&flag->render.matrix, &flagmatrix, &player->render.matrix);
+ Matrix4x4_Concat(&flag->render.matrix, &player->render.matrix, &flagmatrix);
Matrix4x4_Invert_Simple(&flag->render.inversematrix, &flag->render.matrix);
R_LerpAnimation(&flag->render);
CL_BoundingBoxForEntity(&flag->render);
CL_RelinkStaticEntities();
CL_RelinkBeams();
CL_RelinkEffects();
+ CL_RelinkQWNails();
}
else
csqc_frame = true;
return false;
}
+static void QW_CL_ProcessUserInfo(int slot);
static void QW_CL_RequestNextDownload(void)
{
int i;
case dl_single:
break;
case dl_skin:
- // TODO
+ if (cls.qw_downloadnumber == 0)
+ Con_Printf("Checking skins...\n");
+ for (;cls.qw_downloadnumber < cl.maxclients;cls.qw_downloadnumber++)
+ {
+ if (!cl.scores[cls.qw_downloadnumber].name[0])
+ continue;
+ // check if we need to download the file, and return if so
+ if (!QW_CL_CheckOrDownloadFile(va("skins/%s.pcx", cl.scores[cls.qw_downloadnumber].qw_skin)))
+ return;
+ }
+
+ cls.qw_downloadtype = dl_none;
+
+ // load any newly downloaded skins
+ for (i = 0;i < cl.maxclients;i++)
+ QW_CL_ProcessUserInfo(i);
+
+ // if we're still in signon stages, request the next one
+ if (cls.signon != SIGNONS)
+ {
+ cls.signon = SIGNONS-1;
+ // we'll go to SIGNONS when the first entity update is received
+ MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
+ MSG_WriteString(&cls.netcon->message, va("begin %i", cl.qw_servercount));
+ }
break;
case dl_model:
if (cls.qw_downloadnumber == 0)
cls.qw_downloadnumber = 1;
}
- cls.qw_downloadtype = dl_model;
for (;cls.qw_downloadnumber < MAX_MODELS && cl.model_name[cls.qw_downloadnumber][0];cls.qw_downloadnumber++)
{
// skip submodels
return;
}
+ cls.qw_downloadtype = dl_none;
+
// touch all of the precached models that are still loaded so we can free
// anything that isn't needed
Mod_ClearUsed();
if ((cl.model_precache[i] = Mod_ForName(cl.model_name[i], false, false, false))->Draw == NULL)
Con_Printf("Model %s could not be found or downloaded\n", cl.model_name[i]);
+ // check memory integrity
+ Mem_CheckSentinelsGlobal();
+
+ // now that we have a world model, set up the world entity, renderer
+ // modules and csqc
+ cl_entities[0].render.model = cl.worldmodel = cl.model_precache[1];
+ CL_BoundingBoxForEntity(&cl_entities[0].render);
+
+ R_Modules_NewMap();
+ CL_CGVM_Start();
+
// done checking sounds and models, send a prespawn command now
MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
- MSG_WriteString(&cls.netcon->message, va("prespawn %i 0 %i", cl.qw_servercount, cl.worldmodel ? cl.worldmodel->brush.qw_md4sum2 : 0));
+ MSG_WriteString(&cls.netcon->message, va("prespawn %i 0 %i", cl.qw_servercount, cl.model_precache[1]->brush.qw_md4sum2));
if (cls.qw_downloadmemory)
{
cls.qw_downloadnumber = 1;
}
- cls.qw_downloadtype = dl_sound;
for (;cl.sound_name[cls.qw_downloadnumber][0];cls.qw_downloadnumber++)
{
- // skip subsounds
- if (cl.sound_name[cls.qw_downloadnumber][0] == '*')
- continue;
// check if we need to download the file, and return if so
if (!QW_CL_CheckOrDownloadFile(va("sound/%s", cl.sound_name[cls.qw_downloadnumber])))
return;
}
+ cls.qw_downloadtype = dl_none;
+
// load new sounds and unload old ones
// FIXME: S_ServerSounds does not know about cl.sfx_ sounds
S_ServerSounds(cl.sound_name, cls.qw_downloadnumber);
cl.sound_precache[i] = S_PrecacheSound(cl.sound_name[i], true, false);
}
+ // check memory integrity
+ Mem_CheckSentinelsGlobal();
+
// done with sound downloads, next we check models
MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
MSG_WriteString(&cls.netcon->message, va("modellist %i %i", cl.qw_servercount, 0));
static void QW_CL_ParseDownload(void)
{
- int size = MSG_ReadShort();
+ int size = (signed short)MSG_ReadShort();
int percent = MSG_ReadByte();
+ //Con_Printf("download %i %i%% (%i/%i)\n", size, percent, cls.qw_downloadmemorycursize, cls.qw_downloadmemorymaxsize);
+
// skip the download fragment if playing a demo
if (!cls.netcon)
{
return;
}
+ cls.signon = 2;
cls.qw_downloadnumber = 0;
cls.qw_downloadtype = dl_model;
QW_CL_RequestNextDownload();
return;
}
+ cls.signon = 2;
cls.qw_downloadnumber = 0;
cls.qw_downloadtype = dl_sound;
QW_CL_RequestNextDownload();
}
+static void QW_CL_Skins_f(void)
+{
+ cls.qw_downloadnumber = 0;
+ cls.qw_downloadtype = dl_skin;
+ QW_CL_RequestNextDownload();
+}
+
+static void QW_CL_Changing_f(void)
+{
+ if (cls.qw_downloadmemory) // don't change when downloading
+ return;
+
+ S_StopAllSounds();
+ cl.intermission = 0;
+ cls.signon = 1; // not active anymore, but not disconnected
+ Con_Printf("\nChanging map...\n");
+}
+
void QW_CL_NextUpload(void)
{
int r, percent, size;
cl.scores[slot].colors = topcolor * 16 + bottomcolor;
InfoString_GetValue(cl.scores[slot].qw_userinfo, "*spectator", temp, sizeof(temp));
cl.scores[slot].qw_spectator = temp[0] != 0;
+ InfoString_GetValue(cl.scores[slot].qw_userinfo, "team", cl.scores[slot].qw_team, sizeof(cl.scores[slot].qw_team));
InfoString_GetValue(cl.scores[slot].qw_userinfo, "skin", cl.scores[slot].qw_skin, sizeof(cl.scores[slot].qw_skin));
- // LordHavoc: abusing Draw_CachePic for caching skins...
- cl.scores[slot].qw_skin_cachepic = Draw_CachePic(cl.scores[slot].qw_skin, true);
+ if (!cl.scores[slot].qw_skin[0])
+ strlcpy(cl.scores[slot].qw_skin, "base", sizeof(cl.scores[slot].qw_skin));
+ // TODO: skin cache
}
static void QW_CL_UpdateUserInfo(void)
{
char key[2048];
char value[2048];
+ char temp[32];
strlcpy(key, MSG_ReadString(), sizeof(key));
strlcpy(value, MSG_ReadString(), sizeof(value));
Con_DPrintf("SERVERINFO: %s=%s\n", key, value);
InfoString_SetValue(cl.qw_serverinfo, sizeof(cl.qw_serverinfo), key, value);
+ InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
+ cl.qw_teamplay = atoi(temp);
}
static void QW_CL_ParseNails(void)
int numnails = MSG_ReadByte();
vec_t *v;
unsigned char bits[6];
- cl.qw_num_nails = 0;
for (i = 0;i < numnails;i++)
{
- v = cl.qw_nails[cl.qw_num_nails++];
for (j = 0;j < 6;j++)
bits[j] = MSG_ReadByte();
+ if (cl.qw_num_nails > 255)
+ continue;
+ v = cl.qw_nails[cl.qw_num_nails++];
v[0] = ( ( bits[0] + ((bits[1]&15)<<8) ) <<1) - 4096;
v[1] = ( ( (bits[1]>>4) + (bits[2]<<4) ) <<1) - 4096;
v[2] = ( ( bits[3] + ((bits[4]&15)<<8) ) <<1) - 4096;
- v[3] = 360*(bits[4]>>4)/16;
+ v[3] = -360*(bits[4]>>4)/16;
v[4] = 360*bits[5]/256;
v[5] = 0;
}
Con_DPrint("Serverinfo packet received.\n");
+ // if server is active, we already began a loading plaque
+ if (!sv.active)
+ SCR_BeginLoadingPlaque();
+
// check memory integrity
Mem_CheckSentinelsGlobal();
cls.protocol = protocol;
Con_DPrintf("Server protocol is %s\n", Protocol_NameForEnum(cls.protocol));
+ cl_num_entities = 1;
+
if (protocol == PROTOCOL_QUAKEWORLD)
{
cl.qw_servercount = MSG_ReadLong();
// check memory integrity
Mem_CheckSentinelsGlobal();
- S_StopAllSounds();
- // if server is active, we already began a loading plaque
- if (!sv.active)
- SCR_BeginLoadingPlaque();
-
- // disable until we get textures for it
- R_ResetSkyBox();
-
- memset(cl.csqc_model_precache, 0, sizeof(cl.csqc_model_precache)); //[515]: csqc
- memset(cl.model_precache, 0, sizeof(cl.model_precache));
- memset(cl.sound_precache, 0, sizeof(cl.sound_precache));
-
MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
MSG_WriteString(&cls.netcon->message, va("soundlist %i %i", cl.qw_servercount, 0));
cls.state = ca_connected;
cls.signon = 1;
+
+ // note: on QW protocol we can't set up the gameworld until after
+ // downloads finish...
+ // (we don't even know the name of the map yet)
}
else
{
// check memory integrity
Mem_CheckSentinelsGlobal();
- S_StopAllSounds();
- // if server is active, we already began a loading plaque
- if (!sv.active)
- SCR_BeginLoadingPlaque();
-
- // disable until we get textures for it
- R_ResetSkyBox();
-
- memset(cl.csqc_model_precache, 0, sizeof(cl.csqc_model_precache)); //[515]: csqc
- memset(cl.model_precache, 0, sizeof(cl.model_precache));
- memset(cl.sound_precache, 0, sizeof(cl.sound_precache));
-
// parse model precache list
for (nummodels=1 ; ; nummodels++)
{
for (i=1 ; i<numsounds ; i++)
{
CL_KeepaliveMessage();
-
// Don't lock the sfx here, S_ServerSounds already did that
cl.sound_precache[i] = S_PrecacheSound (cl.sound_name[i], true, false);
}
- }
- // local state
- ent = &cl_entities[0];
- // entire entity array was cleared, so just fill in a few fields
- ent->state_current.active = true;
- ent->render.model = cl.worldmodel = cl.model_precache[1];
- ent->render.scale = 1; // some of the renderer still relies on scale
- ent->render.alpha = 1;
- ent->render.colormap = -1; // no special coloring
- ent->render.flags = RENDER_SHADOW | RENDER_LIGHT;
- Matrix4x4_CreateFromQuakeEntity(&ent->render.matrix, 0, 0, 0, 0, 0, 0, 1);
- Matrix4x4_Invert_Simple(&ent->render.inversematrix, &ent->render.matrix);
- CL_BoundingBoxForEntity(&ent->render);
-
- cl_num_entities = 1;
-
- R_Modules_NewMap();
- CL_CGVM_Start();
-
- // noclip is turned off at start
- noclip_anglehack = false;
+ // we now have the worldmodel so we can set up the game world
+ ent->render.model = cl.worldmodel = cl.model_precache[1];
+ CL_BoundingBoxForEntity(&ent->render);
+ R_Modules_NewMap();
+ CL_CGVM_Start();
+ }
// check memory integrity
Mem_CheckSentinelsGlobal();
{
cl.mtime[1] = cl.mtime[0];
cl.mtime[0] = realtime; // qw has no clock
+ cl.movement_needupdate = true;
+
+ // slightly kill qw player entities each frame
+ for (i = 1;i < cl.maxclients;i++)
+ cl_entities_active[i] = false;
+
+ // kill all qw nails
+ cl.qw_num_nails = 0;
+
+ // fade weapon view kick
+ cl.qw_weaponkick = min(cl.qw_weaponkick + 10 * cl.frametime, 0);
while (1)
{
CL_NextDemo();
else
CL_Disconnect();
- break;
+ return;
case qw_svc_print:
i = MSG_ReadByte();
break;
case qw_svc_smallkick:
- Con_Printf("TODO: qw_svc_smallkick\n");
+ cl.qw_weaponkick = -2;
break;
case qw_svc_bigkick:
- Con_Printf("TODO: qw_svc_bigkick\n");
+ cl.qw_weaponkick = -4;
break;
case qw_svc_muzzleflash:
break;
}
}
+
+ // fully kill the still slightly dead qw player entities each frame
+ for (i = 1;i < cl.maxclients;i++)
+ if (!cl_entities_active[i])
+ cl_entities[i].state_current.active = false;
+
QW_CL_UpdateItemsAndWeapon();
}
else
Cmd_AddCommand("nextul", QW_CL_NextUpload, "sends next fragment of current upload buffer (screenshot for example)");
Cmd_AddCommand("stopul", QW_CL_StopUpload, "aborts current upload (screenshot for example)");
+ Cmd_AddCommand("skins", QW_CL_Skins_f, "downloads missing qw skins from server");
+ Cmd_AddCommand("changing", QW_CL_Changing_f, "sent by qw servers to tell client to wait for level change");
}
void CL_Parse_Shutdown(void)
DrawQ_Pic (x, y, "gfx/brand", 0, 0, 1, 1, 1, 1, 0);
}
+/*
+==============
+SCR_DrawDownload
+==============
+*/
+static void SCR_DrawDownload(void)
+{
+ int len;
+ float x, y;
+ float size = 8;
+ char temp[256];
+ if (!cls.qw_downloadname[0])
+ return;
+ dpsnprintf(temp, sizeof(temp), "Downloading %s ... %3i%%\n", cls.qw_downloadname, cls.qw_downloadpercent);
+ len = strlen(temp);
+ x = (vid_conwidth.integer - len*size) / 2;
+ y = vid_conheight.integer - size;
+ DrawQ_Fill(0, y, vid_conwidth.integer, size, 0, 0, 0, 0.5, 0);
+ DrawQ_String(x, y, temp, len, size, size, 1, 1, 1, 1, 0);
+}
+
//=============================================================================
SCR_DrawBrand();
+ SCR_DrawDownload();
+
SCR_UpdateScreen();
}
int qw_ping;
int qw_packetloss;
int qw_spectator;
+ char qw_team[8];
char qw_skin[MAX_QPATH];
- cachepic_t *qw_skin_cachepic; // skins are loaded as cachepics
} scoreboard_t;
typedef struct cshift_s
// local copy of the server infostring
char qw_serverinfo[MAX_SERVERINFO_STRING];
+ // time of last qw "pings" command sent to server while showing scores
+ double last_ping_request;
+
// used during connect
int qw_servercount;
+ // updated from serverinfo
+ int qw_teamplay;
+
// indicates whether the player is spectating
qboolean qw_spectator;
int qw_num_nails;
vec_t qw_nails[255][6];
+ float qw_weaponkick;
+
int qw_validsequence;
qw_usercmd_t qw_moves[QW_UPDATE_BACKUP];
+
+ int qw_deltasequence[QW_UPDATE_BACKUP];
}
client_state_t;
// LordHavoc: thanks to Fuh for bringing the pure evil of SZ_Print to my
// attention, it has been eradicated from here, its only (former) use in
// all of darkplaces.
- MSG_WriteByte(&cls.netcon->message, clc_stringcmd);
+ if (cls.protocol == PROTOCOL_QUAKEWORLD)
+ MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
+ else
+ MSG_WriteByte(&cls.netcon->message, clc_stringcmd);
SZ_Write(&cls.netcon->message, (const unsigned char *)s, (int)strlen(s) + 1);
}
void MSG_WriteCoord (sizebuf_t *sb, float f, protocolversion_t protocol)
{
- if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE)
+ if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_QUAKEWORLD)
MSG_WriteCoord13i (sb, f);
else if (protocol == PROTOCOL_DARKPLACES1)
MSG_WriteCoord32f (sb, f);
void MSG_WriteAngle (sizebuf_t *sb, float f, protocolversion_t protocol)
{
- if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_DARKPLACES1 || protocol == PROTOCOL_DARKPLACES2 || protocol == PROTOCOL_DARKPLACES3 || protocol == PROTOCOL_DARKPLACES4)
+ if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_DARKPLACES1 || protocol == PROTOCOL_DARKPLACES2 || protocol == PROTOCOL_DARKPLACES3 || protocol == PROTOCOL_DARKPLACES4 || protocol == PROTOCOL_QUAKEWORLD)
MSG_WriteAngle8i (sb, f);
else
MSG_WriteAngle16i (sb, f);
{
static char string[MAX_INPUTLINE];
int l,c;
- for (l = 0;l < (int) sizeof(string) - 1 && (c = MSG_ReadChar()) != -1 && c != 0;l++)
+ for (l = 0;l < (int) sizeof(string) - 1 && (c = MSG_ReadByte()) != -1 && c != 0;l++)
string[l] = c;
string[l] = 0;
return string;
int MSG_ReadBytes (int numbytes, unsigned char *out)
{
int l, c;
- for (l = 0;l < numbytes && (c = MSG_ReadChar()) != -1;l++)
+ for (l = 0;l < numbytes && (c = MSG_ReadByte()) != -1;l++)
out[l] = c;
return l;
}
float MSG_ReadCoord (protocolversion_t protocol)
{
- if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE)
+ if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_QUAKEWORLD)
return MSG_ReadCoord13i();
else if (protocol == PROTOCOL_DARKPLACES1)
return MSG_ReadCoord32f();
float MSG_ReadAngle (protocolversion_t protocol)
{
- if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_DARKPLACES1 || protocol == PROTOCOL_DARKPLACES2 || protocol == PROTOCOL_DARKPLACES3 || protocol == PROTOCOL_DARKPLACES4)
+ if (protocol == PROTOCOL_QUAKE || protocol == PROTOCOL_QUAKEDP || protocol == PROTOCOL_NEHAHRAMOVIE || protocol == PROTOCOL_DARKPLACES1 || protocol == PROTOCOL_DARKPLACES2 || protocol == PROTOCOL_DARKPLACES3 || protocol == PROTOCOL_DARKPLACES4 || protocol == PROTOCOL_QUAKEWORLD)
return MSG_ReadAngle8i ();
else
return MSG_ReadAngle16i ();
*/
void Host_Reconnect_f (void)
{
- if (cmd_source == src_command)
- {
- Con_Print("reconnect is not valid from the console\n");
- return;
- }
- if (Cmd_Argc() != 1)
+ if (cls.protocol == PROTOCOL_QUAKEWORLD)
{
- Con_Print("reconnect : wait for signon messages again\n");
- return;
+ if (cls.qw_downloadmemory) // don't change when downloading
+ return;
+
+ S_StopAllSounds();
+
+ if (cls.netcon)
+ {
+ if (cls.state == ca_connected && cls.signon < SIGNONS)
+ {
+ Con_Printf("reconnecting...\n");
+ MSG_WriteChar(&cls.netcon->message, qw_clc_stringcmd);
+ MSG_WriteString(&cls.netcon->message, "new");
+ }
+ else
+ Con_Printf("Please use connect instead (reconnect not implemented)\n");
+ }
}
- if (!cls.signon)
+ else
{
- //Con_Print("reconnect: no signon, ignoring reconnect\n");
- return;
+ if (Cmd_Argc() != 1)
+ {
+ Con_Print("reconnect : wait for signon messages again\n");
+ return;
+ }
+ if (!cls.signon)
+ {
+ Con_Print("reconnect: no signon, ignoring reconnect\n");
+ return;
+ }
+ cls.signon = 0; // need new connection messages
}
- cls.signon = 0; // need new connection messages
}
/*
// TODO: shouldn't this be a cvar instead?
void Host_FullServerinfo_f (void) // credit: taken from QuakeWorld
{
+ char temp[512];
if (Cmd_Argc() != 2)
{
Con_Printf ("usage: fullserverinfo <complete info string>\n");
}
strlcpy (cl.qw_serverinfo, Cmd_Argv(1), sizeof(cl.qw_serverinfo));
+ InfoString_GetValue(cl.qw_serverinfo, "teamplay", temp, sizeof(temp));
+ cl.qw_teamplay = atoi(temp);
}
/*
sendreliable = true;
}
// outgoing unreliable packet number, and outgoing reliable packet number (0 or 1)
- *((int *)(sendbuffer + 0)) = LittleLong(conn->qw.outgoing_sequence | (sendreliable<<31));
+ *((int *)(sendbuffer + 0)) = LittleLong((unsigned int)conn->qw.outgoing_sequence | ((unsigned int)sendreliable<<31));
// last received unreliable packet number, and last received reliable packet number (0 or 1)
- *((int *)(sendbuffer + 4)) = LittleLong(conn->qw.incoming_sequence | (conn->qw.incoming_reliable_sequence<<31));
+ *((int *)(sendbuffer + 4)) = LittleLong((unsigned int)conn->qw.incoming_sequence | ((unsigned int)conn->qw.incoming_reliable_sequence<<31));
packetLen = 8;
+ conn->qw.outgoing_sequence++;
// client sends qport in every packet
if (conn == cls.netcon)
{
*((short *)(sendbuffer + 8)) = LittleShort(cls.qw_qport);
packetLen += 2;
}
- if (packetLen + (sendreliable ? conn->sendMessageLength : 0) + data->cursize > (int)sizeof(sendbuffer))
+ if (packetLen + (sendreliable ? conn->sendMessageLength : 0) > 1400)
{
Con_Printf ("NetConn_SendUnreliableMessage: reliable message too big %u\n", data->cursize);
return -1;
}
+ // add the reliable message if there is one
if (sendreliable)
{
memcpy(sendbuffer + packetLen, conn->sendMessage, conn->sendMessageLength);
packetLen += conn->sendMessageLength;
+ conn->qw.last_reliable_sequence = conn->qw.outgoing_sequence;
+ }
+ // add the unreliable message if possible
+ if (packetLen + data->cursize <= 1400)
+ {
+ memcpy(sendbuffer + packetLen, data->data, data->cursize);
+ packetLen += data->cursize;
}
- memcpy(sendbuffer + packetLen, data->data, data->cursize);
- packetLen += data->cursize;
- conn->qw.outgoing_sequence++;
NetConn_Write(conn->mysocket, (void *)&sendbuffer, packetLen, &conn->peeraddress);
packetsSent++;
unreliableMessagesSent++;
+
+ // delay later packets to obey rate limit
+ conn->qw.cleartime = max(conn->qw.cleartime, realtime) + packetLen * conn->qw.rate;
+
return 0;
}
else
// we should avoid extensive checking on entities already encountered
int areagridmarknumber;
- // PROTOCOL_QUAKE, PROTOCOL_QUAKEDP, PROTOCOL_NEHAHRAMOVIE
+ // PROTOCOL_QUAKE, PROTOCOL_QUAKEDP, PROTOCOL_NEHAHRAMOVIE, PROTOCOL_QUAKEWORLD
// baseline values
entity_state_t baseline;
// we should avoid extensive checking on entities already encountered
int areagridmarknumber;
- // PROTOCOL_QUAKE, PROTOCOL_QUAKEDP, PROTOCOL_NEHAHRAMOVIE
+ // PROTOCOL_QUAKE, PROTOCOL_QUAKEDP, PROTOCOL_NEHAHRAMOVIE, PROTOCOL_QUAKEWORLD
// baseline values
entity_state_t baseline;
}
+static int QW_TranslateEffects(int qweffects, int number)
+{
+ int effects = 0;
+ if (qweffects & QW_EF_BRIGHTFIELD)
+ effects |= EF_BRIGHTFIELD;
+ if (qweffects & QW_EF_MUZZLEFLASH)
+ effects |= EF_MUZZLEFLASH;
+ if (qweffects & QW_EF_FLAG1)
+ {
+ // mimic FTEQW's interpretation of EF_FLAG1 as EF_NODRAW on non-player entities
+ if (number > cl.maxclients)
+ effects |= EF_NODRAW;
+ else
+ effects |= EF_FLAG1QW;
+ }
+ if (qweffects & QW_EF_FLAG2)
+ {
+ // mimic FTEQW's interpretation of EF_FLAG2 as EF_ADDITIVE on non-player entities
+ if (number > cl.maxclients)
+ effects |= EF_ADDITIVE;
+ else
+ effects |= EF_FLAG2QW;
+ }
+ if (qweffects & QW_EF_RED)
+ {
+ if (qweffects & QW_EF_BLUE)
+ effects |= EF_RED | EF_BLUE;
+ else
+ effects |= EF_RED;
+ }
+ else if (qweffects & QW_EF_BLUE)
+ effects |= EF_BLUE;
+ else if (qweffects & QW_EF_BRIGHTLIGHT)
+ effects |= EF_BRIGHTLIGHT;
+ else if (qweffects & QW_EF_DIMLIGHT)
+ effects |= EF_DIMLIGHT;
+ return effects;
+}
void EntityStateQW_ReadPlayerUpdate(void)
{
s = &ent->state_current;
*s = defaultstate;
s->active = true;
+ s->colormap = enumber;
playerflags = MSG_ReadShort();
MSG_ReadVector(s->origin, cls.protocol);
s->frame = MSG_ReadByte();
+
+ VectorClear(viewangles);
+ VectorClear(velocity);
+
if (playerflags & QW_PF_MSEC)
{
// time difference between last update this player sent to the server,
{
bits = MSG_ReadByte();
if (bits & QW_CM_ANGLE1)
- viewangles[0] = MSG_ReadAngle16i();
+ viewangles[0] = MSG_ReadAngle16i(); // cmd->angles[0]
if (bits & QW_CM_ANGLE2)
- viewangles[1] = MSG_ReadAngle16i();
+ viewangles[1] = MSG_ReadAngle16i(); // cmd->angles[1]
if (bits & QW_CM_ANGLE3)
- viewangles[2] = MSG_ReadAngle16i();
+ viewangles[2] = MSG_ReadAngle16i(); // cmd->angles[2]
if (bits & QW_CM_FORWARD)
- MSG_ReadShort();
+ MSG_ReadShort(); // cmd->forwardmove
if (bits & QW_CM_SIDE)
- MSG_ReadShort();
+ MSG_ReadShort(); // cmd->sidemove
if (bits & QW_CM_UP)
- MSG_ReadShort();
+ MSG_ReadShort(); // cmd->upmove
if (bits & QW_CM_BUTTONS)
- MSG_ReadByte();
+ MSG_ReadByte(); // cmd->buttons
if (bits & QW_CM_IMPULSE)
- MSG_ReadByte();
+ MSG_ReadByte(); // cmd->impulse
+ MSG_ReadByte(); // cmd->msec
}
- VectorClear(velocity);
if (playerflags & QW_PF_VELOCITY1)
velocity[0] = MSG_ReadShort();
if (playerflags & QW_PF_VELOCITY2)
if (playerflags & QW_PF_SKINNUM)
s->skin = MSG_ReadByte();
if (playerflags & QW_PF_EFFECTS)
- s->effects = MSG_ReadByte();
+ s->effects = QW_TranslateEffects(MSG_ReadByte(), enumber);
if (playerflags & QW_PF_WEAPONFRAME)
weaponframe = MSG_ReadByte();
+ else
+ weaponframe = 0;
+
+ if (enumber == cl.playerentity)
+ {
+ // if this is an update on our player, update the angles
+ VectorCopy(cl.viewangles, viewangles);
+ }
// calculate the entity angles from the viewangles
s->angles[0] = viewangles[0] * -0.0333;
VectorCopy(velocity, cl.mvelocity[0]);
cl.stats[STAT_WEAPONFRAME] = weaponframe;
+ if (playerflags & QW_PF_GIB)
+ cl.stats[STAT_VIEWHEIGHT] = 8;
+ else if (playerflags & QW_PF_DEAD)
+ cl.stats[STAT_VIEWHEIGHT] = -16;
+ else
+ cl.stats[STAT_VIEWHEIGHT] = 22;
}
// set the cl_entities_active flag
if (bits & QW_U_SKIN)
s->skin = MSG_ReadByte();
if (bits & QW_U_EFFECTS)
- {
- s->effects = 0;
- qweffects = MSG_ReadByte();
- if (qweffects & QW_EF_BRIGHTFIELD)
- s->effects |= EF_BRIGHTFIELD;
- if (qweffects & QW_EF_MUZZLEFLASH)
- s->effects |= EF_MUZZLEFLASH;
- if (qweffects & QW_EF_FLAG1)
- {
- // mimic FTEQW's interpretation of EF_FLAG1 as EF_NODRAW on non-player entities
- if (s->number > cl.maxclients)
- s->effects |= EF_NODRAW;
- else
- s->effects |= EF_FLAG1QW;
- }
- if (qweffects & QW_EF_FLAG2)
- {
- // mimic FTEQW's interpretation of EF_FLAG2 as EF_ADDITIVE on non-player entities
- if (s->number > cl.maxclients)
- s->effects |= EF_ADDITIVE;
- else
- s->effects |= EF_FLAG2QW;
- }
- if (qweffects & QW_EF_RED)
- {
- if (qweffects & QW_EF_BLUE)
- s->effects |= EF_RED | EF_BLUE;
- else
- s->effects |= EF_RED;
- }
- else if (qweffects & QW_EF_BLUE)
- s->effects |= EF_BLUE;
- else if (qweffects & QW_EF_BRIGHTLIGHT)
- s->effects |= EF_BRIGHTLIGHT;
- else if (qweffects & QW_EF_DIMLIGHT)
- s->effects |= EF_DIMLIGHT;
- }
+ s->effects = QW_TranslateEffects(qweffects = MSG_ReadByte(), s->number);
if (bits & QW_U_ORIGIN1)
s->origin[0] = MSG_ReadCoord13i();
if (bits & QW_U_ANGLE1)
if (bits & QW_U_EFFECTS)
Con_Printf(" U_EFFECTS %i", qweffects);
if (bits & QW_U_ORIGIN1)
- Con_Printf(" U_ORIGIN1 %i", s->origin[0]);
+ Con_Printf(" U_ORIGIN1 %f", s->origin[0]);
if (bits & QW_U_ANGLE1)
- Con_Printf(" U_ANGLE1 %i", s->angles[0]);
+ Con_Printf(" U_ANGLE1 %f", s->angles[0]);
if (bits & QW_U_ORIGIN2)
- Con_Printf(" U_ORIGIN2 %i", s->origin[1]);
+ Con_Printf(" U_ORIGIN2 %f", s->origin[1]);
if (bits & QW_U_ANGLE2)
- Con_Printf(" U_ANGLE2 %i", s->angles[1]);
+ Con_Printf(" U_ANGLE2 %f", s->angles[1]);
if (bits & QW_U_ORIGIN3)
- Con_Printf(" U_ORIGIN3 %i", s->origin[2]);
+ Con_Printf(" U_ORIGIN3 %f", s->origin[2]);
if (bits & QW_U_ANGLE3)
- Con_Printf(" U_ANGLE3 %i", s->angles[2]);
+ Con_Printf(" U_ANGLE3 %f", s->angles[2]);
if (bits & QW_U_SOLID)
Con_Printf(" U_SOLID");
Con_Print("\n");
}
}
+entityframeqw_database_t *EntityFrameQW_AllocDatabase(mempool_t *pool)
+{
+ entityframeqw_database_t *d;
+ d = (entityframeqw_database_t *)Mem_Alloc(pool, sizeof(*d));
+ return d;
+}
+
+void EntityFrameQW_FreeDatabase(entityframeqw_database_t *d)
+{
+ Mem_Free(d);
+}
+
void EntityFrameQW_CL_ReadFrame(qboolean delta)
{
qboolean invalid = false;
int number, oldsnapindex, newsnapindex, oldindex, newindex, oldnum, newnum;
entity_t *ent;
- entityframeqw_database_t *d = cl.entitydatabaseqw;
+ entityframeqw_database_t *d;
entityframeqw_snapshot_t *oldsnap, *newsnap;
+ if (!cl.entitydatabaseqw)
+ cl.entitydatabaseqw = EntityFrameQW_AllocDatabase(cl_mempool);
+ d = cl.entitydatabaseqw;
+
newsnapindex = cls.netcon->qw.incoming_sequence & QW_UPDATE_MASK;
newsnap = d->snapshot + newsnapindex;
memset(newsnap, 0, sizeof(*newsnap));
+ oldsnapindex = -1;
+ oldsnap = NULL;
if (delta)
{
- oldsnapindex = MSG_ReadByte() & QW_UPDATE_MASK;
- oldsnap = d->snapshot + oldsnapindex;
- if (cls.netcon->qw.outgoing_sequence - oldsnapindex >= QW_UPDATE_BACKUP-1)
+ number = MSG_ReadByte();
+ oldsnapindex = cl.qw_deltasequence[newsnapindex];
+ if ((number & QW_UPDATE_MASK) != (oldsnapindex & QW_UPDATE_MASK))
+ Con_DPrintf("WARNING: from mismatch\n");
+ if (oldsnapindex != -1)
{
- Con_DPrintf("delta update too old\n");
- newsnap->invalid = invalid = true; // too old
- delta = false;
+ if (cls.netcon->qw.outgoing_sequence - oldsnapindex >= QW_UPDATE_BACKUP-1)
+ {
+ Con_DPrintf("delta update too old\n");
+ newsnap->invalid = invalid = true; // too old
+ delta = false;
+ }
+ oldsnap = d->snapshot + (oldsnapindex & QW_UPDATE_MASK);
}
- }
- else
- {
- oldsnapindex = -1;
- oldsnap = NULL;
+ else
+ delta = false;
}
// read the number of this frame to echo back in next input packet
{
if (newsnap->num_entities >= QW_MAX_PACKET_ENTITIES)
Host_Error("EntityFrameQW_CL_ReadFrame: newsnap->num_entities == MAX_PACKETENTITIES");
- newsnap->entities[newsnap->num_entities] = (newnum == oldnum) ? oldsnap->entities[oldindex++] : cl_entities[newnum].state_baseline;
+ newsnap->entities[newsnap->num_entities] = (newnum == oldnum) ? oldsnap->entities[oldindex] : cl_entities[newnum].state_baseline;
EntityStateQW_ReadEntityUpdate(newsnap->entities + newsnap->num_entities, word);
newsnap->num_entities++;
}
+
+ if (newnum == oldnum)
+ oldindex++;
}
// expand cl_num_entities to include every entity we've seen this game
CL_ExpandEntities(newnum);
}
- // now update the entities from the snapshot states
- number = 1;
+ // now update the non-player entities from the snapshot states
+ number = cl.maxclients + 1;
for (newindex = 0;;newindex++)
{
newnum = newindex >= newsnap->num_entities ? cl_num_entities : newsnap->entities[newindex].number;
ent = &cl_entities[number];
ent->state_previous = ent->state_current;
ent->state_current = newsnap->entities[newindex];
+ ent->state_current.time = cl.mtime[0];
CL_MoveLerpEntityStates(ent);
// the entity lives again...
cl_entities_active[number] = true;
}
entityframeqw_database_t;
+entityframeqw_database_t *EntityFrameQW_AllocDatabase(mempool_t *pool);
+void EntityFrameQW_FreeDatabase(entityframeqw_database_t *d);
void EntityStateQW_ReadPlayerUpdate(void);
void EntityFrameQW_CL_ReadFrame(qboolean delta);
strcpy(teams[teamlines-1].name, "^3Yellow Team");
else
strcpy(teams[teamlines-1].name, "Total Team Score");
-
+
teams[teamlines-1].frags = 0;
teams[teamlines-1].colors = color + 16 * color;
}
*/
float Sbar_PrintScoreboardItem(scoreboard_t *s, float x, float y)
{
+ int minutes;
unsigned char *c;
- // draw colors behind score
- c = (unsigned char *)&palette_complete[(s->colors & 0xf0) + 8];
- DrawQ_Fill(x + 8, y+1, 32, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), c[3] * (1.0f / 255.0f) * sbar_alpha_fg.value, 0);
- c = (unsigned char *)&palette_complete[((s->colors & 15)<<4) + 8];
- DrawQ_Fill(x + 8, y+4, 32, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), c[3] * (1.0f / 255.0f) * sbar_alpha_fg.value, 0);
- // print the text
- //DrawQ_String(x, y, va("%c%4i %s", (s - cl.scores) == cl.playerentity - 1 ? 13 : ' ', (int) s->frags, s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0);
- // FIXME: use a constant for this color tag instead
- DrawQ_ColoredString(x, y, va("%c%4i %s" STRING_COLOR_DEFAULT_STR, (s - cl.scores) == cl.playerentity - 1 ? 13 : ' ', (int) s->frags, s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL );
+ if (cls.protocol == PROTOCOL_QUAKEWORLD)
+ {
+ // draw colors behind score
+ c = (unsigned char *)&palette_complete[(s->colors & 0xf0) + 8];
+ DrawQ_Fill(x + 14*8, y+1, 32, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), c[3] * (1.0f / 255.0f) * sbar_alpha_fg.value, 0);
+ c = (unsigned char *)&palette_complete[((s->colors & 15)<<4) + 8];
+ DrawQ_Fill(x + 14*8, y+4, 32, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), c[3] * (1.0f / 255.0f) * sbar_alpha_fg.value, 0);
+ // print the text
+ //DrawQ_String(x, y, va("%c%4i %s", (s - cl.scores) == cl.playerentity - 1 ? 13 : ' ', (int) s->frags, s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0);
+ DrawQ_ColoredString(x, y, va("%c%4i %2i %4i %4i %-4s %s", (s - cl.scores) == cl.playerentity - 1 ? 13 : ' ', bound(0, s->qw_ping, 9999), bound(0, s->qw_packetloss, 99), minutes,(int) s->frags, cl.qw_teamplay ? s->qw_team : "", s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL );
+ }
+ else
+ {
+ minutes = (int)((cl.intermission ? cl.completed_time - s->qw_entertime : realtime - s->qw_entertime) / 60.0);
+ // draw colors behind score
+ c = (unsigned char *)&palette_complete[(s->colors & 0xf0) + 8];
+ DrawQ_Fill(x + 1*8, y+1, 32, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), c[3] * (1.0f / 255.0f) * sbar_alpha_fg.value, 0);
+ c = (unsigned char *)&palette_complete[((s->colors & 15)<<4) + 8];
+ DrawQ_Fill(x + 1*8, y+4, 32, 3, c[0] * (1.0f / 255.0f), c[1] * (1.0f / 255.0f), c[2] * (1.0f / 255.0f), c[3] * (1.0f / 255.0f) * sbar_alpha_fg.value, 0);
+ // print the text
+ //DrawQ_String(x, y, va("%c%4i %s", (s - cl.scores) == cl.playerentity - 1 ? 13 : ' ', (int) s->frags, s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0);
+ DrawQ_ColoredString(x, y, va("%c%4i %s", (s - cl.scores) == cl.playerentity - 1 ? 13 : ' ', (int) s->frags, s->name), 0, 8, 8, 1, 1, 1, 1 * sbar_alpha_fg.value, 0, NULL );
+ }
return 8;
}
int i, x, y;
cachepic_t *pic;
+ // request new ping times every two second
+ if (cl.last_ping_request < realtime - 2)
+ {
+ cl.last_ping_request = realtime;
+ if (cls.protocol == PROTOCOL_QUAKEWORLD)
+ {
+ MSG_WriteByte(&cls.netcon->message, qw_clc_stringcmd);
+ MSG_WriteString(&cls.netcon->message, "pings");
+ }
+ }
+
pic = Draw_CachePic ("gfx/ranking", true);
DrawQ_Pic ((vid_conwidth.integer - pic->width)/2, 8, "gfx/ranking", 0, 0, 1, 1, 1, 1 * sbar_alpha_fg.value, 0);
// scores
Sbar_SortFrags ();
// draw the text
- x = (vid_conwidth.integer - (6 + 15) * 8) / 2;
+ if (cls.protocol == PROTOCOL_QUAKEWORLD)
+ x = (vid_conwidth.integer - (6 + 17 + 15) * 8) / 2;
+ else
+ x = (vid_conwidth.integer - (6 + 15) * 8) / 2;
y = 40;
if (Sbar_IsTeammatch ())
return;
}
- cmd = MSG_ReadChar ();
+ cmd = MSG_ReadByte ();
if (cmd == -1)
{
// end of message
if (cl.intermission)
{
// entity is a fixed camera, just copy the matrix
- Matrix4x4_Copy(&r_refdef.viewentitymatrix, &ent->render.matrix);
- Matrix4x4_Copy(&viewmodelmatrix, &ent->render.matrix);
- r_refdef.viewentitymatrix.m[2][3] += cl.stats[STAT_VIEWHEIGHT];
- viewmodelmatrix.m[2][3] += cl.stats[STAT_VIEWHEIGHT];
+ if (cls.protocol == PROTOCOL_QUAKEWORLD)
+ Matrix4x4_CreateFromQuakeEntity(&r_refdef.viewentitymatrix, cl.qw_intermission_origin[0], cl.qw_intermission_origin[1], cl.qw_intermission_origin[2], cl.qw_intermission_angles[0], cl.qw_intermission_angles[1], cl.qw_intermission_angles[2], 1);
+ else
+ {
+ r_refdef.viewentitymatrix = ent->render.matrix;
+ r_refdef.viewentitymatrix.m[2][3] += cl.stats[STAT_VIEWHEIGHT];
+ }
+ viewmodelmatrix = r_refdef.viewentitymatrix;
}
else
{
Matrix4x4_OriginFromMatrix(&ent->render.matrix, vieworg);
VectorCopy(cl.viewangles, viewangles);
+ // apply qw weapon recoil effect (this did not work in QW)
+ // TODO: add a cvar to disable this
+ viewangles[PITCH] += cl.qw_weaponkick;
+
if (cl.onground)
{
if (!cl.oldonground)