From dc4baabf4caaa4d402a16c055ef99cf9cb3bfee5 Mon Sep 17 00:00:00 2001 From: Spoike Date: Fri, 26 Aug 2005 22:50:31 +0000 Subject: [PATCH] These'll be for q3 client support git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@1248 fc73d0e0-1445-4013-8a0c-d673dee63da5 --- engine/client/cl_cg.c | 1076 ++++++++++++++++++++++++++++++++++++ engine/client/clq3_parse.c | 1013 +++++++++++++++++++++++++++++++++ engine/client/clq3defs.h | 312 +++++++++++ 3 files changed, 2401 insertions(+) create mode 100644 engine/client/cl_cg.c create mode 100644 engine/client/clq3_parse.c create mode 100644 engine/client/clq3defs.h diff --git a/engine/client/cl_cg.c b/engine/client/cl_cg.c new file mode 100644 index 000000000..0f046c641 --- /dev/null +++ b/engine/client/cl_cg.c @@ -0,0 +1,1076 @@ +#include "quakedef.h" +//#include "cg_public.h" +#ifdef VM_CG + +#ifdef RGLQUAKE + +#include "shader.h" + +#if 1 +#include "glquake.h"//hack +#else +typedef float m3by3_t[3][3]; +#endif + +#include "clq3defs.h" + +//cl_ui.c +int VMUI_fopen (char *name, int *handle, int fmode, int owner); +void VMUI_FRead (char *dest, int quantity, int fnum, int owner); //consistancy is in the eye of the beholder.. :) +void VMUI_fclose (int fnum, int owner); +void VMUI_fcloseall (int owner); +typedef struct q3refEntity_s q3refEntity_t; +void VQ3_AddEntity(const q3refEntity_t *q3); +typedef struct q3refdef_s q3refdef_t; +void VQ3_RenderView(const q3refdef_t *ref); +void CG_Command_f(void); + +void GLDraw_ShaderImage (int x, int y, int w, int h, float s1, float t1, float s2, float t2, shader_t *pic); + +#define CGAME_IMPORT_API_VERSION 4 + +#define CGTAGNUM 5423 + +typedef enum { + CG_PRINT, + CG_ERROR, + CG_MILLISECONDS, + CG_CVAR_REGISTER, + CG_CVAR_UPDATE, + CG_CVAR_SET, + CG_CVAR_VARIABLESTRINGBUFFER, + CG_ARGC, + CG_ARGV, + CG_ARGS, + CG_FS_FOPENFILE, //10 + CG_FS_READ, + CG_FS_WRITE, + CG_FS_FCLOSEFILE, + CG_SENDCONSOLECOMMAND, + CG_ADDCOMMAND, + CG_SENDCLIENTCOMMAND, + CG_UPDATESCREEN, + CG_CM_LOADMAP, + CG_CM_NUMINLINEMODELS, + CG_CM_INLINEMODEL, //20 + CG_CM_LOADMODEL, + CG_CM_TEMPBOXMODEL, + CG_CM_POINTCONTENTS, + CG_CM_TRANSFORMEDPOINTCONTENTS, + CG_CM_BOXTRACE, + CG_CM_TRANSFORMEDBOXTRACE, + CG_CM_MARKFRAGMENTS, + CG_S_STARTSOUND, + CG_S_STARTLOCALSOUND, + CG_S_CLEARLOOPINGSOUNDS, //30 + CG_S_ADDLOOPINGSOUND, + CG_S_UPDATEENTITYPOSITION, + CG_S_RESPATIALIZE, + CG_S_REGISTERSOUND, + CG_S_STARTBACKGROUNDTRACK, + CG_R_LOADWORLDMAP, + CG_R_REGISTERMODEL, + CG_R_REGISTERSKIN, + CG_R_REGISTERSHADER, + CG_R_CLEARSCENE, //40 + CG_R_ADDREFENTITYTOSCENE, + CG_R_ADDPOLYTOSCENE, + CG_R_ADDLIGHTTOSCENE, + CG_R_RENDERSCENE, + CG_R_SETCOLOR, + CG_R_DRAWSTRETCHPIC, + CG_R_MODELBOUNDS, + CG_R_LERPTAG, + CG_GETGLCONFIG, + CG_GETGAMESTATE, //50 + CG_GETCURRENTSNAPSHOTNUMBER, + CG_GETSNAPSHOT, + CG_GETSERVERCOMMAND, + CG_GETCURRENTCMDNUMBER, + CG_GETUSERCMD, + CG_SETUSERCMDVALUE, + CG_R_REGISTERSHADERNOMIP, + CG_MEMORY_REMAINING, + CG_R_REGISTERFONT, + CG_KEY_ISDOWN, //60 + CG_KEY_GETCATCHER, + CG_KEY_SETCATCHER, + CG_KEY_GETKEY, + CG_PC_ADD_GLOBAL_DEFINE, + CG_PC_LOAD_SOURCE, + CG_PC_FREE_SOURCE, + CG_PC_READ_TOKEN, + CG_PC_SOURCE_FILE_AND_LINE, + CG_S_STOPBACKGROUNDTRACK, + CG_REAL_TIME, //70 + CG_SNAPVECTOR, + CG_REMOVECOMMAND, + CG_R_LIGHTFORPOINT, //73 + CG_CIN_PLAYCINEMATIC, //64 + CG_CIN_STOPCINEMATIC, //75 + CG_CIN_RUNCINEMATIC, //76 + CG_CIN_DRAWCINEMATIC, //77 + CG_CIN_SETEXTENTS, //78 + CG_R_REMAP_SHADER, //79 + CG_S_ADDREALLOOPINGSOUND, //80 + CG_S_STOPLOOPINGSOUND, //81 + + CG_CM_TEMPCAPSULEMODEL, //82 + CG_CM_CAPSULETRACE, //83 + CG_CM_TRANSFORMEDCAPSULETRACE, //84 + CG_R_ADDADDITIVELIGHTTOSCENE, //85 + CG_GET_ENTITY_TOKEN, //86 + CG_R_ADDPOLYSTOSCENE, //87 + CG_R_INPVS, //88 + // 1.32 + CG_FS_SEEK, //89 + + CG_MEMSET = 100, + CG_MEMCPY, + CG_STRNCPY, + CG_SIN, + CG_COS, + CG_ATAN2, + CG_SQRT, + CG_FLOOR, + CG_CEIL, + CG_TESTPRINTINT, + CG_TESTPRINTFLOAT, + CG_ACOS +} cgameImport_t; + + +#define VM_FROMHANDLE(a) ((void*)a) +#define VM_TOHANDLE(a) ((int)a) + +/* +================================================================== + +functions exported to the main executable + +================================================================== +*/ + +typedef enum { + CG_INIT, +// void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) + // called when the level loads or when the renderer is restarted + // all media should be registered at this time + // cgame will display loading status by calling SCR_Update, which + // will call CG_DrawInformation during the loading process + // reliableCommandSequence will be 0 on fresh loads, but higher for + // demos, tourney restarts, or vid_restarts + + CG_SHUTDOWN, +// void (*CG_Shutdown)( void ); + // oportunity to flush and close any open files + + CG_CONSOLE_COMMAND, +// qboolean (*CG_ConsoleCommand)( void ); + // a console command has been issued locally that is not recognized by the + // main game system. + // use Cmd_Argc() / Cmd_Argv() to read the command, return qfalse if the + // command is not known to the game + + CG_DRAW_ACTIVE_FRAME, +// void (*CG_DrawActiveFrame)( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ); + // Generates and draws a game scene and status information at the given time. + // If demoPlayback is set, local movement prediction will not be enabled + + CG_CROSSHAIR_PLAYER, +// int (*CG_CrosshairPlayer)( void ); + + CG_LAST_ATTACKER, +// int (*CG_LastAttacker)( void ); + + CG_KEY_EVENT, +// void (*CG_KeyEvent)( int key, qboolean down ); + + CG_MOUSE_EVENT, +// void (*CG_MouseEvent)( int dx, int dy ); + CG_EVENT_HANDLING +// void (*CG_EventHandling)(int type); +} cgameExport_t; + + + + + + + + +unsigned int Contents_To_Q3(unsigned int fte) +{ + int ret = 0; + + if (fte & FTECONTENTS_SOLID) //should use q3 constants. + ret |= 1; + if (fte & FTECONTENTS_WATER) //should use q3 constants. + ret |= 32; + if (fte & FTECONTENTS_SLIME) //should use q3 constants. + ret |= 16; + if (fte & FTECONTENTS_LAVA) //should use q3 constants. + ret |= 8; + if (fte & FTECONTENTS_SKY) //should use q3 constants. + ret |= 0x80000000; + + return ret; +} +unsigned int Contents_From_Q3(unsigned int Q3) +{ + int ret = 0; + + if (Q3 & 1) //should use q3 constants. + ret |= FTECONTENTS_SOLID; + if (Q3 & 32) //should use q3 constants. + ret |= FTECONTENTS_WATER; + if (Q3 & 16) //should use q3 constants. + ret |= FTECONTENTS_SLIME; + if (Q3 & 8) //should use q3 constants. + ret |= FTECONTENTS_LAVA; + if (Q3 & 0x80000000) //should use q3 constants. + ret |= FTECONTENTS_SKY; + + return ret; +} + +#define MAX_GAMESTATE_CHARS 16000 +#define MAX_CONFIGSTRINGS 1024 +typedef struct { + int stringOffsets[MAX_CONFIGSTRINGS]; + char stringData[MAX_GAMESTATE_CHARS]; + int dataCount; +} gameState_t; +gameState_t cggamestate; + +void CG_InsertIntoGameState(int num, char *str) +{ + if (num < 5) + { + Con_DPrintf("%i: %s", num, str); + } + + if (cggamestate.dataCount + strlen(str)+1 > MAX_GAMESTATE_CHARS) + { + char oldstringData[MAX_GAMESTATE_CHARS]; + int i; + char *oldstr; + //copy the old strings to a temporary buffer + memcpy(oldstringData, cggamestate.stringData, MAX_GAMESTATE_CHARS); + cggamestate.dataCount = 0; + for (i = 0; i < MAX_CONFIGSTRINGS; i++) + { + oldstr = oldstringData+cggamestate.stringOffsets[i]; + if (*oldstr) + { + if (cggamestate.dataCount + strlen(oldstr)+1 > MAX_GAMESTATE_CHARS) + Host_EndGame("Too much configstring text\n"); + + cggamestate.dataCount+=1; + strcpy(cggamestate.stringData+cggamestate.dataCount, oldstr); + cggamestate.stringOffsets[i] = cggamestate.dataCount; + cggamestate.dataCount += strlen(oldstr); + } + else + cggamestate.stringOffsets[i] = 0; + } + } + + if (!*str) + { + cggamestate.stringOffsets[num] = 0; + return; + } + + cggamestate.dataCount+=1; + strcpy(cggamestate.stringData+cggamestate.dataCount, str); + cggamestate.stringOffsets[num] = cggamestate.dataCount; + cggamestate.dataCount += strlen(str); +} + +char *CG_GetConfigString(int num) +{ + if ((unsigned)num >= MAX_CONFIGSTRINGS) + return ""; + return cggamestate.stringData + cggamestate.stringOffsets[num]; +} + +int CG_GetGameState(gameState_t *gs) +{ + memcpy(gs, &cggamestate, sizeof(gameState_t)); + return sizeof(gameState_t); +} + +typedef struct { + int serverTime; + int angles[3]; + int buttons; + qbyte weapon; // weapon + signed char forwardmove, rightmove, upmove; +} q3usercmd_t; +#define CMD_MASK Q3UPDATE_MASK +qboolean CGQ3_GetUserCmd(int cmdNumber, q3usercmd_t *ucmd) +{ + usercmd_t *cmd; + cmdNumber--; + + if (cmdNumber > ccs.currentUserCmdNumber) + Host_EndGame("CL_GetUserCmd: cmdNumber > ccs.currentUserCmdNumber"); + + if (ccs.currentUserCmdNumber - cmdNumber > CMD_MASK) + return false; // too old + + cmd = &cl.frames[(cmdNumber) & CMD_MASK].cmd[0]; + ucmd->angles[0] = cmd->angles[0]; + ucmd->angles[1] = cmd->angles[1]; + ucmd->angles[2] = cmd->angles[2]; + ucmd->serverTime = cmd->servertime; + ucmd->forwardmove = cmd->forwardmove; + ucmd->rightmove = cmd->sidemove; + ucmd->upmove = cmd->upmove; + ucmd->buttons = cmd->buttons; + ucmd->weapon = cmd->weapon; + + return true; +} + +static vm_t *cgvm; + +static int keycatcher; + +qboolean CG_GetServerCommand(int cmdnum) +{ + //quote from cgame code: + // get the gamestate from the client system, which will have the + // new configstring already integrated + + char *str = ccs.serverCommands[cmdnum % TEXTCMD_MASK]; + + Con_DPrintf("Dispaching %s\n", str); + Cmd_TokenizeString(str, false, false); + + if (!strcmp(Cmd_Argv(0), "cs")) + CG_InsertIntoGameState(atoi(Cmd_Argv(1)), Cmd_Argv(2)); + return true; +} + + + +void GLDraw_Image(float x, float y, float w, float h, float s1, float t1, float s2, float t2, qpic_t *pic); +int VM_LerpTag(void *out, model_t *model, int f1, int f2, float l2, char *tagname); + + +#define VALIDATEPOINTER(o,l) if ((int)o + l >= mask || VM_POINTER(o) < offset) Host_EndGame("Call to cgame trap %i passes invalid pointer\n", fn); //out of bounds. + +static long CG_SystemCallsEx(void *offset, unsigned int mask, int fn, const long *arg) +{ + int ret=0; + + //Remember to range check pointers. + //The QVM must not be allowed to write to anything outside it's memory. + //This includes getting the exe to copy it for it. + + //don't bother with reading, as this isn't a virus risk. + //could be a cheat risk, but hey. + + //make sure that any called functions are also range checked. + //like reading from files copies names into alternate buffers, allowing stack screwups. +//OutputDebugString(va("cl_cg: %i\n", fn)); + switch(fn) + { + case CG_PRINT: + Con_Printf("%s", VM_POINTER(arg[0])); + break; + case CG_ERROR: + Host_EndGame("%s", VM_POINTER(arg[0])); + break; + + case CG_ARGC: + VM_LONG(ret) = Cmd_Argc(); + break; + case CG_ARGV: + VALIDATEPOINTER(arg[1], arg[2]); + Q_strncpyz(VM_POINTER(arg[1]), Cmd_Argv(VM_LONG(arg[0])), VM_LONG(arg[2])); + break; + case CG_ARGS: + VALIDATEPOINTER(arg[0], arg[1]); + Q_strncpyz(VM_POINTER(arg[0]), Cmd_Args(), VM_LONG(arg[1])); + break; + case CG_CVAR_REGISTER: + if (arg[0]) + VALIDATEPOINTER(arg[0], sizeof(vmcvar_t)); + { + vmcvar_t *vmc; + cvar_t *var; + vmc = VM_POINTER(arg[0]); + var = Cvar_Get(VM_POINTER(arg[1]), VM_POINTER(arg[2]), VM_LONG(arg[3])&(CVAR_ARCHIVE|CVAR_USERINFO|CVAR_SERVERINFO), "UI cvar"); + if (!vmc) + break; + vmc->handle = (char *)var - (char *)offset; + + vmc->integer = var->value; + vmc->value = var->value; + vmc->modificationCount = var->modified; + Q_strncpyz(vmc->string, var->string, sizeof(vmc->string)); + } + break; + case CG_CVAR_UPDATE: + VALIDATEPOINTER(arg[0], sizeof(vmcvar_t)); + { + cvar_t *var; + vmcvar_t *vmc; + vmc = VM_POINTER(arg[0]); + var = (cvar_t *)((int)vmc->handle + (char *)offset); + + vmc->integer = var->value; + vmc->value = var->value; + vmc->modificationCount = var->modified; + Q_strncpyz(vmc->string, var->string, sizeof(vmc->string)); + } + break; + + case CG_CVAR_SET: + { + cvar_t *var; + var = Cvar_FindVar(VM_POINTER(arg[0])); + if (var) + Cvar_Set(var, VM_POINTER(arg[1])); //set it + else + Cvar_Get(VM_POINTER(arg[0]), VM_POINTER(arg[1]), 0, "Q3CG created"); //create one + } + break; + case CG_CVAR_VARIABLESTRINGBUFFER: + { + cvar_t *var; + var = Cvar_FindVar(VM_POINTER(arg[0])); + if (!VM_LONG(arg[2])) + VM_LONG(ret) = 0; + else if (!var) + { + VALIDATEPOINTER(arg[1], 1); + *(char *)VM_POINTER(arg[1]) = '\0'; + VM_LONG(ret) = -1; + } + else + { + VALIDATEPOINTER(arg[1], arg[2]); + Q_strncpyz(VM_POINTER(arg[1]), var->string, VM_LONG(arg[2])); + } + } + break; + + case CG_SENDCONSOLECOMMAND: + Con_DPrintf("CG_SENDCONSOLECOMMAND: %s", VM_POINTER(arg[0])); + Cbuf_AddText(VM_POINTER(arg[0]), RESTRICT_SERVER); + break; + case CG_ADDCOMMAND: + Cmd_AddRemCommand(VM_POINTER(arg[0]), CG_Command_f); + break; + case CG_SENDCLIENTCOMMAND: + Con_DPrintf("CG_SENDCLIENTCOMMAND: %s", VM_POINTER(arg[0])); + CL_SendClientCommand(true, "%s", VM_POINTER(arg[0])); + break; + + case CG_UPDATESCREEN: //force a buffer swap cos loading won't refresh it soon. + GL_EndRendering(); + GL_DoSwap(); + break; + + case CG_FS_FOPENFILE: //fopen + if (arg[1]) + VALIDATEPOINTER(arg[1], 4); + VM_LONG(ret) = VMUI_fopen(VM_POINTER(arg[0]), VM_POINTER(arg[1]), VM_LONG(arg[2]), 1); + break; + + case CG_FS_READ: //fread + VALIDATEPOINTER(arg[1], 4); + VMUI_FRead(VM_POINTER(arg[0]), VM_LONG(arg[1]), VM_LONG(arg[2]), 1); + break; + case CG_FS_WRITE: //fwrite + break; + case CG_FS_FCLOSEFILE: //fclose + VMUI_fclose(VM_LONG(arg[0]), 1); + break; + + case CG_CM_POINTCONTENTS: //int trap_CM_PointContents( const vec3_t p, clipHandle_t model ); + { + unsigned int pc; + model_t *mod = VM_FROMHANDLE(arg[1]); + if (!mod) + mod = cl.worldmodel; + if (mod) + pc = mod->funcs.PointContents(mod, VM_POINTER(arg[0])); + else + pc = FTECONTENTS_SOLID; + VM_LONG(ret) = Contents_To_Q3(pc); + } + break; + + case CG_CM_TRANSFORMEDPOINTCONTENTS: //int trap_CM_TransformedPointContents( const vec3_t p, clipHandle_t model, const vec3_t origin, const vec3_t angles ) { + { + unsigned int pc; + float *p = VM_POINTER(arg[0]); + model_t *mod = VM_FROMHANDLE(arg[1]); + float *origin = VM_POINTER(arg[2]); + float *angles = VM_POINTER(arg[3]); + + if (!mod) + mod = cl.worldmodel; + + { + vec3_t p_l; + vec3_t temp; + vec3_t forward, right, up; + + // subtract origin offset + VectorSubtract (p, origin, p_l); + + // rotate start and end into the models frame of reference + if (angles[0] || angles[1] || angles[2]) + { + AngleVectors (angles, forward, right, up); + + VectorCopy (p_l, temp); + p_l[0] = DotProduct (temp, forward); + p_l[1] = -DotProduct (temp, right); + p_l[2] = DotProduct (temp, up); + } + + if (mod) + pc = mod->funcs.PointContents(mod, p_l); + else + pc = FTECONTENTS_SOLID; + } + VM_LONG(ret) = Contents_To_Q3(pc); + } + break; + + case CG_CM_TRANSFORMEDBOXTRACE: +// void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, +// const vec3_t mins, const vec3_t maxs, +// clipHandle_t model, int brushmask ); + { +//FIXME: no protection of result trace. + trace_t tr; + q3trace_t *results = VM_POINTER(arg[0]); + float *start = VM_POINTER(arg[1]); + float *end = VM_POINTER(arg[2]); + float *mins = VM_POINTER(arg[3]); + float *maxs = VM_POINTER(arg[4]); + model_t *mod = VM_FROMHANDLE(arg[5]); + int brushmask = VM_LONG(arg[6]); + float *origin = VM_POINTER(arg[7]); + float *angles = VM_POINTER(arg[8]); + if (!mod) + mod = cl.worldmodel; + if (!mins) + mins = vec3_origin; + if (!maxs) + maxs = vec3_origin; + if (!origin) + origin = vec3_origin; + if (!angles) + angles = vec3_origin; + if (mod) + tr = CM_TransformedBoxTrace(mod, start, end, mins, maxs, brushmask, origin, angles); + else + { + memset(&tr, 0, sizeof(tr)); + tr.allsolid = tr.startsolid = true; + tr.contents = 1; + } + results->allsolid = tr.allsolid; + results->contents = tr.contents; + results->fraction = tr.fraction; + results->entityNum = 0; + results->startsolid = tr.startsolid; + results->surfaceFlags = tr.surface?tr.surface->flags:0; + memcpy(results->endpos, tr.endpos, sizeof(vec3_t)); + memcpy(&results->plane, &tr.plane, sizeof(cplane_t)); + } + break; + case CG_CM_BOXTRACE: +// void trap_CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end, +// const vec3_t mins, const vec3_t maxs, +// clipHandle_t model, int brushmask ); + { +//FIXME: no protection of result trace. + trace_t tr; + q3trace_t *results = VM_POINTER(arg[0]); + float *start = VM_POINTER(arg[1]); + float *end = VM_POINTER(arg[2]); + float *mins = VM_POINTER(arg[3]); + float *maxs = VM_POINTER(arg[4]); + model_t *mod = VM_FROMHANDLE(arg[5]); + int brushmask = VM_LONG(arg[6]); + if (!mod) + mod = cl.worldmodel; + if (!mins) + mins = vec3_origin; + if (!maxs) + maxs = vec3_origin; + if (mod) + tr = CM_BoxTrace(mod, start, end, mins, maxs, brushmask); + else + { + memset(&tr, 0, sizeof(tr)); + tr.allsolid = tr.startsolid = true; + tr.contents = 1; + } + results->allsolid = tr.allsolid; + results->contents = tr.contents; + results->fraction = tr.fraction; + results->entityNum = 0; + results->startsolid = tr.startsolid; + results->surfaceFlags = tr.surface?tr.surface->flags:0; + memcpy(results->endpos, tr.endpos, sizeof(vec3_t)); + memcpy(&results->plane, &tr.plane, sizeof(cplane_t)); + } + break; + + case CG_R_LOADWORLDMAP: //FTE can't distinguish. :/ + break; //So long as noone has one collision model with a different rendering one, we'll be fine + + case CG_CM_LOADMAP: + { + int i; + strcpy(cl.model_name[1], VM_POINTER(arg[0])); + cl.worldmodel = cl.model_precache[1] = Mod_ForName(VM_POINTER(arg[0]), false); + if (cl.worldmodel->needload) + Host_EndGame("Couldn't load map"); + + for (i=1 ; inumsubmodels ; i++) + { + strcpy(cl.model_name[1+i], va("*%i", i)); + cl.model_precache[i+1] = Mod_ForName (cl.model_name[i+1], false); + } + } + + break; + + case CG_CM_INLINEMODEL: + VM_LONG(ret) = VM_TOHANDLE(cl.model_precache[VM_LONG(arg[0])+1]); + break; + case CG_CM_NUMINLINEMODELS: + VM_LONG(ret) = cl.worldmodel?cl.worldmodel->numsubmodels:0; + break; + + case CG_CM_TEMPBOXMODEL: + VM_LONG(ret) = VM_TOHANDLE(CM_TempBoxModel(VM_POINTER(arg[0]), VM_POINTER(arg[1]))); + break; + + case CG_R_MODELBOUNDS: + VALIDATEPOINTER(arg[1], sizeof(vec3_t)); + VALIDATEPOINTER(arg[2], sizeof(vec3_t)); + { + model_t *mod = VM_FROMHANDLE(arg[0]); + if (mod) + { + VectorCopy(mod->mins, ((float*)VM_POINTER(arg[1]))); + VectorCopy(mod->maxs, ((float*)VM_POINTER(arg[2]))); + } + } + break; + + case CG_R_REGISTERMODEL: //precache model + { + model_t *mod; + mod = Mod_ForName(VM_POINTER(arg[0]), false); + if (mod->needload || mod->type == mod_dummy) + return 0; + VM_LONG(ret) = VM_TOHANDLE(mod); + } + break; + + case CG_R_REGISTERSKIN: + { + char *buf; + char *skinname = VM_POINTER(arg[0]); + buf = Z_TagMalloc(strlen(skinname)+1, CGTAGNUM); + strcpy(buf, skinname); + VM_LONG(ret) = VM_TOHANDLE(buf); //precache skin - engine ignores these anyway... (for now) + } + break; + + case CG_R_REGISTERSHADER: + case CG_R_REGISTERSHADERNOMIP: + VM_LONG(ret) = VM_TOHANDLE(R_RegisterShader(VM_POINTER(arg[0]))); + /* + if (!Draw_SafeCachePic) + VM_LONG(ret) = 0; + else + VM_LONG(ret) = (long)Draw_SafeCachePic(VM_POINTER(arg[0])); + */ + break; + + case CG_R_CLEARSCENE: //clear scene + cl_numvisedicts=0; + break; + case CG_R_ADDREFENTITYTOSCENE: //add ent to scene + VQ3_AddEntity(VM_POINTER(arg[0])); + break; + case CG_R_ADDADDITIVELIGHTTOSCENE: + case CG_R_ADDLIGHTTOSCENE: //add light to scene. + { + float *org = VM_POINTER(arg[0]); + CL_NewDlightRGB(-1, org[0], org[1], org[2], VM_FLOAT(arg[1]), 0, VM_FLOAT(arg[2]), VM_FLOAT(arg[3]), VM_FLOAT(arg[4])); + } + break; + case CG_R_RENDERSCENE: //render scene + VQ3_RenderView(VM_POINTER(arg[0])); + break; + + case CG_R_SETCOLOR: //setcolour float* + { + float *f = VM_POINTER(arg[0]); + if (f) + Draw_ImageColours(f[0], f[1], f[2], f[3]); + else + Draw_ImageColours(1, 1, 1, 1); + } + break; + + case CG_R_DRAWSTRETCHPIC: + switch (qrenderer) + { +#ifdef RGLQUAKE + case QR_OPENGL: + GLDraw_ShaderImage(VM_FLOAT(arg[0]), VM_FLOAT(arg[1]), VM_FLOAT(arg[2]), VM_FLOAT(arg[3]), VM_FLOAT(arg[4]), VM_FLOAT(arg[5]), VM_FLOAT(arg[6]), VM_FLOAT(arg[7]), VM_FROMHANDLE(arg[8])); + break; +#endif + default: + break; //FIXME + } + break; + + case CG_R_LERPTAG: //Lerp tag... + VALIDATEPOINTER(arg[0], sizeof(float)*12); + VM_LONG(ret) = VM_LerpTag(VM_POINTER(arg[0]), (model_t*)VM_LONG(arg[1]), VM_LONG(arg[2]), VM_LONG(arg[3]), VM_FLOAT(arg[4]), VM_POINTER(arg[5])); + break; + + case CG_S_REGISTERSOUND: + { + sfx_t *sfx; + sfx = S_PrecacheSound(va("../%s", VM_POINTER(arg[0]))); + if (sfx) + VM_LONG(ret) = VM_TOHANDLE(sfx); + else + VM_LONG(ret) = -1; + } + break; + + case CG_S_STARTLOCALSOUND: + if (VM_LONG(arg[0]) != -1 && arg[0]) + S_LocalSound(((sfx_t*)VM_FROMHANDLE(arg[0]))->name ); + break; + + case CG_S_STARTSOUND:// ( vec3_t origin, int entityNum, int entchannel, sfxHandle_t sfx ) + S_StartSound(VM_LONG(arg[1]), VM_LONG(arg[2]), (sfx_t*)VM_LONG(arg[3]), VM_POINTER(arg[0]), 1, 1); + break; + + case CG_S_ADDLOOPINGSOUND: + break; + + case CG_S_STARTBACKGROUNDTRACK: + case CG_S_CLEARLOOPINGSOUNDS: + break; + + case CG_S_UPDATEENTITYPOSITION://void trap_S_UpdateEntityPosition( int entityNum, const vec3_t origin ); + break; + case CG_S_RESPATIALIZE://void trap_S_Respatialize( int entityNum, const vec3_t origin, vec3_t axis[3], int inwater ); + break; + + case CG_S_ADDREALLOOPINGSOUND: + break; + + case CG_KEY_ISDOWN: + { + extern qboolean keydown[256]; + if (keydown[VM_LONG(arg[0])]) + VM_LONG(ret) = 1; + else + VM_LONG(ret) = 0; + } + break; + + case CG_KEY_GETCATCHER: + VM_LONG(ret) = keycatcher; + break; + case CG_KEY_SETCATCHER: + keycatcher = VM_LONG(arg[0]); + break; + + case CG_GETGLCONFIG: + VALIDATEPOINTER(arg[0], 11332); + + //do any needed work + memset(VM_POINTER(arg[0]), 0, 11304); + *(int *)VM_POINTER(arg[0]+11304) = vid.width; + *(int *)VM_POINTER(arg[0]+11308) = vid.height; + *(float *)VM_POINTER(arg[0]+11312) = (float)vid.width/vid.height; + memset(VM_POINTER(arg[0]+11316), 0, 11332-11316); + break; + + case CG_GETGAMESTATE: + VALIDATEPOINTER(arg[0], sizeof(gameState_t)); + VM_LONG(ret) = CG_GetGameState(VM_POINTER(arg[0])); + break; + + case CG_CM_MARKFRAGMENTS: + break; + + case CG_GETCURRENTSNAPSHOTNUMBER: + VALIDATEPOINTER(arg[0], sizeof(int)); + VALIDATEPOINTER(arg[1], sizeof(int)); + *(int *)VM_POINTER(arg[0]) = ccs.snap.serverMessageNum; + *(int *)VM_POINTER(arg[1]) = ccs.snap.serverTime;// + Sys_DoubleTime()*1000-ccs.snap.localTime; + break; + + case CG_GETSNAPSHOT: + VALIDATEPOINTER(arg[1], sizeof(snapshot_t)); + VM_LONG(ret) = CG_FillQ3Snapshot(VM_LONG(arg[0]), VM_POINTER(arg[1])); + break; + + case CG_GETCURRENTCMDNUMBER: + VM_LONG(ret) = ccs.currentUserCmdNumber; + break; + case CG_GETUSERCMD: + VALIDATEPOINTER(arg[1], sizeof(q3usercmd_t)); + VM_LONG(ret) = CGQ3_GetUserCmd(VM_LONG(arg[0]), VM_POINTER(arg[1])); + break; + case CG_SETUSERCMDVALUE: //weaponselect, zoomsensativity. + ccs.selected_weapon = VM_LONG(arg[0]); + in_sensitivityscale = VM_FLOAT(arg[1]); + break; + + case CG_GETSERVERCOMMAND: + VM_LONG(ret) = CG_GetServerCommand(VM_LONG(arg[0])); + break; + + case CG_MEMORY_REMAINING: + VM_LONG(ret) = Hunk_LowMemAvailable(); + break; + + case CG_MILLISECONDS: + VM_LONG(ret) = Sys_Milliseconds(); + break; + case CG_REAL_TIME: + VM_FLOAT(ret) = realtime; + break; + + case CG_SNAPVECTOR: // ( float *v ) + VALIDATEPOINTER(arg[0], sizeof(vec3_t)); + { + float *fp = (float *)VM_POINTER(arg[0]); +#define rint(x) (int)((x > 0)?(x + 0.5f):(x-0.5f)) + fp[0] = rint(fp[0]); + fp[1] = rint(fp[1]); + fp[2] = rint(fp[2]); + } + break; + + case CG_PC_ADD_GLOBAL_DEFINE: + Con_Printf("CG_PC_ADD_GLOBAL_DEFINE not supported\n"); + break; + case CG_PC_SOURCE_FILE_AND_LINE: + Script_Get_File_And_Line(arg[0], VM_POINTER(arg[1]), VM_POINTER(arg[2])); + break; + + case CG_PC_LOAD_SOURCE: + return Script_LoadFile(VM_POINTER(arg[0])); + case CG_PC_FREE_SOURCE: + Script_Free(arg[0]); + break; + case CG_PC_READ_TOKEN: + //fixme: memory protect. + return Script_Read(arg[0], VM_POINTER(arg[1])); + +// standard Q3 + case CG_MEMSET: + VALIDATEPOINTER(arg[0], arg[2]); + memset(VM_POINTER(arg[0]), arg[1], arg[2]); + break; + case CG_MEMCPY: + VALIDATEPOINTER(arg[0], arg[2]); + memcpy(VM_POINTER(arg[0]), VM_POINTER(arg[1]), arg[2]); + break; + case CG_STRNCPY: + VALIDATEPOINTER(arg[0], arg[2]); + strncpy(VM_POINTER(arg[0]), VM_POINTER(arg[1]), arg[2]); + break; + case CG_SIN: + VM_FLOAT(ret)=(float)sin(VM_FLOAT(arg[0])); + break; + case CG_COS: + VM_FLOAT(ret)=(float)cos(VM_FLOAT(arg[0])); + break; + case CG_ACOS: + VM_FLOAT(ret)=(float)acos(VM_FLOAT(arg[0])); + break; + case CG_ATAN2: + VM_FLOAT(ret)=(float)atan2(VM_FLOAT(arg[0]), VM_FLOAT(arg[1])); + break; + case CG_SQRT: + VM_FLOAT(ret)=(float)sqrt(VM_FLOAT(arg[0])); + break; + case CG_FLOOR: + VM_FLOAT(ret)=(float)floor(VM_FLOAT(arg[0])); + break; + case CG_CEIL: + VM_FLOAT(ret)=(float)ceil(VM_FLOAT(arg[0])); + break; + + case CG_R_REGISTERFONT: + VALIDATEPOINTER(arg[2], sizeof(fontInfo_t)); + UI_RegisterFont(VM_POINTER(arg[0]), VM_LONG(arg[1]), VM_POINTER(arg[2])); + break; + default: + Con_Printf("Q3CG: Bad system trap: %d\n", fn); + } + + return ret; +} +#ifdef _DEBUG +static long CG_SystemCallsExWrapper(void *offset, unsigned int mask, int fn, const long *arg) +{ //this is so we can use edit and continue properly (vc doesn't like function pointers for edit+continue) + fn*=1; + return CG_SystemCallsEx(offset, mask, fn, arg); +} +#define CG_SystemCallsEx CG_SystemCallsExWrapper +#endif + +//I'm not keen on this. +//but dlls call it without saying what sort of vm it comes from, so I've got to have them as specifics +static int EXPORT_FN CG_SystemCalls(int arg, ...) +{ + long args[10]; + va_list argptr; + + va_start(argptr, arg); + args[0]=va_arg(argptr, int); + args[1]=va_arg(argptr, int); + args[2]=va_arg(argptr, int); + args[3]=va_arg(argptr, int); + args[4]=va_arg(argptr, int); + args[5]=va_arg(argptr, int); + args[6]=va_arg(argptr, int); + args[7]=va_arg(argptr, int); + args[8]=va_arg(argptr, int); + args[9]=va_arg(argptr, int); + va_end(argptr); + + return CG_SystemCallsEx(NULL, (unsigned)~0, arg, args); +} + +#endif + +int CG_Refresh(void) +{ +#ifdef RGLQUAKE + int time; + if (!cgvm) + return false; + + time = ccs.serverTime; + VM_Call(cgvm, CG_DRAW_ACTIVE_FRAME, time, 0, false); + + Draw_ImageColours(1, 1, 1, 1); + + return true; +#else + return false; +#endif +} + + + +void CG_Stop (void) +{ +#ifdef RGLQUAKE + keycatcher &= ~2; + if (cgvm) + { + VM_Call(cgvm, CG_SHUTDOWN); + VM_Destroy(cgvm); + VMUI_fcloseall(1); + cgvm = NULL; + } +#endif +} + +void CG_Start (void) +{ +#ifdef RGLQUAKE + if (!Draw_SafeCachePic) //no renderer loaded + { + CG_Stop(); + return; + } + + if (qrenderer != QR_OPENGL) + { //sorry. + CG_Stop(); + return; + } + + if (cls.protocol != CP_QUAKE3) + { //q3 clients only. + CG_Stop(); + return; + } + + Z_FreeTags(CGTAGNUM); + + cgvm = VM_Create(NULL, "vm/cgame", CG_SystemCalls, CG_SystemCallsEx); + if (cgvm) + { //hu... cgame doesn't appear to have a query version call! + VM_Call(cgvm, CG_INIT, ccs.serverMessageNum, ccs.lastServerCommandNum, cl.playernum[0]); + } + else + { + Host_EndGame("Failed to initialise cgame module\n"); + } +#endif +} + +qboolean CG_Command(void) +{ + Con_DPrintf("CG_Command: %s %s\n", Cmd_Argv(0), Cmd_Args()); +#ifdef RGLQUAKE + if (!cgvm) + return false; + return VM_Call(cgvm, CG_CONSOLE_COMMAND); +#else + return false; +#endif +} + +void CG_Command_f(void) +{ + Con_DPrintf("CG_Command_f: %s %s\n", Cmd_Argv(0), Cmd_Args()); + if (cgvm) + if (!VM_Call(cgvm, CG_CONSOLE_COMMAND)) + { + Cmd_ForwardToServer(); + } +} + +qboolean CG_KeyPress(int key, int down) +{ + if (!cgvm) + return false; + return VM_Call(cgvm, CG_KEY_EVENT, key, down); +} + +void CG_Restart_f(void) +{ + CG_Stop(); + CG_Start(); +} + +void CG_Init(void) +{ + Cmd_AddCommand("cg_restart", CG_Restart_f); +} + +#endif diff --git a/engine/client/clq3_parse.c b/engine/client/clq3_parse.c new file mode 100644 index 000000000..d0ef676e7 --- /dev/null +++ b/engine/client/clq3_parse.c @@ -0,0 +1,1013 @@ +#include "quakedef.h" + +//urm, yeah, this is more than just parse. + +#ifdef Q3CLIENT + +#include "clq3defs.h" + +#define CMD_MASK Q3UPDATE_MASK + +#define SHOWSTRING(s) if(cl_shownet.value==2)Con_Printf ("%s\n", s); +#define SHOWNET(x) if(cl_shownet.value==2)Con_Printf ("%3i:%s\n", msg_readcount-1, x); +#define SHOWNET2(x, y) if(cl_shownet.value==2)Con_Printf ("%3i:%3i:%s\n", msg_readcount-1, y, x); + +void MSG_WriteBits(sizebuf_t *msg, int value, int bits); + + +ClientConnectionState_t ccs; + + + +qboolean CG_FillQ3Snapshot(int snapnum, snapshot_t *snapshot) +{ + int i; + clientSnap_t *snap; + + if (snapnum > ccs.serverMessageNum) + { + Host_EndGame("CG_FillQ3Snapshot: snapshotNumber > cl.snap.serverMessageNum"); + } + + if (ccs.serverMessageNum - snapnum >= Q3UPDATE_BACKUP) + { + return false; // too old + } + + snap = &ccs.snapshots[snapnum & Q3UPDATE_MASK]; + if(!snap->valid || snap->serverMessageNum != snapnum) + { + return false; // invalid + } + + memcpy(&snapshot->ps, &snap->playerstate, sizeof(snapshot->ps)); + snapshot->numEntities = snap->numEntities; + for (i=0; inumEntities; i++) + { + memcpy(&snapshot->entities[i], &ccs.parseEntities[(snap->firstEntity+i) & PARSE_ENTITIES_MASK], sizeof(snapshot->entities[0])); + } + + memcpy( &snapshot->areamask, snap->areabits, sizeof( snapshot->areamask ) ); + + snapshot->snapFlags = snap->snapFlags; + snapshot->ping = snap->ping; + + snapshot->serverTime = snap->serverTime; + + snapshot->numServerCommands = snap->serverCommandNum; + snapshot->serverCommandSequence = ccs.lastServerCommandNum; + + return true; +} + +/* +===================== +CLQ3_ParseServerCommand +===================== +*/ +void CLQ3_ParseServerCommand(void) + { + int number; + char *string; + + number = MSG_ReadLong(); + SHOWNET(va("%i", number)); + + string = MSG_ReadString(); + SHOWSTRING(string); + + if( number <= ccs.lastServerCommandNum ) + { + return; // we have already received this command + } + + ccs.lastServerCommandNum++; + + if( number > ccs.lastServerCommandNum ) + { + Host_EndGame("Lost %i reliable serverCommands\n", + number - ccs.lastServerCommandNum ); + } + + // archive the command to be processed by cgame later + Q_strncpyz( ccs.serverCommands[number & TEXTCMD_MASK], string, sizeof( ccs.serverCommands[0] ) ); +} + +/* +================== +CL_DeltaEntity + +Parses deltas from the given base and adds the resulting entity +to the current frame +================== +*/ +static void CLQ3_DeltaEntity( clientSnap_t *frame, int newnum, q3entityState_t *old, qboolean unchanged ) +{ + q3entityState_t *state; + + state = &ccs.parseEntities[ccs.firstParseEntity & PARSE_ENTITIES_MASK]; + + if( unchanged ) + { + memcpy( state, old, sizeof(*state) ); // don't read any bits + } + else + { + if (!MSG_Q3_ReadDeltaEntity(old, state, newnum)) // the entity present in oldframe is not in the current frame + return; + } + + ccs.firstParseEntity++; + frame->numEntities++; +} + +/* +================== +CL_ParsePacketEntities + +An svc_packetentities has just been parsed, deal with the +rest of the data stream. +================== +*/ +static void CLQ3_ParsePacketEntities( clientSnap_t *oldframe, clientSnap_t *newframe ) +{ + int numentities; + int oldnum; + int newnum; + q3entityState_t *oldstate; + + oldstate = NULL; + newframe->firstEntity = ccs.firstParseEntity; + newframe->numEntities = 0; + + // delta from the entities present in oldframe + numentities = 0; + if( !oldframe ) + { + oldnum = 99999; + } + else if( oldframe->numEntities <= 0 ) + { + oldnum = 99999; + } + else + { + oldstate = &ccs.parseEntities[oldframe->firstEntity & PARSE_ENTITIES_MASK]; + oldnum = oldstate->number; + } + + while( 1 ) + { + newnum = MSG_ReadBits( GENTITYNUM_BITS ); + if( newnum < 0 || newnum >= MAX_GENTITIES ) + { + Host_EndGame("CLQ3_ParsePacketEntities: bad number %i", newnum); + } + + if( msg_readcount > net_message.cursize ) + { + Host_EndGame("CLQ3_ParsePacketEntities: end of message"); + } + + // end of packetentities + if( newnum == ENTITYNUM_NONE ) + { + break; + } + + while( oldnum < newnum ) + { + // one or more entities from the old packet are unchanged + SHOWSTRING( va( "unchanged: %i", oldnum ) ); + + CLQ3_DeltaEntity( newframe, oldnum, oldstate, true ); + + numentities++; + + if( numentities >= oldframe->numEntities ) + { + oldnum = 99999; + } + else + { + oldstate = &ccs.parseEntities[(oldframe->firstEntity + numentities) & PARSE_ENTITIES_MASK]; + oldnum = oldstate->number; + } + } + + if( oldnum == newnum ) + { + // delta from previous state + SHOWSTRING( va( "delta: %i", newnum ) ); + + CLQ3_DeltaEntity( newframe, newnum, oldstate, false ); + + numentities++; + + if( numentities >= oldframe->numEntities ) + { + oldnum = 99999; + } + else + { + oldstate = &ccs.parseEntities[(oldframe->firstEntity + numentities) & PARSE_ENTITIES_MASK]; + oldnum = oldstate->number; + } + continue; + } + + if( oldnum > newnum ) + { + // delta from baseline + SHOWSTRING( va( "baseline: %i", newnum ) ); + + CLQ3_DeltaEntity( newframe, newnum, &ccs.baselines[newnum], false ); + } + } + + // any remaining entities in the old frame are copied over + while( oldnum != 99999 ) + { + // one or more entities from the old packet are unchanged + SHOWSTRING( va( "unchanged: %i", oldnum ) ); + + CLQ3_DeltaEntity( newframe, oldnum, oldstate, true ); + + numentities++; + + if( numentities >= oldframe->numEntities ) + { + oldnum = 99999; + } + else + { + oldstate = &ccs.parseEntities[(oldframe->firstEntity + numentities) & PARSE_ENTITIES_MASK]; + oldnum = oldstate->number; + } + } +} + +void CLQ3_ParseSnapshot(void) +{ + clientSnap_t snap, *oldsnap; + int delta; + int len; + int i; + frame_t *frame; +// usercmd_t *ucmd; +// int commandTime; + + memset(&snap, 0, sizeof(snap)); + snap.serverMessageNum = ccs.serverMessageNum; + snap.serverCommandNum = ccs.lastServerCommandNum; + snap.serverTime = MSG_ReadLong(); + snap.localTime = Sys_DoubleTime()*1000; + + // If the frame is delta compressed from data that we + // no longer have available, we must suck up the rest of + // the frame, but not use it, then ask for a non-compressed message + delta = MSG_ReadByte(); + if(delta) + { + snap.deltaFrame = ccs.serverMessageNum - delta; + oldsnap = &ccs.snapshots[snap.deltaFrame & Q3UPDATE_MASK]; + + if(!oldsnap->valid) + { + // should never happen + Con_Printf( "Delta from invalid frame (not supposed to happen!).\n"); + } + else if( oldsnap->serverMessageNum != snap.deltaFrame ) + { + // The frame that the server did the delta from + // is too old, so we can't reconstruct it properly. + Con_Printf( "Delta frame too old.\n" ); + } + else if(ccs.firstParseEntity - oldsnap->firstEntity > + MAX_PARSE_ENTITIES - MAX_ENTITIES_IN_SNAPSHOT) + { + Con_Printf( "Delta parse_entities too old.\n" ); + } + else + { + snap.valid = true; // valid delta parse + } + } + else + { + oldsnap = NULL; + snap.deltaFrame = -1; + snap.valid = true; // uncompressed frame + } + + // read snapFlags + snap.snapFlags = MSG_ReadByte(); + + // read areabits + len = MSG_ReadByte(); + MSG_ReadData(snap.areabits, len ); + + // read playerinfo + SHOWSTRING("playerstate"); + MSG_Q3_ReadDeltaPlayerstate(oldsnap ? &oldsnap->playerstate : NULL, &snap.playerstate); + + // read packet entities + SHOWSTRING("packet entities"); + CLQ3_ParsePacketEntities(oldsnap, &snap); + + if (!snap.valid) + { + return; + } + +// cl.adjustTimeDelta = true; + + // Find last usercmd server has processed and calculate snap.ping + + snap.ping = 3; + for (i=cls.netchan.outgoing_sequence-1 ; i>cls.netchan.outgoing_sequence-Q3UPDATE_BACKUP ; i--) + { + frame = &cl.frames[i & Q3UPDATE_MASK]; + if (frame->server_message_num == snap.deltaFrame) + { + snap.ping = Sys_Milliseconds() - frame->client_time; + break; + } + } + + memcpy(&ccs.snap, &snap, sizeof(snap)); + memcpy(&ccs.snapshots[ccs.serverMessageNum & Q3UPDATE_MASK], &snap, sizeof(snap)); + + SHOWSTRING(va("snapshot:%i delta:%i ping:%i", snap.serverMessageNum, snap.deltaFrame, snap.ping)); +} + +#define MAXCHUNKSIZE 2048 +void CLQ3_ParseDownload(void) +{ + unsigned int chunknum; + static unsigned int downloadsize; + unsigned int chunksize; + unsigned char chunkdata[MAXCHUNKSIZE]; + int i; + char *s; + + chunknum = (unsigned short) MSG_ReadShort(); + + if (downloadsize >= MAXCHUNKSIZE*0xffff) + { + chunknum |= ccs.downloadchunknum&0x10000; //add the chunk number, truncated by the network protocol. + } + + if (!chunknum) + { + downloadsize = MSG_ReadLong(); + Cvar_SetValue( Cvar_Get("cl_downloadSize", "0", 0, "Download stuff"), downloadsize ); + } + + if (downloadsize == (unsigned int)-1) + { + s = MSG_ReadString(); + Con_Printf("\nDownload refused:\n %s\n", s); + return; + } + + chunksize = MSG_ReadShort(); + if (chunksize > MAXCHUNKSIZE) + Host_EndGame("Server sent a download chunk of size %i (it's too damn big!)\n", chunksize); + + for (i = 0; i < chunksize; i++) + chunkdata[i] = MSG_ReadByte(); + + if (ccs.downloadchunknum != chunknum) //the q3 client is rather lame. + { //ccs.downloadchunknum holds the chunk number. + Con_Printf("PACKETLOSS WHEN DOWNLOADING!!!!\n"); + return; //let the server try again some time + } + ccs.downloadchunknum++; + + if (!cls.downloadqw) + { + if (!*cls.downloadtempname) + { + Con_Printf("Server sending download, but no download was requested\n"); + CLQ3_SendClientCommand("stopdl"); + cls.downloadmethod = DL_NONE; + return; + } + + COM_CreatePath(cls.downloadtempname); + cls.downloadqw = fopen(cls.downloadtempname, "wb"); + if (!cls.downloadqw) + { + Con_Printf("Couldn't write to temporary file %s - stopping download\n", cls.downloadtempname); + CLQ3_SendClientCommand("stopdl"); + cls.downloadmethod = DL_NONE; + return; + } + } + + Con_Printf("dl: chnk %i, size %i, csize %i\n", chunknum, downloadsize, chunksize); + + if (!chunksize) + { + fclose(cls.downloadqw); + cls.downloadqw = NULL; + rename(cls.downloadtempname, cls.downloadname); // -> + *cls.downloadtempname = *cls.downloadname = 0; + cls.downloadmethod = DL_NONE; + + cl.servercount = -1; //make sure the server resends us that vital gamestate. + ccs.downloadchunknum = -1; + } + else + { + fwrite(chunkdata, chunksize, 1, cls.downloadqw); + chunksize=ftell(cls.downloadqw); + Con_Printf("Recieved %i\n", chunksize); + + cls.downloadpercent = (100.0 * chunksize) / downloadsize; + } + + + CLQ3_SendClientCommand("nextdl %i", chunknum); +} + +qboolean CLQ3_SystemInfoChanged(char *str) +{ + qboolean usingpure, usingcheats; + char *value; + char *pc, *pn; + char *rc, *rn; + + usingpure = atoi(Info_ValueForKey(str, "sv_pure")); + usingcheats = atoi(Info_ValueForKey(str, "sv_cheats")); + Cvar_ForceCheatVars(usingpure||usingcheats, usingcheats); + +// if (atoi(value)) +// Host_EndGame("Unable to connect to Q3 Pure Servers\n"); + value = Info_ValueForKey(str, "fs_game"); + +#ifndef CLIENTONLY + if (!sv.state) +#endif + { + COM_FlushTempoaryPacks(); + COM_Gamedir(value); +#ifndef CLIENTONLY + Info_SetValueForStarKey (svs.info, "*gamedir", value, MAX_SERVERINFO_STRING); +#endif + COM_FlushFSCache(); + + Shader_Init(); + } + + if (usingpure) + { + rc = Info_ValueForKey(str, "sv_referencedPaks"); //the ones that we should download. + rn = Info_ValueForKey(str, "sv_referencedPakNames"); + + + + while(rn) + { + FILE *f; + rn = COM_Parse(rn); + if (!*com_token) + break; + + f = fopen(va("%s.pk3", com_token), "rb"); + if (f) + fclose(f); + else + { + //fixme: request to download it + Con_Printf("Sending request to download %s\n", com_token); + CLQ3_SendClientCommand("download %s.pk3", com_token); + ccs.downloadchunknum = 0; + _snprintf(cls.downloadname, sizeof(cls.downloadname), "%s.pk3", com_token); + _snprintf(cls.downloadtempname, sizeof(cls.downloadtempname), "%s.tmp", com_token); + cls.downloadmethod = DL_Q3; + cls.downloadpercent = 0; + return false; + } + } + + pc = Info_ValueForKey(str, "sv_paks"); //the ones that we are allowed to use (in order!) + pn = Info_ValueForKey(str, "sv_pakNames"); + FS_ForceToPure(pn, pc, ccs.fs_key); + } + else + { + FS_ForceToPure(NULL, NULL, ccs.fs_key); + } + + return true; //yay, we're in +} + +void CLQ3_ParseGameState(void) +{ + int c; + int index; + char *configString; + +// +// wipe the client_state_t struct +// + memset(&cl, 0, sizeof(cl)); + + cl.minpitch = -90; + cl.maxpitch = 90; + + ccs.lastServerCommandNum = MSG_ReadLong(); + ccs.currentServerCommandNum = ccs.lastServerCommandNum; + + for(;;) + { + c = MSG_ReadByte(); + + if(msg_badread) + { + Host_EndGame("CL_ParseGameState: read past end of server message"); + } + + if(c == svcq3_eom) + { + break; + } + + SHOWNET(va("%i", c)); + + switch(c) + { + default: + Host_EndGame("CL_ParseGameState: bad command byte"); + break; + + case svcq3_configstring: + index = MSG_ReadShort(); + if (index < 0 || index >= MAX_Q3_CONFIGSTRINGS) + { + Host_EndGame("CL_ParseGameState: configString index %i out of range", index); + } + configString = MSG_ReadString(); + if (index == 1) + { + //check some things. + cl.servercount = atoi(Info_ValueForKey(configString, "sv_serverid")); + } + + CG_InsertIntoGameState(index, configString); + break; + + case svcq3_baseline: + index = MSG_ReadBits(GENTITYNUM_BITS); + if (index < 0 || index >= MAX_GENTITIES) + { + Host_EndGame("CL_ParseGameState: baseline index %i out of range", index); + } + MSG_Q3_ReadDeltaEntity(NULL, &ccs.baselines[index], index); + break; + } + } + + cl.playernum[0] = MSG_ReadLong(); + ccs.fs_key = MSG_ReadLong(); + + if (!CLQ3_SystemInfoChanged(CG_GetConfigString(1))) + return; + + CG_Restart_f(); + UI_Restart_f(); + + if (!cl.worldmodel) + Host_EndGame("CGame didn't set a map.\n"); + R_NewMap (); + + SCR_EndLoadingPlaque(); + + Hunk_Check (); // make sure nothing is hurt + + CL_MakeActive("Quake3Arena"); + + cl.splitclients = 1; + CL_RegisterSplitCommands(); + + { + char buffer[2048]; + strcpy(buffer, va("cp %i ", cl.servercount)); + FS_GenerateClientPacksList(buffer, sizeof(buffer), ccs.fs_key); + CLQ3_SendClientCommand(buffer); + } + + // load cgame, etc +// CL_ChangeLevel(); + +} + +#define TEXTCMD_BACKUP 64 +void CLQ3_ParseServerMessage (void) +{ + int cmd; + if (!CLQ3_Netchan_Process()) + return; //was a fragment. + + if (cl_shownet.value == 1) + Con_TPrintf (TL_INT_SPACE,net_message.cursize); + else if (cl_shownet.value == 2) + Con_TPrintf (TLC_LINEBREAK_MINUS); + + net_message.packing = SZ_RAWBYTES; + MSG_BeginReading(); + ccs.serverMessageNum = MSG_ReadLong(); + net_message.packing = SZ_HUFFMAN; //the rest is huffman compressed. + net_message.currentbit = msg_readcount*8; + + // read last client command number server received + ccs.lastClientCommandNum = MSG_ReadLong(); + if( ccs.lastClientCommandNum <= ccs.numClientCommands - TEXTCMD_BACKUP ) + { + ccs.lastClientCommandNum = ccs.numClientCommands - TEXTCMD_BACKUP + 1; + } + else if( ccs.lastClientCommandNum > ccs.numClientCommands ) + { + ccs.lastClientCommandNum = ccs.numClientCommands; + } + +// +// parse the message +// + for(;;) + { + cmd = MSG_ReadByte(); + + if(msg_badread) //hm, we have an eom, so only stop when the message is bad. + { + Host_EndGame("CLQ3_ParseServerMessage: read past end of server message"); + break; + } + + if(cmd == svcq3_eom) + { + SHOWNET2("END OF MESSAGE", 2); + break; + } + + SHOWNET(va("%i", cmd)); + + // other commands + switch(cmd) + { + default: + Host_EndGame("CLQ3_ParseServerMessage: Illegible server message"); + break; + case svcq3_nop: + break; + case svcq3_gamestate: + CLQ3_ParseGameState(); + break; + case svcq3_serverCommand: + CLQ3_ParseServerCommand(); + break; + case svcq3_download: + CLQ3_ParseDownload(); + break; + case svcq3_snapshot: + CLQ3_ParseSnapshot(); + break; + } + } +} + + + + +qboolean CLQ3_Netchan_Process(void) +{ + int sequence; + int lastClientCommandNum; + qbyte bitmask; + qbyte c; + int i, j; + char *string; + int bit; + int readcount; + + if(!Netchan_ProcessQ3(&cls.netchan)) + { + return false; + } + + // archive buffer state + bit = net_message.currentbit; + readcount = msg_readcount; + net_message.packing = SZ_HUFFMAN; + net_message.currentbit = 32; + + lastClientCommandNum = MSG_ReadLong(); + sequence = LittleLong(*(int *)net_message.data); + + // restore buffer state + net_message.currentbit = bit; + msg_readcount = readcount; + + // calculate bitmask + bitmask = sequence ^ cls.challenge; + string = ccs.clientCommands[lastClientCommandNum & TEXTCMD_MASK]; + + // decrypt the packet + for(i=msg_readcount+4,j=0 ; i 127 || c == '%') + { + c = '.'; + } + bitmask ^= c << (i & 1); + net_message.data[i] ^= bitmask; + } + + return true; +} + +void CL_Netchan_Transmit( int length, const qbyte *data ) +{ +#define msg net_message + int serverid; + int lastSequence; + int lastServerCommandNum; + qbyte bitmask; + qbyte c; + int i, j; + char *string; + net_message.cursize = 0; + SZ_Write(&msg, data, length); + + if(msg.overflowed) + { + Host_EndGame("Client message overflowed"); + } + + msg_readcount = 0; + msg.currentbit = 0; + msg.packing = SZ_HUFFMAN; + + serverid = MSG_ReadLong(); + lastSequence = MSG_ReadLong(); + lastServerCommandNum = MSG_ReadLong(); + + // calculate bitmask + bitmask = lastSequence ^ serverid ^ cls.challenge; + string = ccs.serverCommands[lastServerCommandNum & TEXTCMD_MASK]; + + // encrypt the packet + for( i=12,j=0 ; i 127 || c == '%' ) + { + c = '.'; + } + bitmask ^= c << (i & 1); + msg.data[i] ^= bitmask; + } + + Netchan_TransmitQ3( &cls.netchan, msg.cursize, msg.data ); +#undef msg +} + + + + +static void MSG_WriteDeltaKey( sizebuf_t *msg, int key, int from, int to, int bits ) +{ + if( from == to ) + { + MSG_WriteBits( msg, 0, 1 ); + return; // unchanged + } + + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, to ^ key, bits ); +} + +void MSG_Q3_WriteDeltaUsercmd( sizebuf_t *msg, int key, const usercmd_t *from, const usercmd_t *to ) +{ + // figure out how to pack serverTime + if( to->servertime - from->servertime < 255 ) + { + MSG_WriteBits(msg, 1, 1); + MSG_WriteBits(msg, to->servertime - from->servertime, 8); + } + else + { + MSG_WriteBits( msg, 0, 1 ); + MSG_WriteBits( msg, to->servertime, 32); + } + + if( !memcmp( (qbyte *)from + 4, (qbyte *)to + 4, sizeof( usercmd_t ) - 4 ) ) + { + MSG_WriteBits(msg, 0, 1); + return; // nothing changed + } + MSG_WriteBits(msg, 1, 1); + + key ^= to->servertime; + + MSG_WriteDeltaKey(msg, key, from->angles[0], to->angles[0], 16); + MSG_WriteDeltaKey(msg, key, from->angles[1], to->angles[1], 16); + MSG_WriteDeltaKey(msg, key, from->angles[2], to->angles[2], 16); + MSG_WriteDeltaKey(msg, key, from->forwardmove, to->forwardmove, 8); + MSG_WriteDeltaKey(msg, key, from->sidemove, to->sidemove, 8 ); + MSG_WriteDeltaKey(msg, key, from->upmove, to->upmove, 8); + MSG_WriteDeltaKey(msg, key, from->buttons, to->buttons, 16); + MSG_WriteDeltaKey(msg, key, from->weapon, to->weapon, 8); +} + + + +void VARGS CLQ3_SendClientCommand(const char *fmt, ...) +{ + va_list argptr; + char command[MAX_STRING_CHARS]; + + va_start( argptr, fmt ); + vsprintf( command, fmt, argptr ); + va_end( argptr ); + + // create new clientCommand + ccs.numClientCommands++; + + // check if server will lose some of our clientCommands + if(ccs.numClientCommands - ccs.lastClientCommandNum >= TEXTCMD_BACKUP) + Host_EndGame("Client command overflow"); + + Q_strncpyz(ccs.clientCommands[ccs.numClientCommands & TEXTCMD_MASK], command, sizeof(ccs.clientCommands[0])); + Con_DPrintf("Sending %s\n", command); +} + +void CLQ3_SendCmd(usercmd_t *cmd) +{ + char *string; + int i; + char data[MAX_OVERALLMSGLEN]; + sizebuf_t msg; + frame_t *frame, *oldframe; + int cmdcount, key; + usercmd_t *to, *from; + + if (cls.resendinfo) + { + cls.resendinfo = false; + CLQ3_SendClientCommand("userinfo \"%s\"", cls.userinfo); + } + + ccs.serverTime = ccs.snap.serverTime + (Sys_Milliseconds()-ccs.snap.localTime); + + //reuse the q1 array + cmd->servertime = ccs.serverTime; + cmd->weapon = ccs.selected_weapon; + + cmd->forwardmove *= 127/400.0f; + cmd->sidemove *= 127/400.0f; + cmd->upmove *= 127/400.0f; + + cl.frames[ccs.currentUserCmdNumber&CMD_MASK].cmd[0] = *cmd; + ccs.currentUserCmdNumber++; + + + + frame = &cl.frames[cls.netchan.outgoing_sequence & Q3UPDATE_MASK]; + frame->cmd_sequence = ccs.currentUserCmdNumber; + frame->server_message_num = ccs.serverMessageNum; + frame->server_time = cl.gametime; + frame->client_time = Sys_DoubleTime()*1000; + + + memset(&msg, 0, sizeof(msg)); + msg.maxsize = sizeof(data); + msg.data = data; + msg.packing = SZ_HUFFMAN; + + MSG_WriteBits(&msg, cl.servercount, 32); + MSG_WriteBits(&msg, ccs.serverMessageNum, 32); + MSG_WriteBits(&msg, ccs.lastServerCommandNum, 32); + + // write clientCommands not acknowledged by server yet + for (i=ccs.lastClientCommandNum+1; i<=ccs.numClientCommands; i++) + { + MSG_WriteBits(&msg, clcq3_clientCommand, 8); + MSG_WriteBits(&msg, i, 32); + string = ccs.clientCommands[i & TEXTCMD_MASK]; + while(*string) + MSG_WriteBits(&msg, *string++, 8); + MSG_WriteBits(&msg, 0, 8); + } + + i = (cls.netchan.outgoing_sequence - 1); + oldframe = &cl.frames[i & Q3UPDATE_MASK]; + cmdcount = ccs.currentUserCmdNumber - oldframe->cmd_sequence; + if (cmdcount > Q3UPDATE_MASK) + cmdcount = Q3UPDATE_MASK; + // begin a client move command, if any + if( cmdcount ) + { + if(!ccs.snap.valid || + ccs.snap.serverMessageNum != ccs.serverMessageNum) + MSG_WriteBits(&msg, clcq3_nodeltaMove, 8); // no compression + else + MSG_WriteBits(&msg, clcq3_move, 8); + + // write cmdcount + MSG_WriteBits(&msg, cmdcount, 8); + + // calculate key + string = ccs.serverCommands[ccs.lastServerCommandNum & TEXTCMD_MASK]; + key = ccs.fs_key ^ ccs.serverMessageNum ^ StringKey(string, 32); + + // send this and the previous cmds in the message, so + // if the last packet was dropped, it can be recovered + from = &nullcmd; + for (i = oldframe->cmd_sequence; i < ccs.currentUserCmdNumber; i++) + { + to = &cl.frames[i&CMD_MASK].cmd[0]; + MSG_Q3_WriteDeltaUsercmd( &msg, key, from, to ); + from = to; + } + } + + MSG_WriteBits(&msg, clcq3_eom, 8); + + CL_Netchan_Transmit( msg.cursize, msg.data ); + while(cls.netchan.reliable_length) + Netchan_TransmitNextFragment(&cls.netchan); +} + +void CLQ3_SendAuthPacket(netadr_t gameserver) +{ + char data[2048]; + sizebuf_t msg; + +//send the auth packet +//this should be the right code, but it doesn't work. + if (gameserver.type == NA_IP) + { + char keydata[64]; + char *key = Cvar_Get("cl_cdkey", "", 0, "Quake3 auth")->string; + netadr_t authaddr; +#define AUTHORIZE_SERVER_NAME "authorize.quake3arena.com:27952" + if (*key) + { + Con_Printf("Resolving %s\n", AUTHORIZE_SERVER_NAME); + if (NET_StringToAdr(AUTHORIZE_SERVER_NAME, &authaddr)) + { + msg.data = data; + msg.cursize = 0; + msg.overflowed = msg.allowoverflow = 0; + msg.maxsize = sizeof(data); + MSG_WriteLong(&msg, -1); + MSG_WriteString(&msg, "getKeyAuthorize 0 "); + msg.cursize--; + while(*key) + { + if ((*key >= 'a' && *key <= 'z') || (*key >= 'A' && *key <= 'Z') || (*key >= '0' && *key <= '9')) + MSG_WriteByte(&msg, *key); + key++; + } + MSG_WriteByte(&msg, 0); + + NET_SendPacket (NS_CLIENT, msg.cursize, msg.data, authaddr); + } + else + Con_Printf(" failed\n"); + } + } +} + +void CLQ3_SendConnectPacket(netadr_t to) +{ + char data[2048]; + sizebuf_t msg; + + memset(&ccs, 0, sizeof(ccs)); + + cl.splitclients = 1; + CL_RegisterSplitCommands(); + msg.data = data; + msg.cursize = 0; + msg.overflowed = msg.allowoverflow = 0; + msg.maxsize = sizeof(data); + MSG_WriteLong(&msg, -1); + MSG_WriteString(&msg, va("connect \"\\challenge\\%i\\qport\\%i\\protocol\\%i\\ip\\%s%s\"", cls.challenge, cls.qport, PROTOCOL_VERSION_Q3, NET_AdrToString (net_local_cl_ipadr), cls.userinfo)); + Huff_EncryptPacket(&msg, 12); + Huff_PreferedCompressionCRC(); + NET_SendPacket (NS_CLIENT, msg.cursize, msg.data, to); +} +#endif \ No newline at end of file diff --git a/engine/client/clq3defs.h b/engine/client/clq3defs.h new file mode 100644 index 000000000..21c266dfd --- /dev/null +++ b/engine/client/clq3defs.h @@ -0,0 +1,312 @@ +#ifndef _Q3DEFS_H_ +#define _Q3DEFS_H_ +#define PROTOCOL_VERSION_Q3 68 + +typedef struct { + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + cplane_t plane; // surface normal at impact, transformed to world space + int surfaceFlags; // surface hit + int contents; // contents on other side of surface hit + int entityNum; // entity the contacted surface is a part of +} q3trace_t; + + +#define MAX_Q3_STATS 16 +#define MAX_Q3_PERSISTANT 16 +#define MAX_Q3_POWERUPS 16 +#define MAX_Q3_WEAPONS 16 + +#define MAX_PS_EVENTS 2 +typedef struct q3playerState_s { + int commandTime; // cmd->serverTime of last executed command + int pm_type; + int bobCycle; // for view bobbing and footstep generation + int pm_flags; // ducked, jump_held, etc + int pm_time; + + vec3_t origin; + vec3_t velocity; + int weaponTime; + int gravity; + int speed; + int delta_angles[3]; // add to command angles to get view direction + // changed by spawns, rotating objects, and teleporters + + int groundEntityNum;// ENTITYNUM_NONE = in air + + int legsTimer; // don't change low priority animations until this runs out + int legsAnim; // mask off ANIM_TOGGLEBIT + + int torsoTimer; // don't change low priority animations until this runs out + int torsoAnim; // mask off ANIM_TOGGLEBIT + + int movementDir; // a number 0 to 7 that represents the reletive angle + // of movement to the view angle (axial and diagonals) + // when at rest, the value will remain unchanged + // used to twist the legs during strafing + + vec3_t grapplePoint; // location of grapple to pull towards if PMF_GRAPPLE_PULL + + int eFlags; // copied to entityState_t->eFlags + + int eventSequence; // pmove generated events + int events[MAX_PS_EVENTS]; + int eventParms[MAX_PS_EVENTS]; + + int externalEvent; // events set on player from another source + int externalEventParm; + int externalEventTime; + + int clientNum; // ranges from 0 to MAX_CLIENTS-1 + int weapon; // copied to entityState_t->weapon + int weaponstate; + + vec3_t viewangles; // for fixed views + int viewheight; + + // damage feedback + int damageEvent; // when it changes, latch the other parms + int damageYaw; + int damagePitch; + int damageCount; + + int stats[MAX_Q3_STATS]; + int persistant[MAX_Q3_PERSISTANT]; // stats that aren't cleared on death + int powerups[MAX_Q3_POWERUPS]; // level.time that the powerup runs out + int ammo[MAX_Q3_WEAPONS]; + + int generic1; + int loopSound; + int jumppad_ent; // jumppad entity hit this frame + + // not communicated over the net at all + int ping; // server to game info for scoreboard + int pmove_framecount; // FIXME: don't transmit over the network + int jumppad_frame; + int entityEventSequence; +} q3playerState_t; + + + +typedef enum { + TR_STATIONARY, + TR_INTERPOLATE, // non-parametric, but interpolate between snapshots + TR_LINEAR, + TR_LINEAR_STOP, + TR_SINE, // value = base + sin( time / duration ) * delta + TR_GRAVITY +} trType_t; +typedef struct { + trType_t trType; + int trTime; + int trDuration; // if non 0, trTime + trDuration = stop time + vec3_t trBase; + vec3_t trDelta; // velocity, etc +} trajectory_t; +typedef struct q3entityState_s { + int number; // entity index + int eType; // entityType_t + int eFlags; + + trajectory_t pos; // for calculating position + trajectory_t apos; // for calculating angles + + int time; + int time2; + + vec3_t origin; + vec3_t origin2; + + vec3_t angles; + vec3_t angles2; + + int otherEntityNum; // shotgun sources, etc + int otherEntityNum2; + + int groundEntityNum; // -1 = in air + + int constantLight; // r + (g<<8) + (b<<16) + (intensity<<24) + int loopSound; // constantly loop this sound + + int modelindex; + int modelindex2; + int clientNum; // 0 to (MAX_CLIENTS - 1), for players and corpses + int frame; + + int solid; // for client side prediction, trap_linkentity sets this properly + + int event; // impulse events -- muzzle flashes, footsteps, etc + int eventParm; + + // for players + int powerups; // bit flags + int weapon; // determines weapon and flash model, etc + int legsAnim; // mask off ANIM_TOGGLEBIT + int torsoAnim; // mask off ANIM_TOGGLEBIT + + int generic1; +} q3entityState_t; + +#define MAX_MAP_AREA_BYTES 32 + +#define MAX_ENTITIES_IN_SNAPSHOT 256 +typedef struct snapshot_s { + int snapFlags; // SNAPFLAG_RATE_DELAYED, etc + int ping; + + int serverTime; // server time the message is valid for (in msec) + + qbyte areamask[MAX_MAP_AREA_BYTES]; // portalarea visibility bits + + q3playerState_t ps; // complete information about the current player at this time + + int numEntities; // all of the entities that need to be presented + q3entityState_t entities[MAX_ENTITIES_IN_SNAPSHOT]; // at the time of this snapshot + + int numServerCommands; // text based server commands to execute when this + int serverCommandSequence; // snapshot becomes current +} snapshot_t; +#define SNAPFLAG_NOT_ACTIVE 2 + + + +// +// clientSnap_t is will be converted to snapshot_t for internal cgame use +// +typedef struct clientSnap_s { + qboolean valid; // cleared if delta parsing was invalid + int snapFlags; + int serverMessageNum; + int serverCommandNum; + int serverTime; // server time the message is valid for (in msec) + int localTime; + int deltaFrame; + qbyte areabits[MAX_MAP_AREA_BYTES]; // portalarea visibility bits + q3playerState_t playerstate; + int numEntities; + int firstEntity; // non-masked index into cl.parseEntities[] array + int ping; +} clientSnap_t; + +// for ping calculation, rate estimation, usercmd delta'ing +typedef struct frame_s { + int userCmdNumber; + int serverMessageNum; + int clientTime; + int serverTime; +} q3frame_t; + +#define MAX_PARSE_ENTITIES 1024 +#define PARSE_ENTITIES_MASK (MAX_PARSE_ENTITIES-1) + +#define MAX_STRING_CHARS 1024 +#define TEXTCMD_BACKUP 64 // size of reliable text commands buffer, must be power of two +#define TEXTCMD_MASK (TEXTCMD_BACKUP-1) + +#define MAX_Q3_CONFIGSTRINGS 1024 + +#define GENTITYNUM_BITS 10 +#define MAX_GENTITIES (1<