diff --git a/Quake/cl_main.c b/Quake/cl_main.c index c9c1c823..e51fa89a 100644 --- a/Quake/cl_main.c +++ b/Quake/cl_main.c @@ -100,6 +100,8 @@ void CL_ClearState (void) CL_ClearTrailStates(); + PR_ClearProgs(&cl.qcvm); + // wipe the entire cl structure memset (&cl, 0, sizeof(cl)); @@ -116,6 +118,9 @@ void CL_ClearState (void) cl.entities = (entity_t *) Hunk_AllocName (cl.max_edicts*sizeof(entity_t), "cl_entities"); //johnfitz + //Spike -- this stuff needs to get reset to defaults. + cl.csqc_sensitivity = 1; + cl.viewent.netstate = nullentitystate; #ifdef PSET_SCRIPT PScript_Shutdown(); diff --git a/Quake/cl_parse.c b/Quake/cl_parse.c index 46131654..8016a0e8 100644 --- a/Quake/cl_parse.c +++ b/Quake/cl_parse.c @@ -205,24 +205,6 @@ entity_t *CL_EntityNum (int num) return &cl.entities[num]; } - - - - -static int MSG_ReadEntity(void) -{ - int e = (unsigned short)MSG_ReadShort(); - if (cl.protocol_pext2 & PEXT2_REPLACEMENTDELTAS) - { - if (e & 0x8000) - { - e = (e & 0x7fff) << 8; - e |= MSG_ReadByte(); - } - } - return e; -} - static unsigned int CLFTE_ReadDelta(unsigned int entnum, entity_state_t *news, const entity_state_t *olds, const entity_state_t *baseline) { unsigned int predbits = 0; @@ -478,7 +460,7 @@ static unsigned int CLFTE_ReadDelta(unsigned int entnum, entity_state_t *news, c } if (bits & UF_TAGINFO) { - news->tagentity = MSG_ReadEntity(); + news->tagentity = MSG_ReadEntity(cl.protocol_pext2); news->tagindex = MSG_ReadByte(); } if (bits & UF_LIGHT) @@ -751,6 +733,68 @@ static void CLFTE_ParseEntitiesUpdate(void) } } +//csqc entities protocol, payload is identical in both fte+dp. just the svcs differ. +void CLFTE_ParseCSQCEntitiesUpdate(void) +{ + /*if (cl.qcvm.extfuncs.CSQC_Ent_Update) + { + edict_t *ed; + for(;;) + { + //replacement deltas now also includes 22bit entity num indicies. + if (cls.fteprotocolextensions2 & PEXT2_REPLACEMENTDELTAS) + { + entnum = (unsigned short)MSG_ReadShort(); + removeflag = !!(entnum & 0x8000); + if (entnum & 0x4000) + entnum = (entnum & 0x3fff) | (MSG_ReadByte()<<14); + else + entnum &= ~0x8000; + } + else + { //otherwise just a 16bit value, with the high bit used as a 'remove' flag + entnum = (unsigned short)MSG_ReadShort(); + removeflag = !!(entnum & 0x8000); + entnum &= ~0x8000; + } + if ((!entnum && !removeflag) || msg_badread) + break; //end of svc + + if (removeflag) + { + ed = cl.ssqc_to_csqc[entnum]; + if (ed) + { + pr_global_struct->self = EDICT_TO_PROG(ed); + if (cl.qcvm.extfuncs.CSQC_Ent_Remove) + PR_ExecuteProgram(cl.qcvm.extfuncs.CSQC_Ent_Remove); + else + ED_Free(ed); + } + } + else + { +// if (cl.csqcdebug) +// packetsize = MSG_ReadShort(); + + ed = cl.ssqc_to_csqc[entnum]; + if (!ed) + { + cl.ssqc_to_csqc[entnum] = ed = ED_Alloc(); + ed->v.entnum = entnum; + G_FLOAT(OFS_PARM0) = true; + } + else + G_FLOAT(OFS_PARM0) = false; + pr_global_struct->self = EDICT_TO_PROG(ed); + PR_ExecuteProgram(cl.qcvm.extfuncs.CSQC_Ent_Update); + } + } + } + else*/ + Host_Error ("Received svcdp_csqcentities but unable to parse"); +} + //darkplaces protocols 5 to 7 use these #define E5_FULLUPDATE (1<<0) @@ -898,7 +942,7 @@ static void CLDP_ReadDelta(unsigned int entnum, entity_state_t *s, const entity_ s->colormap = MSG_ReadByte(); if (bits & E5_ATTACHMENT) { - s->tagentity = MSG_ReadEntity(); + s->tagentity = MSG_ReadEntity(cl.protocol_pext2); s->tagindex = MSG_ReadByte(); } if (bits & E5_LIGHT) @@ -2259,6 +2303,8 @@ void CL_ParseServerMessage (void) if (cl.protocol == PROTOCOL_VERSION_DP7) CL_EntitiesDeltaed(); + if (*cl.stuffcmdbuf && net_message.cursize < 512) + CL_ParseStuffText("\n"); //there's a few mods that forget to write \ns, that then fuck up other things too. So make sure it gets flushed to the cbuf. the cursize check is to reduce backbuffer overflows that would give a false positive. return; // end of message } @@ -2454,10 +2500,12 @@ void CL_ParseServerMessage (void) case svc_killedmonster: cl.stats[STAT_MONSTERS]++; + cl.statsf[STAT_MONSTERS] = cl.stats[STAT_MONSTERS]; break; case svc_foundsecret: cl.stats[STAT_SECRETS]++; + cl.statsf[STAT_SECRETS] = cl.stats[STAT_SECRETS]; break; case svc_updatestat: @@ -2562,28 +2610,28 @@ void CL_ParseServerMessage (void) case svcdp_effect: - case svcdp_effect2: + case svcdp_effect2: //these are kinda pointless when the particle system can do it if (cl.protocol != PROTOCOL_VERSION_DP7) Host_Error ("Received svcdp_effect[1|2] but extension not active"); CL_ParseEffect(cmd==svcdp_effect2); break; - case svcdp_csqcentities: - if (cl.protocol != PROTOCOL_VERSION_DP7) + case svcdp_csqcentities: //FTE uses DP's svc number for nq, because compat (despite fte's svc being first). same payload either way. + if (!(cl.protocol_pext2 & PEXT2_REPLACEMENTDELTAS) && cl.protocol != PROTOCOL_VERSION_DP7) Host_Error ("Received svcdp_csqcentities but extension not active"); - Host_Error ("csqc is not supported"); + CLFTE_ParseCSQCEntitiesUpdate(); break; - case svcdp_spawnbaseline2: + case svcdp_spawnbaseline2: //limited to a handful of extra properties. if (cl.protocol != PROTOCOL_VERSION_DP7) Host_Error ("Received svcdp_spawnbaseline2 but extension not active"); i = MSG_ReadShort (); CL_ParseBaseline (CL_EntityNum(i), 7); break; - case svcdp_spawnstaticsound2: + case svcdp_spawnstaticsound2: //many different ways to use 16bit sounds... no other advantage if (cl.protocol != PROTOCOL_VERSION_DP7) Host_Error ("Received svcdp_spawnstaticsound2 but extension not active"); CL_ParseStaticSound (2); break; - case svcdp_spawnstatic2: + case svcdp_spawnstatic2: //16bit model and frame. no alpha or anything fun. if (cl.protocol != PROTOCOL_VERSION_DP7) Host_Error ("Received svcdp_spawnstatic2 but extension not active"); CL_ParseStatic (7); @@ -2633,7 +2681,7 @@ void CL_ParseServerMessage (void) case svcfte_spawnbaseline2: if (!(cl.protocol_pext2 & PEXT2_REPLACEMENTDELTAS)) Host_Error ("Received svcfte_spawnbaseline2 but extension not active"); - i = MSG_ReadEntity (); + i = MSG_ReadEntity (cl.protocol_pext2); // must use CL_EntityNum() to force cl.num_entities up CL_ParseBaseline (CL_EntityNum(i), 6); break; @@ -2644,6 +2692,17 @@ void CL_ParseServerMessage (void) CLFTE_ParseEntitiesUpdate(); break; + case svcfte_cgamepacket: + if (cl.qcvm.extfuncs.CSQC_Parse_Event) + { + PR_SwitchQCVM(&cl.qcvm); + PR_ExecuteProgram(cl.qcvm.extfuncs.CSQC_Parse_Event); + PR_SwitchQCVM(NULL); + } + else + Host_Error ("CSQC_Parse_Event: Missing or incompatible CSQC\n"); + break; + //voicechat, because we can. why reduce packet sizes if you're not going to use that extra space?!? case svcfte_voicechat: if (!(cl.protocol_pext2 & PEXT2_VOICECHAT)) diff --git a/Quake/client.h b/Quake/client.h index 25c49721..4db7ad01 100644 --- a/Quake/client.h +++ b/Quake/client.h @@ -254,6 +254,11 @@ typedef struct const char *name; int index; } particle_precache[MAX_PARTICLETYPES]; + struct + { + const char *name; + int index; + } local_particle_precache[MAX_PARTICLETYPES]; #endif int ackframes[8]; //big enough to cover burst unsigned int ackframes_count; @@ -281,6 +286,10 @@ typedef struct int sound_download; char sound_name[MAX_SOUNDS][MAX_QPATH]; //spike -- end downloads + + qcvm_t qcvm; //for csqc. + qboolean csqc_cursorforced; //we want a mouse cursor. + float csqc_sensitivity; //scaler for sensitivity } client_state_t; diff --git a/Quake/cmd.c b/Quake/cmd.c index b7dc0ef7..0e5ed5b1 100644 --- a/Quake/cmd.c +++ b/Quake/cmd.c @@ -596,7 +596,7 @@ void Cmd_Init (void) Cmd_AddCommand ("apropos", Cmd_Apropos_f); Cmd_AddCommand ("find", Cmd_Apropos_f); - Cvar_RegisterVariable (&cl_nopext); + Cvar_RegisterVariable (&cl_nopext); Cvar_RegisterVariable (&cmd_warncmd); } @@ -696,7 +696,7 @@ void Cmd_AddCommand2 (const char *cmd_name, xcommand_t function, cmd_source_t sr cmd_function_t *cmd; cmd_function_t *cursor,*prev; //johnfitz -- sorted list insert - if (host_initialized) // because hunk allocation would get stomped + if (host_initialized && function) // because hunk allocation would get stomped Sys_Error ("Cmd_AddCommand after host_initialized"); // fail if the command is a variable name @@ -711,12 +711,16 @@ void Cmd_AddCommand2 (const char *cmd_name, xcommand_t function, cmd_source_t sr { if (!Q_strcmp (cmd_name, cmd->name) && cmd->srctype == srctype) { - Con_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name); + if (cmd->function != function && function) + Con_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name); return; } } - cmd = (cmd_function_t *) Hunk_Alloc (sizeof(cmd_function_t)); + if (host_initialized) + cmd = malloc(sizeof(cmd_function_t)); + else + cmd = (cmd_function_t *) Hunk_Alloc (sizeof(cmd_function_t)); cmd->name = cmd_name; cmd->function = function; cmd->srctype = srctype; @@ -816,8 +820,25 @@ qboolean Cmd_ExecuteString (const char *text, cmd_source_t src) continue; //src_command can execute anything but server commands (which it ignores, allowing for alternative behaviour) else if (src == src_server && cmd->srctype != src_server) continue; //src_server may only execute server commands (such commands must be safe to parse within the context of a network message, so no disconnect/connect/playdemo/etc) - else + else if (cmd->function) cmd->function (); + else + { + if (cl.qcvm.extfuncs.CSQC_ConsoleCommand) + { + qboolean ret; + PR_SwitchQCVM(&cl.qcvm); + G_INT(OFS_PARM0) = PR_MakeTempString(text); + PR_ExecuteProgram(cl.qcvm.extfuncs.CSQC_ConsoleCommand); + ret = G_FLOAT(OFS_RETURN); + if (!ret) + Con_Printf ("gamecode cannot \"%s\"\n", Cmd_Argv(0)); + PR_SwitchQCVM(NULL); + return ret; + } + else + Con_Printf ("gamecode not running, cannot \"%s\"\n", Cmd_Argv(0)); + } return true; } } diff --git a/Quake/common.c b/Quake/common.c index b31566a5..00244469 100644 --- a/Quake/common.c +++ b/Quake/common.c @@ -27,7 +27,11 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include #ifndef _WIN32 -#include + #include + #include + #ifndef FNM_CASEFOLD + #define FNM_CASEFOLD 0 //not available. I guess we're not on gnu/linux + #endif #endif static char *largv[MAX_NUM_ARGVS + 1]; @@ -928,6 +932,20 @@ float MSG_ReadAngle16 (unsigned int flags) } //johnfitz +int MSG_ReadEntity(unsigned int pext2) +{ + int e = (unsigned short)MSG_ReadShort(); + if (pext2 & PEXT2_REPLACEMENTDELTAS) + { + if (e & 0x8000) + { + e = (e & 0x7fff) << 8; + e |= MSG_ReadByte(); + } + } + return e; +} + //spike -- for downloads byte *MSG_ReadData (unsigned int length) { @@ -1708,6 +1726,7 @@ static int COM_FindFile (const char *filename, int *handle, FILE **file, char netpath[MAX_OSPATH]; pack_t *pak; int i, findtime; + const char *ext; if (file && handle) Sys_Error ("COM_FindFile: both handle and file set"); @@ -1809,10 +1828,14 @@ static int COM_FindFile (const char *filename, int *handle, FILE **file, } } - if (strcmp(COM_FileGetExtension(filename), "pcx") != 0 - && strcmp(COM_FileGetExtension(filename), "tga") != 0 - && strcmp(COM_FileGetExtension(filename), "lit") != 0 - && strcmp(COM_FileGetExtension(filename), "ent") != 0) + ext = COM_FileGetExtension(filename); + if (strcmp(ext, "pcx") != 0 + && strcmp(ext, "tga") != 0 + && strcmp(ext, "png") != 0 + && strcmp(ext, "jpg") != 0 + && strcmp(ext, "jpeg") != 0 + && strcmp(ext, "lit") != 0 + && strcmp(ext, "ent") != 0) Con_DPrintf ("FindFile: can't find %s\n", filename); else Con_DPrintf2("FindFile: can't find %s\n", filename); // Log pcx, tga, lit, ent misses only if (developer.value >= 2) @@ -2138,8 +2161,17 @@ static pack_t *COM_LoadPackFile (const char *packfile) return pack; } +#ifdef _WIN32 +static time_t Sys_FileTimeToTime(FILETIME ft) +{ + ULARGE_INTEGER ull; + ull.LowPart = ft.dwLowDateTime; + ull.HighPart = ft.dwHighDateTime; + return ull.QuadPart / 10000000u - 11644473600u; +} +#endif -static void COM_ListSystemFiles(void *ctx, const char *gamedir, const char *ext, qboolean (*cb)(void *ctx, const char *fname)) +void COM_ListSystemFiles(void *ctx, const char *gamedir, const char *ext, qboolean (*cb)(void *ctx, const char *fname)) { #ifdef _WIN32 WIN32_FIND_DATA fdat; @@ -2170,6 +2202,61 @@ static void COM_ListSystemFiles(void *ctx, const char *gamedir, const char *ext, #endif } +void COM_ListFiles(void *ctx, const char *gamedir, const char *pattern, qboolean (*cb)(void *ctx, const char *fname, time_t mtime, size_t fsize)) +{ + char prefixdir[MAX_OSPATH]; + char *sl; + sl = strrchr(pattern, '/'); + if (sl) + { + sl++; + if (sl-pattern >= MAX_OSPATH) + return; + memcpy(prefixdir, pattern, sl-pattern); + prefixdir[sl-pattern] = 0; + pattern = sl; + } + else + *prefixdir = 0; + +#ifdef _WIN32 + { + char filestring[MAX_OSPATH]; + WIN32_FIND_DATA fdat; + HANDLE fhnd; + q_snprintf (filestring, sizeof(filestring), "%s/%s%s", gamedir, prefixdir, pattern); + fhnd = FindFirstFile(filestring, &fdat); + if (fhnd == INVALID_HANDLE_VALUE) + return; + do + { + q_snprintf (filestring, sizeof(filestring), "%s%s", prefixdir, fdat.cFileName); + cb (ctx, filestring, Sys_FileTimeToTime(fdat.ftLastWriteTime), fdat.nFileSizeLow); + } while (FindNextFile(fhnd, &fdat)); + FindClose(fhnd); + } +#else + { + char filestring[MAX_OSPATH]; + DIR *dir_p; + struct dirent *dir_t; + + q_snprintf (filestring, sizeof(filestring), "%s/%s%s", gamedir, prefixdir, pattern); + dir_p = opendir(filestring); + if (dir_p == NULL) + return; + while ((dir_t = readdir(dir_p)) != NULL) + { + if (!fnmatch(pattern, dir_t->d_name, FNM_NOESCAPE|FNM_PATHNAME|FNM_CASEFOLD)) + { + q_snprintf (filestring, sizeof(filestring), "%s%s", prefixdir, dir_t->d_name); + cb (ctx, filestring, 0, 0); + } + } + closedir(dir_p); + } +#endif +} static qboolean COM_AddPackage(searchpath_t *basepath, const char *pakfile) { diff --git a/Quake/common.h b/Quake/common.h index b6a653bc..bbf75064 100644 --- a/Quake/common.h +++ b/Quake/common.h @@ -121,6 +121,7 @@ float MSG_ReadCoord (unsigned int flags); float MSG_ReadAngle (unsigned int flags); float MSG_ReadAngle16 (unsigned int flags); //johnfitz byte *MSG_ReadData (unsigned int length); // spike +int MSG_ReadEntity(unsigned int pext2); //spike void COM_Effectinfo_Enumerate(int (*cb)(const char *pname)); //spike -- for dp compat diff --git a/Quake/console.c b/Quake/console.c index 0f223451..e2916e14 100644 --- a/Quake/console.c +++ b/Quake/console.c @@ -117,23 +117,17 @@ void Con_ToggleConsole_f (void) history_line = edit_line; //johnfitz -- it should also return you to the bottom of the command history if (cls.state == ca_connected) - { - IN_Activate(); key_dest = key_game; - } else - { M_Menu_Main_f (); - } } else - { - IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_console; - } SCR_EndLoadingPlaque (); memset (con_times, 0, sizeof(con_times)); + + IN_UpdateGrabs(); } /* @@ -1363,8 +1357,8 @@ void Con_NotifyBox (const char *text) Con_Printf ("Press a key.\n"); Con_Printf ("%s", Con_Quakebar(40)); //johnfitz - IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_console; + IN_UpdateGrabs(); Key_BeginInputGrab (); do @@ -1380,9 +1374,9 @@ void Con_NotifyBox (const char *text) Key_EndInputGrab (); Con_Printf ("\n"); - IN_Activate(); key_dest = key_game; realtime = 0; // put the cursor back to invisible + IN_UpdateGrabs(); } diff --git a/Quake/draw.h b/Quake/draw.h index 396f57e1..7321ef1d 100644 --- a/Quake/draw.h +++ b/Quake/draw.h @@ -32,6 +32,7 @@ void Draw_Init (void); void Draw_Character (int x, int y, int num); void Draw_DebugChar (char num); void Draw_Pic (int x, int y, qpic_t *pic); +void Draw_SubPic (float x, float y, float w, float h, qpic_t *pic, float s1, float t1, float s2, float t2); void Draw_TransPicTranslate (int x, int y, qpic_t *pic, int top, int bottom); //johnfitz -- more parameters void Draw_ConsoleBackground (void); //johnfitz -- removed parameter int lines void Draw_TileClear (int x, int y, int w, int h); @@ -40,6 +41,7 @@ void Draw_FadeScreen (void); void Draw_String (int x, int y, const char *str); qpic_t *Draw_PicFromWad (const char *name); qpic_t *Draw_CachePic (const char *path); +qpic_t *Draw_TryCachePic (const char *path); void Draw_NewGame (void); void GL_SetCanvas (canvastype newcanvas); //johnfitz diff --git a/Quake/gl_draw.c b/Quake/gl_draw.c index c05d4fb2..f3662ebb 100644 --- a/Quake/gl_draw.c +++ b/Quake/gl_draw.c @@ -121,7 +121,7 @@ typedef struct cachepic_s byte padding[32]; // for appended glpic } cachepic_t; -#define MAX_CACHED_PICS 128 +#define MAX_CACHED_PICS 512 //Spike -- increased to avoid csqc issues. cachepic_t menu_cachepics[MAX_CACHED_PICS]; int menu_numcachepics; @@ -216,10 +216,21 @@ Draw_PicFromWad */ qpic_t *Draw_PicFromWad (const char *name) { + int i; + cachepic_t *pic; qpic_t *p; glpic_t gl; src_offset_t offset; //johnfitz + //Spike -- added cachepic stuff here, to avoid glitches if the function is called multiple times with the same image. + for (pic=menu_cachepics, i=0 ; iname)) + return &pic->pic; + } + if (menu_numcachepics == MAX_CACHED_PICS) + Sys_Error ("menu_numcachepics == MAX_CACHED_PICS"); + p = (qpic_t *) W_GetLumpName (name); if (!p) return pic_nul; //johnfitz @@ -260,9 +271,25 @@ qpic_t *Draw_PicFromWad (const char *name) gl.th = (float)p->height/(float)TexMgr_PadConditional(p->height); //johnfitz } - memcpy (p->data, &gl, sizeof(glpic_t)); + menu_numcachepics++; + strcpy (pic->name, name); + pic->pic = *p; + memcpy (pic->pic.data, &gl, sizeof(glpic_t)); - return p; + return &pic->pic; +} + +qpic_t *Draw_GetCachedPic (const char *path) +{ + cachepic_t *pic; + int i; + + for (pic=menu_cachepics, i=0 ; iname)) + return &pic->pic; + } + return NULL; } /* @@ -270,7 +297,7 @@ qpic_t *Draw_PicFromWad (const char *name) Draw_CachePic ================ */ -qpic_t *Draw_CachePic (const char *path) +qpic_t *Draw_TryCachePic (const char *path) { cachepic_t *pic; int i; @@ -292,7 +319,7 @@ qpic_t *Draw_CachePic (const char *path) // dat = (qpic_t *)COM_LoadTempFile (path, NULL); if (!dat) - Sys_Error ("Draw_CachePic: failed to load %s", path); + return NULL; SwapPic (dat); // HACK HACK HACK --- we need to keep the bytes for @@ -315,6 +342,14 @@ qpic_t *Draw_CachePic (const char *path) return &pic->pic; } +qpic_t *Draw_CachePic (const char *path) +{ + qpic_t *pic = Draw_TryCachePic(path); + if (!pic) + Sys_Error ("Draw_CachePic: failed to load %s", path); + return pic; +} + /* ================ Draw_MakePic -- johnfitz -- generate pics from internal data @@ -523,6 +558,29 @@ void Draw_Pic (int x, int y, qpic_t *pic) glEnd (); } +void Draw_SubPic (float x, float y, float w, float h, qpic_t *pic, float s1, float t1, float s2, float t2) +{ + glpic_t *gl; + + s2 += s1; + t2 += t1; + + if (scrap_dirty) + Scrap_Upload (); + gl = (glpic_t *)pic->data; + GL_Bind (gl->gltexture); + glBegin (GL_QUADS); + glTexCoord2f (gl->sl*(1-s1) + s1*gl->sh, gl->tl*(1-t1) + t1*gl->th); + glVertex2f (x, y); + glTexCoord2f (gl->sl*(1-s2) + s2*gl->sh, gl->tl*(1-t1) + t1*gl->th); + glVertex2f (x+w, y); + glTexCoord2f (gl->sl*(1-s2) + s2*gl->sh, gl->tl*(1-t2) + t2*gl->th); + glVertex2f (x+w, y+h); + glTexCoord2f (gl->sl*(1-s1) + s1*gl->sh, gl->tl*(1-t2) + t2*gl->th); + glVertex2f (x, y+h); + glEnd (); +} + /* ============= Draw_TransPicTranslate -- johnfitz -- rewritten to use texmgr to do translation @@ -719,6 +777,11 @@ void GL_SetCanvas (canvastype newcanvas) glOrtho (0, 640, 200, 0, -99999, 99999); glViewport (glx + (glwidth - 320*s) / 2, gly + (glheight - 200*s) / 2, 640*s, 200*s); break; + case CANVAS_CSQC: + s = CLAMP (1.0, scr_sbarscale.value, (float)glwidth / 320.0); + glOrtho (0, glwidth/s, glheight/s, 0, -99999, 99999); + glViewport (glx, gly, glwidth, glheight); + break; case CANVAS_SBAR: s = CLAMP (1.0, scr_sbarscale.value, (float)glwidth / 320.0); if (cl.gametype == GAME_DEATHMATCH) diff --git a/Quake/gl_model.c b/Quake/gl_model.c index 556804a6..c3ab54df 100644 --- a/Quake/gl_model.c +++ b/Quake/gl_model.c @@ -478,6 +478,8 @@ static bspx_header_t *bspxheader; //unsupported lumps ('documented' elsewhere): //BRUSHLIST (because hulls suck) //LIGHTINGDIR (.lux) +//LIGHTING_E5BGR9 (hdr lighting) +//VERTEXNORMALS (smooth shading with dlights/rtlights) static void *Q1BSPX_FindLump(char *lumpname, int *lumpsize) { int i; @@ -3149,6 +3151,10 @@ void Mod_LoadAliasModel (qmodel_t *mod, void *buffer) Mod_CalcAliasBounds (pheader); //johnfitz + //Spike: for setmodel compat with vanilla + mod->clipmins[0] = mod->clipmins[1] = mod->clipmins[2] = -16; + mod->clipmaxs[0] = mod->clipmaxs[1] = mod->clipmaxs[2] = 16; + // // build the draw lists // diff --git a/Quake/gl_rmain.c b/Quake/gl_rmain.c index 99fb6b61..4d77e342 100644 --- a/Quake/gl_rmain.c +++ b/Quake/gl_rmain.c @@ -753,7 +753,7 @@ void R_ShowBoundingBoxes (void) glDisable (GL_CULL_FACE); glColor3f (1,1,1); - for (i=0, ed=NEXT_EDICT(sv.edicts) ; iedicts) ; inum_edicts ; i++, ed=NEXT_EDICT(ed)) { if (ed == sv_player) continue; //don't draw player's own bbox diff --git a/Quake/gl_screen.c b/Quake/gl_screen.c index 5372d22e..bad743a5 100644 --- a/Quake/gl_screen.c +++ b/Quake/gl_screen.c @@ -391,7 +391,7 @@ static void SCR_CalcRefdef (void) size = scr_viewsize.value; scale = CLAMP (1.0, scr_sbarscale.value, (float)glwidth / 320.0); - if (size >= 120 || cl.intermission || scr_sbaralpha.value < 1) //johnfitz -- scr_sbaralpha.value + if (size >= 120 || cl.intermission || (scr_sbaralpha.value < 1 || cl.qcvm.extfuncs.CSQC_DrawHud)) //johnfitz -- scr_sbaralpha.value. Spike -- simple csqc assumes fullscreen video the same way. sb_lines = 0; else if (size >= 110) sb_lines = 24 * scale; diff --git a/Quake/gl_vidsdl.c b/Quake/gl_vidsdl.c index 0164efde..032d66b8 100644 --- a/Quake/gl_vidsdl.c +++ b/Quake/gl_vidsdl.c @@ -840,13 +840,7 @@ static void VID_Restart (void) // // update mouse grab // - if (key_dest == key_console || key_dest == key_menu) - { - if (modestate == MS_WINDOWED) - IN_Deactivate(true); - else if (modestate == MS_FULLSCREEN) - IN_Activate(); - } + IN_UpdateGrabs(); } /* @@ -1397,6 +1391,15 @@ void VID_Shutdown (void) } } +void VID_SetWindowCaption(const char *newcaption) +{ +#if defined(USE_SDL2) + SDL_SetWindowTitle(draw_context, newcaption); +#else + SDL_WM_SetCaption(newcaption, newcaption); +#endif +} + /* =================================================================== @@ -1802,14 +1805,7 @@ void VID_Toggle (void) VID_SyncCvars(); - // update mouse grab - if (key_dest == key_console || key_dest == key_menu) - { - if (modestate == MS_WINDOWED) - IN_Deactivate(true); - else if (modestate == MS_FULLSCREEN) - IN_Activate(); - } + IN_UpdateGrabs(); } else { @@ -2231,7 +2227,7 @@ static void VID_MenuKey (int key) Cbuf_AddText ("vid_restart\n"); key_dest = key_game; m_state = m_none; - IN_Activate(); + IN_UpdateGrabs(); break; default: break; @@ -2323,10 +2319,10 @@ VID_Menu_f */ static void VID_Menu_f (void) { - IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_menu; m_state = m_video; m_entersound = true; + IN_UpdateGrabs(); //set all the cvars to match the current mode when entering the menu VID_SyncCvars (); diff --git a/Quake/host.c b/Quake/host.c index 4f5752b3..d65d35ec 100644 --- a/Quake/host.c +++ b/Quake/host.c @@ -62,6 +62,7 @@ cvar_t host_speeds = {"host_speeds","0",CVAR_NONE}; // set for running times cvar_t host_maxfps = {"host_maxfps", "72", CVAR_ARCHIVE}; //johnfitz cvar_t host_timescale = {"host_timescale", "0", CVAR_NONE}; //johnfitz cvar_t max_edicts = {"max_edicts", "15000", CVAR_NONE}; //johnfitz //ericw -- changed from 2048 to 8192, removed CVAR_ARCHIVE +cvar_t cl_nocsqc = {"cl_nocsqc", "0", CVAR_NONE}; //spike -- blocks the loading of any csqc modules cvar_t sys_ticrate = {"sys_ticrate","0.05",CVAR_NONE}; // dedicated server cvar_t serverprofile = {"serverprofile","0",CVAR_NONE}; @@ -124,6 +125,8 @@ void Host_EndGame (const char *message, ...) va_end (argptr); Con_DPrintf ("Host_EndGame: %s\n",string); + PR_SwitchQCVM(NULL); + if (sv.active) Host_ShutdownServer (false); @@ -155,6 +158,8 @@ void Host_Error (const char *error, ...) Sys_Error ("Host_Error: recursively entered"); inerror = true; + PR_SwitchQCVM(NULL); + SCR_EndLoadingPlaque (); // reenable screen updates va_start (argptr,error); @@ -261,6 +266,7 @@ void Host_InitLocal (void) Cvar_SetCallback (&host_maxfps, Max_Fps_f); Cvar_RegisterVariable (&host_timescale); //johnfitz + Cvar_RegisterVariable (&cl_nocsqc); //spike Cvar_RegisterVariable (&max_edicts); //johnfitz Cvar_SetCallback (&max_edicts, Max_Edicts_f); Cvar_RegisterVariable (&devstats); //johnfitz @@ -524,11 +530,13 @@ void Host_ShutdownServer(qboolean crash) if (count) Con_Printf("Host_ShutdownServer: NET_SendToAll failed for %u clients\n", count); + PR_SwitchQCVM(&sv.qcvm); for (i = 0, host_client = svs.clients; i < svs.maxclients; i++, host_client++) if (host_client->active) SV_DropClient(crash); - - sv.worldmodel = NULL; + + qcvm->worldmodel = NULL; + PR_SwitchQCVM(NULL); // // clear structures @@ -554,7 +562,8 @@ void Host_ClearMemory (void) /* host_hunklevel MUST be set at this point */ Hunk_FreeToLowMark (host_hunklevel); cls.signon = 0; - free(sv.edicts); // ericw -- sv.edicts switched to use malloc() + PR_ClearProgs(&sv.qcvm); + PR_ClearProgs(&cl.qcvm); free(cl.static_entities); free(sv.static_entities); //spike -- this is dynamic too, now free(sv.ambientsounds); @@ -656,14 +665,14 @@ void Host_ServerFrame (void) //johnfitz -- devstats if (cls.signon == SIGNONS) { - for (i=0, active=0; inum_edicts; i++) { ent = EDICT_NUM(i); if (!ent->free) active++; } if (active > 600 && dev_peakstats.edicts <= 600) - Con_DWarning ("%i edicts exceeds standard limit of 600 (max = %d).\n", active, sv.max_edicts); + Con_DWarning ("%i edicts exceeds standard limit of 600 (max = %d).\n", active, qcvm->max_edicts); dev_stats.edicts = active; dev_peakstats.edicts = q_max(active, dev_peakstats.edicts); } @@ -714,6 +723,46 @@ void _Host_Frame (float time) { if (CL_CheckDownloads()) { + PR_ClearProgs(&cl.qcvm); + if (pr_checkextension.value && !cl_nocsqc.value) + { //only try to use csqc if qc extensions are enabled. + PR_SwitchQCVM(&cl.qcvm); + //try csprogs.dat first, then fall back on progs.dat in case someone tried merging the two. + //we only care about it if it actually contains a CSQC_DrawHud, otherwise its either just a (misnamed) ssqc progs or a full csqc progs that would just crash us on 3d stuff. + if ((PR_LoadProgs("csprogs.dat", false, pr_csqcbuiltins, pr_csqcnumbuiltins) && qcvm->extfuncs.CSQC_DrawHud)|| + (PR_LoadProgs("progs.dat", false, pr_csqcbuiltins, pr_csqcnumbuiltins) && qcvm->extfuncs.CSQC_DrawHud)) + { + qcvm->max_edicts = CLAMP (MIN_EDICTS,(int)max_edicts.value,MAX_EDICTS); + qcvm->edicts = (edict_t *) malloc (qcvm->max_edicts*qcvm->edict_size); + qcvm->num_edicts = qcvm->reserved_edicts = 1; + memset(qcvm->edicts, 0, qcvm->num_edicts*qcvm->edict_size); + + //set a few globals, if they exist + if (qcvm->extglobals.maxclients) + *qcvm->extglobals.maxclients = cl.maxclients; + pr_global_struct->time = cl.time; + pr_global_struct->mapname = PR_SetEngineString(cl.mapname); + pr_global_struct->total_monsters = cl.statsf[STAT_TOTALMONSTERS]; + pr_global_struct->total_secrets = cl.statsf[STAT_TOTALSECRETS]; + pr_global_struct->deathmatch = cl.gametype; + pr_global_struct->coop = (cl.gametype == GAME_COOP) && cl.maxclients != 1; + if (qcvm->extglobals.player_localnum) + *qcvm->extglobals.player_localnum = cl.viewentity; //this is a guess, but is important for scoreboards. + + //and call the init function... if it exists. + if (qcvm->extfuncs.CSQC_Init) + { + G_FLOAT(OFS_PARM0) = 0; + G_INT(OFS_PARM1) = PR_SetEngineString("QuakeSpasm-Spiked"); + G_FLOAT(OFS_PARM2) = QUAKESPASM_VERSION; + PR_ExecuteProgram(qcvm->extfuncs.CSQC_Init); + } + } + else + PR_ClearProgs(qcvm); + PR_SwitchQCVM(NULL); + } + cl.sendprespawn = false; MSG_WriteByte (&cls.message, clc_stringcmd); MSG_WriteString (&cls.message, "prespawn"); @@ -736,7 +785,11 @@ void _Host_Frame (float time) Host_GetConsoleCommands (); if (sv.active) + { + PR_SwitchQCVM(&sv.qcvm); Host_ServerFrame (); + PR_SwitchQCVM(NULL); + } //------------------- // diff --git a/Quake/host_cmd.c b/Quake/host_cmd.c index 962d44af..2d5fc531 100644 --- a/Quake/host_cmd.c +++ b/Quake/host_cmd.c @@ -464,10 +464,10 @@ void Host_Status_f (void) j++; if (j) print_fn ( "effects: %i/%i\n", j, MAX_PARTICLETYPES-1); - for (i = 1,j=1; i < sv.num_edicts; i++) - if (!sv.edicts[i].free) + for (i = 1,j=1; i < sv.qcvm.num_edicts; i++) + if (!sv.qcvm.edicts[i].free) j++; - print_fn ( "entities:%i/%i\n", j, sv.max_edicts); + print_fn ( "entities:%i/%i\n", j, sv.qcvm.max_edicts); print_fn ( "players: %i active (%i max)\n\n", net_activeconnections, svs.maxclients); for (j = 0, client = svs.clients; j < svs.maxclients; j++, client++) @@ -840,9 +840,9 @@ void Host_Map_f (void) CL_Disconnect (); Host_ShutdownServer(false); - if (cls.state != ca_dedicated) - IN_Activate(); key_dest = key_game; // remove console or menu + if (cls.state != ca_dedicated) + IN_UpdateGrabs(); SCR_BeginLoadingPlaque (); svs.serverflags = 0; // haven't completed an episode yet @@ -851,7 +851,9 @@ void Host_Map_f (void) p = strstr(name, ".bsp"); if (p && p[4] == '\0') *p = '\0'; + PR_SwitchQCVM(&sv.qcvm); SV_SpawnServer (name); + PR_SwitchQCVM(NULL); if (!sv.active) return; @@ -933,12 +935,15 @@ void Host_Changelevel_f (void) Host_Error ("cannot find map %s", level); //johnfitz - if (cls.state != ca_dedicated) - IN_Activate(); // -- S.A. key_dest = key_game; // remove console or menu + if (cls.state != ca_dedicated) + IN_UpdateGrabs(); // -- S.A. + + PR_SwitchQCVM(&sv.qcvm); SV_SaveSpawnparms (); q_strlcpy (level, Cmd_Argv(1), sizeof(level)); SV_SpawnServer (level); + PR_SwitchQCVM(NULL); // also issue an error if spawn failed -- O.S. if (!sv.active) Host_Error ("cannot run map %s", level); @@ -966,7 +971,9 @@ void Host_Restart_f (void) return; } q_strlcpy (mapname, sv.name, sizeof(mapname)); // mapname gets cleared in spawnserver + PR_SwitchQCVM(&sv.qcvm); SV_SpawnServer (mapname); + PR_SwitchQCVM(NULL); if (!sv.active) Host_Error ("cannot restart map %s", mapname); } @@ -1134,7 +1141,7 @@ void Host_Savegame_f (void) fprintf (f, "%f\n", svs.clients->spawn_parms[i]); fprintf (f, "%d\n", current_skill); fprintf (f, "%s\n", sv.name); - fprintf (f, "%f\n",sv.time); + fprintf (f, "%f\n", qcvm->time); // write the light styles @@ -1148,7 +1155,7 @@ void Host_Savegame_f (void) ED_WriteGlobals (f); - for (i = 0; i < sv.num_edicts; i++) + for (i = 0; i < qcvm->num_edicts; i++) { ED_Write (f, EDICT_NUM(i)); fflush (f); @@ -1268,10 +1275,12 @@ void Host_Loadgame_f (void) CL_Disconnect_f (); + PR_SwitchQCVM(&sv.qcvm); SV_SpawnServer (mapname); if (!sv.active) { + PR_SwitchQCVM(NULL); free (start); start = NULL; Con_Printf ("Couldn't load map\n"); @@ -1369,12 +1378,12 @@ void Host_Loadgame_f (void) else { // parse an edict ent = EDICT_NUM(entnum); - if (entnum < sv.num_edicts) { + if (entnum < qcvm->num_edicts) { ent->free = false; - memset (&ent->v, 0, progs->entityfields * 4); + memset (&ent->v, 0, qcvm->progs->entityfields * 4); } else { - memset (ent, 0, pr_edict_size); + memset (ent, 0, qcvm->edict_size); } data = ED_ParseEdict (data, ent); @@ -1386,8 +1395,8 @@ void Host_Loadgame_f (void) entnum++; } - sv.num_edicts = entnum; - sv.time = time; + qcvm->num_edicts = entnum; + qcvm->time = time; free (start); start = NULL; @@ -1395,6 +1404,8 @@ void Host_Loadgame_f (void) for (i = 0; i < NUM_SPAWN_PARMS; i++) svs.clients->spawn_parms[i] = spawn_parms[i]; + PR_SwitchQCVM(NULL); + if (cls.state != ca_dedicated) { CL_EstablishConnection ("local"); @@ -1674,7 +1685,7 @@ void Host_Kill_f (void) return; } - pr_global_struct->time = sv.time; + pr_global_struct->time = qcvm->time; pr_global_struct->self = EDICT_TO_PROG(sv_player); PR_ExecuteProgram (pr_global_struct->ClientKill); } @@ -1783,7 +1794,7 @@ void Host_Spawn_f (void) // set up the edict ent = host_client->edict; - memset (&ent->v, 0, progs->entityfields * 4); + memset (&ent->v, 0, qcvm->progs->entityfields * 4); ent->v.colormap = NUM_FOR_EDICT(ent); ent->v.team = (host_client->colors & 15) + 1; ent->v.netname = PR_SetEngineString(host_client->name); @@ -1792,11 +1803,11 @@ void Host_Spawn_f (void) for (i=0 ; i< NUM_SPAWN_PARMS ; i++) (&pr_global_struct->parm1)[i] = host_client->spawn_parms[i]; // call the spawn function - pr_global_struct->time = sv.time; + pr_global_struct->time = qcvm->time; pr_global_struct->self = EDICT_TO_PROG(sv_player); PR_ExecuteProgram (pr_global_struct->ClientConnect); - if ((Sys_DoubleTime() - NET_QSocketGetTime(host_client->netconnection)) <= sv.time) + if ((Sys_DoubleTime() - NET_QSocketGetTime(host_client->netconnection)) <= qcvm->time) Sys_Printf ("%s entered the game\n", host_client->name); PR_ExecuteProgram (pr_global_struct->PutClientInServer); @@ -1808,7 +1819,7 @@ void Host_Spawn_f (void) // send time of update MSG_WriteByte (&host_client->message, svc_time); - MSG_WriteFloat (&host_client->message, sv.time); + MSG_WriteFloat (&host_client->message, qcvm->time); if (host_client->protocol_pext2 & PEXT2_PREDINFO) MSG_WriteShort(&host_client->message, (host_client->lastmovemessage&0xffff)); @@ -2226,7 +2237,7 @@ edict_t *FindViewthing (void) int i; edict_t *e; - for (i=0 ; inum_edicts ; i++) { e = EDICT_NUM(i); if ( !strcmp (PR_GetString(e->v.classname), "viewthing") ) diff --git a/Quake/in_sdl.c b/Quake/in_sdl.c index 7175749e..ee793fe5 100644 --- a/Quake/in_sdl.c +++ b/Quake/in_sdl.c @@ -33,6 +33,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #endif static qboolean textmode; +extern qboolean bind_grab; //from the menu code, so that we regrab the mouse in order to pass inputs through static cvar_t in_debugkeys = {"in_debugkeys", "0", CVAR_NONE}; @@ -199,8 +200,8 @@ static void IN_ReenableOSXMouseAccel (void) } #endif /* MACOS_X_ACCELERATION_HACK */ - -void IN_Activate (void) +#if 0 +static void IN_Activate (void) { if (no_mouse) return; @@ -238,7 +239,7 @@ void IN_Activate (void) total_dy = 0; } -void IN_Deactivate (qboolean free_cursor) +static void IN_Deactivate (qboolean free_cursor) { if (no_mouse) return; @@ -270,7 +271,84 @@ void IN_Deactivate (qboolean free_cursor) } /* discard all mouse events when input is deactivated */ - IN_BeginIgnoringMouseEvents(); + if (cl.qcvm.extfuncs.CSQC_InputEvent && free_cursor) + IN_EndIgnoringMouseEvents(); + else + IN_BeginIgnoringMouseEvents(); +} +#endif + +static void IN_UpdateGrabs_Internal(qboolean forecerelease) +{ + qboolean wantcursor; //we're trying to get a cursor here... + qboolean freemouse; //the OS should have a free cursor too... + qboolean needevents; //whether we want to receive events still + + wantcursor = (key_dest == key_console || (key_dest == key_menu&&!bind_grab)) || (key_dest == key_game && cl.csqc_cursorforced); + freemouse = wantcursor && (modestate == MS_WINDOWED || key_dest == key_game && cl.csqc_cursorforced); + needevents = (!wantcursor) || key_dest == key_game; + + if (forecerelease) + needevents = freemouse = wantcursor = true; + +#ifdef MACOS_X_ACCELERATION_HACK + if (!freemouse) + { /* Save the status of mouse acceleration */ + if (originalMouseSpeed == -1 && in_disablemacosxmouseaccel.value) + IN_DisableOSXMouseAccel(); + } + else if (originalMouseSpeed != -1) + IN_ReenableOSXMouseAccel(); +#endif + +#if defined(USE_SDL2) + if (SDL_SetRelativeMouseMode(freemouse?SDL_FALSE:SDL_TRUE) != 0) + { + Con_Printf("WARNING: SDL_SetRelativeMouseMode(%s) failed.\n", freemouse?"SDL_FALSE":"SDL_TRUE"); + } +#else + if (freemouse) + { + if (SDL_WM_GrabInput(SDL_GRAB_QUERY) != SDL_GRAB_OFF) + { + SDL_WM_GrabInput(SDL_GRAB_OFF); + if (SDL_WM_GrabInput(SDL_GRAB_QUERY) != SDL_GRAB_OFF) + Con_Printf("WARNING: SDL_WM_GrabInput(SDL_GRAB_OFF) failed.\n"); + } + + if (SDL_ShowCursor(SDL_QUERY) != SDL_ENABLE) + { + SDL_ShowCursor(SDL_ENABLE); + if (SDL_ShowCursor(SDL_QUERY) != SDL_ENABLE) + Con_Printf("WARNING: SDL_ShowCursor(SDL_ENABLE) failed.\n"); + } + } + else + { + if (SDL_WM_GrabInput(SDL_GRAB_QUERY) != SDL_GRAB_ON) + { + SDL_WM_GrabInput(SDL_GRAB_ON); + if (SDL_WM_GrabInput(SDL_GRAB_QUERY) != SDL_GRAB_ON) + Con_Printf("WARNING: SDL_WM_GrabInput(SDL_GRAB_ON) failed.\n"); + } + + if (SDL_ShowCursor(SDL_QUERY) != SDL_DISABLE) + { + SDL_ShowCursor(SDL_DISABLE); + if (SDL_ShowCursor(SDL_QUERY) != SDL_DISABLE) + Con_Printf("WARNING: SDL_ShowCursor(SDL_DISABLE) failed.\n"); + } + } +#endif + + if (needevents) + IN_EndIgnoringMouseEvents(); + else + IN_BeginIgnoringMouseEvents(); +} +void IN_UpdateGrabs(void) +{ + IN_UpdateGrabs_Internal(false); } void IN_StartupJoystick (void) @@ -375,13 +453,13 @@ void IN_Init (void) Cvar_RegisterVariable(&joy_swapmovelook); Cvar_RegisterVariable(&joy_enable); - IN_Activate(); + IN_UpdateGrabs(); IN_StartupJoystick(); } void IN_Shutdown (void) { - IN_Deactivate(true); + IN_UpdateGrabs(); IN_ShutdownJoystick(); } @@ -389,8 +467,36 @@ extern cvar_t cl_maxpitch; /* johnfitz -- variable pitch clamping */ extern cvar_t cl_minpitch; /* johnfitz -- variable pitch clamping */ -void IN_MouseMotion(int dx, int dy) +void IN_MouseMotion(int dx, int dy, int wx, int wy) { + if (key_dest != key_game && key_dest != key_message) + dx = dy = 0; + else if (cl.qcvm.extfuncs.CSQC_InputEvent) + { + PR_SwitchQCVM(&cl.qcvm); + if (cl.csqc_cursorforced) + { + float s = CLAMP (1.0, scr_sbarscale.value, (float)glwidth / 320.0); + wx /= s; + wy /= s; + + G_FLOAT(OFS_PARM0) = CSIE_MOUSEABS; + G_VECTORSET(OFS_PARM1, wx, wy, 0); //x + G_VECTORSET(OFS_PARM2, wy, 0, 0); //y + G_VECTORSET(OFS_PARM3, 0, 0, 0); //devid + } + else + { + G_FLOAT(OFS_PARM0) = CSIE_MOUSEDELTA; + G_VECTORSET(OFS_PARM1, dx, dy, 0); //x + G_VECTORSET(OFS_PARM2, dy, 0, 0); //y + G_VECTORSET(OFS_PARM3, 0, 0, 0); //devid + } + PR_ExecuteProgram(cl.qcvm.extfuncs.CSQC_InputEvent); + if (G_FLOAT(OFS_RETURN) || cl.csqc_cursorforced) + dx = dy = 0; //if the qc says it handled it, swallow the movement. + PR_SwitchQCVM(NULL); + } total_dx += dx; total_dy += dy; } @@ -684,8 +790,8 @@ void IN_JoyMove (usercmd_t *cmd) cmd->sidemove += (cl_sidespeed.value * speed * moveEased.x); cmd->forwardmove -= (cl_forwardspeed.value * speed * moveEased.y); - cl.viewangles[YAW] -= lookEased.x * joy_sensitivity_yaw.value * host_frametime; - cl.viewangles[PITCH] += lookEased.y * joy_sensitivity_pitch.value * (joy_invert.value ? -1.0 : 1.0) * host_frametime; + cl.viewangles[YAW] -= lookEased.x * joy_sensitivity_yaw.value * host_frametime * cl.csqc_sensitivity; + cl.viewangles[PITCH] += lookEased.y * joy_sensitivity_pitch.value * (joy_invert.value ? -1.0 : 1.0) * host_frametime * cl.csqc_sensitivity; if (lookEased.x != 0 || lookEased.y != 0) V_StopPitchDrift(); @@ -711,7 +817,7 @@ void IN_MouseMove(usercmd_t *cmd) if ( (in_strafe.state & 1) || (lookstrafe.value && (in_mlook.state & 1) )) cmd->sidemove += m_side.value * dmx; else - cl.viewangles[YAW] -= m_yaw.value * dmx; + cl.viewangles[YAW] -= m_yaw.value * dmx * cl.csqc_sensitivity; if (in_mlook.state & 1) { @@ -721,7 +827,7 @@ void IN_MouseMove(usercmd_t *cmd) if ( (in_mlook.state & 1) && !(in_strafe.state & 1)) { - cl.viewangles[PITCH] += m_pitch.value * dmy; + cl.viewangles[PITCH] += m_pitch.value * dmy * cl.csqc_sensitivity; /* johnfitz -- variable pitch clamping */ if (cl.viewangles[PITCH] > cl_maxpitch.value) cl.viewangles[PITCH] = cl_maxpitch.value; @@ -1004,6 +1110,8 @@ void IN_SendKeyEvents (void) int key; qboolean down; + IN_UpdateGrabs(); + while (SDL_PollEvent(&event)) { switch (event.type) @@ -1098,7 +1206,7 @@ void IN_SendKeyEvents (void) #endif case SDL_MOUSEMOTION: - IN_MouseMotion(event.motion.xrel, event.motion.yrel); + IN_MouseMotion(event.motion.xrel, event.motion.yrel, event.motion.x, event.motion.y); break; #if defined(USE_SDL2) diff --git a/Quake/input.h b/Quake/input.h index fe551fb0..b4b74435 100644 --- a/Quake/input.h +++ b/Quake/input.h @@ -32,7 +32,7 @@ void IN_Commands (void); // oportunity for devices to stick commands on the script buffer // mouse moved by dx and dy pixels -void IN_MouseMotion(int dx, int dy); +void IN_MouseMotion(int dx, int dy, int wx, int wy); void IN_SendKeyEvents (void); @@ -47,11 +47,9 @@ void IN_Move (usercmd_t *cmd); void IN_ClearStates (void); // restores all button and position states to defaults -// called when the app becomes active -void IN_Activate (); - -// called when the app becomes inactive -void IN_Deactivate (qboolean free_cursor); +// spike - called whenever mouse focus etc has changed (including console toggled). this is optional, but there's still a number of blocking commands, like connect +// doing all the mode, state, etc checks in one place ensures that they're consistent, regardless of what else is happening. +void IN_UpdateGrabs(void); #endif /* _QUAKE_INPUT_H */ diff --git a/Quake/keys.c b/Quake/keys.c index 6d1f0c9b..95b93a6d 100644 --- a/Quake/keys.c +++ b/Quake/keys.c @@ -174,6 +174,326 @@ keyname_t keynames[] = {NULL, 0} }; + + +//QC key codes are based upon DP's keycode constants. This is on account of menu.dat coming first. +int Key_NativeToQC(int code) +{ + switch(code) + { + case K_TAB: return 9; + case K_ENTER: return 13; + case K_ESCAPE: return 27; + case K_SPACE: return 32; + case K_BACKSPACE: return 127; + case K_UPARROW: return 128; + case K_DOWNARROW: return 129; + case K_LEFTARROW: return 130; + case K_RIGHTARROW: return 131; + case K_ALT: return 132; + case K_CTRL: return 133; + case K_SHIFT: return 134; + case K_F1: return 135; + case K_F2: return 136; + case K_F3: return 137; + case K_F4: return 138; + case K_F5: return 139; + case K_F6: return 140; + case K_F7: return 141; + case K_F8: return 142; + case K_F9: return 143; + case K_F10: return 144; + case K_F11: return 145; + case K_F12: return 146; + case K_INS: return 147; + case K_DEL: return 148; + case K_PGDN: return 149; + case K_PGUP: return 150; + case K_HOME: return 151; + case K_END: return 152; + case K_PAUSE: return 153; + case K_KP_NUMLOCK: return 154; +// case K_CAPSLOCK: return 155; +// case K_SCRLCK: return 156; + case K_KP_INS: return 157; + case K_KP_END: return 158; + case K_KP_DOWNARROW: return 159; + case K_KP_PGDN: return 160; + case K_KP_LEFTARROW: return 161; + case K_KP_5: return 162; + case K_KP_RIGHTARROW: return 163; + case K_KP_HOME: return 164; + case K_KP_UPARROW: return 165; + case K_KP_PGUP: return 166; + case K_KP_DEL: return 167; + case K_KP_SLASH: return 168; + case K_KP_STAR: return 169; + case K_KP_MINUS: return 170; + case K_KP_PLUS: return 171; + case K_KP_ENTER: return 172; +// case K_KP_EQUALS: return 173; +// case K_PRINTSCREEN: return 174; + + case K_MOUSE1: return 512; + case K_MOUSE2: return 513; + case K_MOUSE3: return 514; + case K_MWHEELUP: return 515; + case K_MWHEELDOWN: return 516; + case K_MOUSE4: return 517; + case K_MOUSE5: return 518; +// case K_MOUSE6: return 519; +// case K_MOUSE7: return 520; +// case K_MOUSE8: return 521; +// case K_MOUSE9: return 522; +// case K_MOUSE10: return 523; +// case K_MOUSE11: return 524; +// case K_MOUSE12: return 525; +// case K_MOUSE13: return 526; +// case K_MOUSE14: return 527; +// case K_MOUSE15: return 528; +// case K_MOUSE16: return 529; + + case K_JOY1: return 768; + case K_JOY2: return 769; + case K_JOY3: return 770; + case K_JOY4: return 771; +// case K_JOY5: return 772; +// case K_JOY6: return 773; +// case K_JOY7: return 774; +// case K_JOY8: return 775; +// case K_JOY9: return 776; +// case K_JOY10: return 777; +// case K_JOY11: return 778; +// case K_JOY12: return 779; +// case K_JOY13: return 780; +// case K_JOY14: return 781; +// case K_JOY15: return 782; +// case K_JOY16: return 783; + + case K_AUX1: return 784; + case K_AUX2: return 785; + case K_AUX3: return 786; + case K_AUX4: return 787; + case K_AUX5: return 788; + case K_AUX6: return 789; + case K_AUX7: return 790; + case K_AUX8: return 791; + case K_AUX9: return 792; + case K_AUX10: return 793; + case K_AUX11: return 794; + case K_AUX12: return 795; + case K_AUX13: return 796; + case K_AUX14: return 797; + case K_AUX15: return 798; + case K_AUX16: return 799; + case K_AUX17: return 800; + case K_AUX18: return 801; + case K_AUX19: return 802; + case K_AUX20: return 803; + case K_AUX21: return 804; + case K_AUX22: return 805; + case K_AUX23: return 806; + case K_AUX24: return 807; + case K_AUX25: return 808; + case K_AUX26: return 809; + case K_AUX27: return 810; + case K_AUX28: return 811; + case K_AUX29: return 812; + case K_AUX30: return 813; + case K_AUX31: return 814; + case K_AUX32: return 815; + +// case K_GP_DPAD_UP: return 816; +// case K_GP_DPAD_DOWN: return 817; +// case K_GP_DPAD_LEFT: return 818; +// case K_GP_DPAD_RIGHT: return 819; +// case K_GP_START: return 820; +// case K_GP_BACK: return 821; + case K_LTHUMB: return 822; + case K_RTHUMB: return 823; + case K_LSHOULDER: return 824; + case K_RSHOULDER: return 825; + case K_ABUTTON: return 826; + case K_BBUTTON: return 827; + case K_XBUTTON: return 828; + case K_YBUTTON: return 829; + case K_LTRIGGER: return 830; + case K_RTRIGGER: return 831; + + default: + //ascii chars are mapped as-is (yes this means upper-case keys don't get used). + if (code >= 0 && code < 127) + return code; + return -code; //qc doesn't have extended keys available to it. + } +} + +int Key_QCToNative(int code) +{ + switch(code) + { + case 9: return K_TAB; + case 13: return K_ENTER; + case 27: return K_ESCAPE; + case 32: return K_SPACE; + case 127: return K_BACKSPACE; + case 128: return K_UPARROW; + case 129: return K_DOWNARROW; + case 130: return K_LEFTARROW; + case 131: return K_RIGHTARROW; + case 132: return K_ALT; + case 133: return K_CTRL; + case 134: return K_SHIFT; + case 135: return K_F1; + case 136: return K_F2; + case 137: return K_F3; + case 138: return K_F4; + case 139: return K_F5; + case 140: return K_F6; + case 141: return K_F7; + case 142: return K_F8; + case 143: return K_F9; + case 144: return K_F10; + case 145: return K_F11; + case 146: return K_F12; + case 147: return K_INS; + case 148: return K_DEL; + case 149: return K_PGDN; + case 150: return K_PGUP; + case 151: return K_HOME; + case 152: return K_END; + case 153: return K_PAUSE; + case 154: return K_KP_NUMLOCK; +// case 155: return K_CAPSLOCK; +// case 156: return K_SCRLCK; + case 157: return K_KP_INS; + case 158: return K_KP_END; + case 159: return K_KP_DOWNARROW; + case 160: return K_KP_PGDN; + case 161: return K_KP_LEFTARROW; + case 162: return K_KP_5; + case 163: return K_KP_RIGHTARROW; + case 164: return K_KP_HOME; + case 165: return K_KP_UPARROW; + case 166: return K_KP_PGUP; + case 167: return K_KP_DEL; + case 168: return K_KP_SLASH; + case 169: return K_KP_STAR; + case 170: return K_KP_MINUS; + case 171: return K_KP_PLUS; + case 172: return K_KP_ENTER; +// case 173: return K_KP_EQUALS; +// case 174: return K_PRINTSCREEN; + + case 512: return K_MOUSE1; + case 513: return K_MOUSE2; + case 514: return K_MOUSE3; + case 515: return K_MWHEELUP; + case 516: return K_MWHEELDOWN; + case 517: return K_MOUSE4; + case 518: return K_MOUSE5; +// case 519: return K_MOUSE6; +// case 520: return K_MOUSE7; +// case 521: return K_MOUSE8; +// case 522: return K_MOUSE9; +// case 523: return K_MOUSE10; +// case 524: return K_MOUSE11; +// case 525: return K_MOUSE12; +// case 526: return K_MOUSE13; +// case 527: return K_MOUSE14; +// case 528: return K_MOUSE15; +// case 529: return K_MOUSE16; + + case 768: return K_JOY1; + case 769: return K_JOY2; + case 770: return K_JOY3; + case 771: return K_JOY4; +// case 772: return K_JOY5; +// case 773: return K_JOY6; +// case 774: return K_JOY7; +// case 775: return K_JOY8; +// case 776: return K_JOY9; +// case 777: return K_JOY10; +// case 778: return K_JOY11; +// case 779: return K_JOY12; +// case 780: return K_JOY13; +// case 781: return K_JOY14; +// case 782: return K_JOY15; +// case 783: return K_JOY16; + + case 784: return K_AUX1; + case 785: return K_AUX2; + case 786: return K_AUX3; + case 787: return K_AUX4; + case 788: return K_AUX5; + case 789: return K_AUX6; + case 790: return K_AUX7; + case 791: return K_AUX8; + case 792: return K_AUX9; + case 793: return K_AUX10; + case 794: return K_AUX11; + case 795: return K_AUX12; + case 796: return K_AUX13; + case 797: return K_AUX14; + case 798: return K_AUX15; + case 799: return K_AUX16; + case 800: return K_AUX17; + case 801: return K_AUX18; + case 802: return K_AUX19; + case 803: return K_AUX20; + case 804: return K_AUX21; + case 805: return K_AUX22; + case 806: return K_AUX23; + case 807: return K_AUX24; + case 808: return K_AUX25; + case 809: return K_AUX26; + case 810: return K_AUX27; + case 811: return K_AUX28; + case 812: return K_AUX29; + case 813: return K_AUX30; + case 814: return K_AUX31; + case 815: return K_AUX32; + +// case 816: return K_GP_DPAD_UP; +// case 817: return K_GP_DPAD_DOWN; +// case 818: return K_GP_DPAD_LEFT; +// case 819: return K_GP_DPAD_RIGHT; +// case 820: return K_GP_START; +// case 821: return K_GP_BACK; + case 822: return K_LTHUMB; + case 823: return K_RTHUMB; + case 824: return K_LSHOULDER; + case 825: return K_RSHOULDER; + case 826: return K_ABUTTON; + case 827: return K_BBUTTON; + case 828: return K_XBUTTON; + case 829: return K_YBUTTON; + case 830: return K_LTRIGGER; + case 831: return K_RTRIGGER; +// case 832: return K_GP_LEFT_THUMB_UP; +// case 833: return K_GP_LEFT_THUMB_DOWN; +// case 834: return K_GP_LEFT_THUMB_LEFT; +// case 835: return K_GP_LEFT_THUMB_RIGHT; +// case 836: return K_GP_RIGHT_THUMB_UP; +// case 837: return K_GP_RIGHT_THUMB_DOWN; +// case 838: return K_GP_RIGHT_THUMB_LEFT; +// case 839: return K_GP_RIGHT_THUMB_RIGHT; + default: + //ascii chars are mapped as-is (yes this means upper-case keys don't get used). + if (code >= 0 && code < 127) + return code; + else if (code < 0) + { + code = -code; + if (code < 0 || code >= MAX_KEYS) + code = -1; //was invalid somehow... don't crash anything. + return code; //qc doesn't have extended keys available to it. so map negative keys back to native ones. + } + else + return -code; //this qc keycode has no native equivelent. use negatives, because we can. + } +} + /* ============================================================================== @@ -931,6 +1251,23 @@ void Key_GetGrabbedInput (int *lastkey, int *lastchar) *lastchar = key_inputgrab.lastchar; } +qboolean CSQC_HandleKeyEvent(qboolean down, int keyc, int unic) +{ + qboolean inhibit = false; + if (cl.qcvm.extfuncs.CSQC_InputEvent && key_dest == key_game) + { + PR_SwitchQCVM(&cl.qcvm); + G_FLOAT(OFS_PARM0) = down?CSIE_KEYDOWN:CSIE_KEYUP; + G_VECTORSET(OFS_PARM1, Key_NativeToQC(keyc), 0, 0); //x + G_VECTORSET(OFS_PARM2, unic, 0, 0); //y + G_VECTORSET(OFS_PARM3, 0, 0, 0); //devid + PR_ExecuteProgram(cl.qcvm.extfuncs.CSQC_InputEvent); + inhibit = G_FLOAT(OFS_RETURN); + PR_SwitchQCVM(NULL); + } + return inhibit; +} + /* =================== Key_Event @@ -981,7 +1318,10 @@ void Key_Event (int key, qboolean down) if (key == K_ESCAPE) { if (!down) + { + CSQC_HandleKeyEvent(down, key, 0); //Spike -- for consistency return; + } if (keydown[K_SHIFT]) { @@ -999,6 +1339,8 @@ void Key_Event (int key, qboolean down) break; case key_game: case key_console: + if (CSQC_HandleKeyEvent(down, key, 0)) //Spike -- CSQC needs to be able to intercept escape. Note that shift+escape will always give the console for buggy mods. + break; M_ToggleMenu_f (); break; default: @@ -1008,6 +1350,10 @@ void Key_Event (int key, qboolean down) return; } + //Spike -- give csqc a change to handle (and swallow) key events. + if (CSQC_HandleKeyEvent(down, key, 0)) + return; + // key up events only generate commands if the game key binding is // a button command (leading + sign). These will occur even in console mode, // to keep the character from continuing an action started before a console @@ -1180,16 +1526,16 @@ void Key_UpdateForDest (void) if (forced && cls.state == ca_connected) { forced = false; - IN_Activate(); key_dest = key_game; + IN_UpdateGrabs(); } break; case key_game: if (cls.state != ca_connected) { forced = true; - IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_console; + IN_UpdateGrabs(); break; } /* fallthrough */ diff --git a/Quake/keys.h b/Quake/keys.h index 2e178efc..c05a4a80 100644 --- a/Quake/keys.h +++ b/Quake/keys.h @@ -187,6 +187,9 @@ qboolean Key_TextEntry (void); void Key_SetBinding (int keynum, const char *binding); const char *Key_KeynumToString (int keynum); +int Key_StringToKeynum (const char *str); +int Key_NativeToQC(int code); +int Key_QCToNative(int code); //warning: will return negative values for unknown qc keys. void Key_WriteBindings (FILE *f); void Key_EndChat (void); diff --git a/Quake/menu.c b/Quake/menu.c index 97dbdac0..953803f4 100644 --- a/Quake/menu.c +++ b/Quake/menu.c @@ -218,9 +218,10 @@ void M_ToggleMenu_f (void) return; } - IN_Activate(); key_dest = key_game; m_state = m_none; + + IN_UpdateGrabs(); return; } if (key_dest == key_console) @@ -248,10 +249,11 @@ void M_Menu_Main_f (void) m_save_demonum = cls.demonum; cls.demonum = -1; } - IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_menu; m_state = m_main; m_entersound = true; + + IN_UpdateGrabs(); } @@ -277,10 +279,10 @@ void M_Main_Key (int key) { case K_ESCAPE: case K_BBUTTON: - IN_Activate(); key_dest = key_game; m_state = m_none; cls.demonum = m_save_demonum; + IN_UpdateGrabs(); if (!fitzmode) /* QuakeSpasm customization: */ break; if (cls.demonum != -1 && !cls.demoplayback && cls.state != ca_connected) @@ -338,10 +340,11 @@ int m_singleplayer_cursor; void M_Menu_SinglePlayer_f (void) { - IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_menu; m_state = m_singleplayer; m_entersound = true; + + IN_UpdateGrabs(); } @@ -393,8 +396,8 @@ void M_SinglePlayer_Key (int key) if (sv.active) if (!SCR_ModalMessage("Are you sure you want to\nstart a new game?\n", 0.0f)) break; - IN_Activate(); key_dest = key_game; + IN_UpdateGrabs(); if (sv.active) Cbuf_AddText ("disconnect\n"); Cbuf_AddText ("maxplayers 1\n"); @@ -459,9 +462,10 @@ void M_Menu_Load_f (void) m_entersound = true; m_state = m_load; - IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_menu; M_ScanSaves (); + + IN_UpdateGrabs(); } @@ -476,8 +480,8 @@ void M_Menu_Save_f (void) m_entersound = true; m_state = m_save; - IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_menu; + IN_UpdateGrabs(); M_ScanSaves (); } @@ -530,8 +534,8 @@ void M_Load_Key (int k) if (!loadable[load_cursor]) return; m_state = m_none; - IN_Activate(); key_dest = key_game; + IN_UpdateGrabs(); // Host_Loadgame_f can't bring up the loading plaque because too much // stack space has been used, so do it now @@ -573,8 +577,8 @@ void M_Save_Key (int k) case K_KP_ENTER: case K_ABUTTON: m_state = m_none; - IN_Activate(); key_dest = key_game; + IN_UpdateGrabs(); Cbuf_AddText (va("save s%i\n", load_cursor)); return; @@ -605,10 +609,10 @@ int m_multiplayer_cursor; void M_Menu_MultiPlayer_f (void) { - IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_menu; m_state = m_multiplayer; m_entersound = true; + IN_UpdateGrabs(); } @@ -693,7 +697,6 @@ int setup_bottom; void M_Menu_Setup_f (void) { - IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_menu; m_state = m_setup; m_entersound = true; @@ -701,6 +704,8 @@ void M_Menu_Setup_f (void) Q_strcpy(setup_hostname, hostname.string); setup_top = setup_oldtop = ((int)cl_color.value) >> 4; setup_bottom = setup_oldbottom = ((int)cl_color.value) & 15; + + IN_UpdateGrabs(); } @@ -883,12 +888,13 @@ const char *net_helpMessage [] = void M_Menu_Net_f (void) { - IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_menu; m_state = m_net; m_entersound = true; m_net_items = 2; + IN_UpdateGrabs(); + if (m_net_cursor >= m_net_items) m_net_cursor = 0; m_net_cursor--; @@ -1012,10 +1018,11 @@ int options_cursor; void M_Menu_Options_f (void) { - IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_menu; m_state = m_options; m_entersound = true; + + IN_UpdateGrabs(); } @@ -1368,7 +1375,7 @@ static size_t numbindnames; static size_t keys_first; static size_t keys_cursor; -static qboolean bind_grab; +qboolean bind_grab; void M_Keys_Close (void) { @@ -1429,10 +1436,10 @@ void M_Keys_Populate(void) void M_Menu_Keys_f (void) { - IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_menu; m_state = m_keys; m_entersound = true; + IN_UpdateGrabs(); M_Keys_Populate(); } @@ -1570,7 +1577,7 @@ void M_Keys_Key (int k) } bind_grab = false; - IN_Deactivate(modestate == MS_WINDOWED); // deactivate because we're returning to the menu + IN_UpdateGrabs(); return; } @@ -1622,7 +1629,7 @@ void M_Keys_Key (int k) if (keys[2] != -1) M_UnbindCommand (bindnames[keys_cursor].cmd); bind_grab = true; - IN_Activate(); // activate to allow mouse key binding + IN_UpdateGrabs(); // activate to allow mouse key binding break; case K_BACKSPACE: // delete bindings @@ -1662,11 +1669,11 @@ int help_page; void M_Menu_Help_f (void) { - IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_menu; m_state = m_help; m_entersound = true; help_page = 0; + IN_UpdateGrabs(); } @@ -1715,12 +1722,13 @@ void M_Menu_Quit_f (void) if (m_state == m_quit) return; wasInMenus = (key_dest == key_menu); - IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_menu; m_quit_prevstate = m_state; m_state = m_quit; m_entersound = true; msgNumber = rand()&7; + + IN_UpdateGrabs(); } @@ -1735,9 +1743,9 @@ void M_Quit_Key (int key) } else { - IN_Activate(); key_dest = key_game; m_state = m_none; + IN_UpdateGrabs(); } } } @@ -1756,17 +1764,17 @@ void M_Quit_Char (int key) } else { - IN_Activate(); key_dest = key_game; m_state = m_none; + IN_UpdateGrabs(); } break; case 'y': case 'Y': - IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_console; Host_Quit_f (); + IN_UpdateGrabs(); break; default: @@ -1825,7 +1833,6 @@ char lanConfig_joinname[22]; void M_Menu_LanConfig_f (void) { - IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_menu; m_state = m_lanconfig; m_entersound = true; @@ -1843,6 +1850,7 @@ void M_Menu_LanConfig_f (void) m_return_onerror = false; m_return_reason[0] = 0; + IN_UpdateGrabs(); } @@ -1969,9 +1977,9 @@ void M_LanConfig_Key (int key) { m_return_state = m_state; m_return_onerror = true; - IN_Activate(); key_dest = key_game; m_state = m_none; + IN_UpdateGrabs(); Cbuf_AddText ( va ("connect \"%s\"\n", lanConfig_joinname) ); } } @@ -2200,9 +2208,9 @@ double m_serverInfoMessageTime; void M_Menu_GameOptions_f (void) { - IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_menu; m_state = m_gameoptions; + IN_UpdateGrabs(); m_entersound = true; if (maxplayers == 0) maxplayers = svs.maxclients; @@ -2531,9 +2539,9 @@ enum slistScope_e searchLastScope = SLIST_LAN; void M_Menu_Search_f (enum slistScope_e scope) { - IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_menu; m_state = m_search; + IN_UpdateGrabs(); m_entersound = false; slistSilent = true; slistScope = searchLastScope = scope; @@ -2593,9 +2601,9 @@ qboolean slist_sorted; void M_Menu_ServerList_f (void) { - IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_menu; m_state = m_slist; + IN_UpdateGrabs(); m_entersound = true; slist_cursor = 0; slist_first = 0; @@ -2671,9 +2679,9 @@ void M_ServerList_Key (int k) m_return_state = m_state; m_return_onerror = true; slist_sorted = false; - IN_Activate(); key_dest = key_game; m_state = m_none; + IN_UpdateGrabs(); Cbuf_AddText ( va ("connect \"%s\"\n", NET_SlistPrintServerName(slist_cursor)) ); break; diff --git a/Quake/net_dgrm.c b/Quake/net_dgrm.c index 84c7e31f..f18188e3 100644 --- a/Quake/net_dgrm.c +++ b/Quake/net_dgrm.c @@ -2244,10 +2244,11 @@ ErrorReturn2: dfunc.Close_Socket(newsock); if (m_return_onerror) { - IN_Deactivate(modestate == MS_WINDOWED); key_dest = key_menu; m_state = m_return_state; m_return_onerror = false; + + IN_UpdateGrabs(); } return NULL; } diff --git a/Quake/pr_cmds.c b/Quake/pr_cmds.c index 7cc591e6..4700b6b1 100644 --- a/Quake/pr_cmds.c +++ b/Quake/pr_cmds.c @@ -32,7 +32,7 @@ char *PR_GetTempString (void) return pr_string_temp[(STRINGTEMP_BUFFERS-1) & ++pr_string_tempindex]; } -#define RETURN_EDICT(e) (((int *)pr_globals)[OFS_RETURN] = EDICT_TO_PROG(e)) +#define RETURN_EDICT(e) (((int *)qcvm->globals)[OFS_RETURN] = EDICT_TO_PROG(e)) #define MSG_BROADCAST 0 // unreliable to all #define MSG_ONE 1 // reliable to one (msg_entity) @@ -57,7 +57,7 @@ char *PF_VarString (int first) out[0] = 0; s = 0; - for (i = first; i < pr_argc; i++) + for (i = first; i < qcvm->argc; i++) { s = q_strlcat(out, G_STRING((OFS_PARM0+i*3)), sizeof(out)); if (s >= sizeof(out)) @@ -95,7 +95,7 @@ static void PF_error (void) s = PF_VarString(0); Con_Printf ("======SERVER ERROR in %s:\n%s\n", - PR_GetString(pr_xfunction->s_name), s); + PR_GetString(qcvm->xfunction->s_name), s); ed = PROG_TO_EDICT(pr_global_struct->self); ED_Print (ed); @@ -119,7 +119,7 @@ static void PF_objerror (void) s = PF_VarString(0); Con_Printf ("======OBJECT ERROR in %s:\n%s\n", - PR_GetString(pr_xfunction->s_name), s); + PR_GetString(qcvm->xfunction->s_name), s); ed = PROG_TO_EDICT(pr_global_struct->self); ED_Print (ed); ED_Free (ed); @@ -168,7 +168,7 @@ static void PF_setorigin (void) } -static void SetMinMaxSize (edict_t *e, float *minvec, float *maxvec, qboolean rotate) +void SetMinMaxSize (edict_t *e, float *minvec, float *maxvec, qboolean rotate) { float *angles; vec3_t rmin, rmax; @@ -270,7 +270,8 @@ PF_setmodel setmodel(entity, model) ================= */ -static void PF_setmodel (void) +cvar_t sv_gameplayfix_setmodelrealbox = {"sv_gameplayfix_setmodelrealbox", "1"}; +static void PF_sv_setmodel (void) { int i; const char *m, **check; @@ -311,8 +312,38 @@ static void PF_setmodel (void) if (mod) //johnfitz -- correct physics cullboxes for bmodels + /* Spike -- THIS IS A HUGE CLUSTERFUCK. + the mins/maxs sizes of models in vanilla was always set to xyz -16/+16. + which causes issues with clientside culling. + many engines fixed that, but not here. + which means that setmodel-without-setsize is now fucked. + the qc will usually do a setsize after setmodel anyway, so applying that fix here will do nothing. + you'd need to apply the serverside version of the cull fix in SV_LinkEdict instead, which is where the pvs is calculated. + tracebox is limited to specific hull sizes. the traces are biased such that they're aligned to the mins point of the box, rather than the center of the trace. + so vanilla's '-16 -16 -16'/'16 16 16' is wrong for Z (which is usually corrected for with gravity anyway), but X+Y will be correctly aligned for the resulting hull. + but traceboxes using models with -12 or -20 or whatever will be biased/offcenter in the X+Y axis (as well as Z, but that's still mostly unimportant) + deciding whether to replicate the vanilla behaviour based upon model type sucks. + + vanilla: + brush - always the models size + mdl - always [-16, -16, -16], [16, 16, 16] + quakespasm: + brush - always the models size + mdl - always the models size + quakeworld: + *.bsp - always the models size (matched by extension rather than type) + other - model isn't even loaded, setmodel does not do setsize at all. + fte default (with nq mod): + *.bsp (or sv_gameplayfix_setmodelrealbox) - always the models size (matched by extension rather than type) + other - always [-16, -16, -16], [16, 16, 16] + + fte's behaviour means: + a) dedicated servers don't have to bother loading non-mdls. + b) nq mods still work fine, where extensions are adhered to in the original qc. + c) when replacement models are used for bsp models, things still work without them reverting to +/- 16. + */ { - if (mod->type == mod_brush) + if (mod->type == mod_brush || !sv_gameplayfix_setmodelrealbox.value) SetMinMaxSize (e, mod->clipmins, mod->clipmaxs, true); else SetMinMaxSize (e, mod->mins, mod->maxs, true); @@ -566,7 +597,7 @@ PF_ambientsound ================= */ -static void PF_ambientsound (void) +static void PF_sv_ambientsound (void) { const char *samp, **check; float *pos; @@ -716,7 +747,7 @@ static void PF_traceline (void) if (trace.ent) pr_global_struct->trace_ent = EDICT_TO_PROG(trace.ent); else - pr_global_struct->trace_ent = EDICT_TO_PROG(sv.edicts); + pr_global_struct->trace_ent = EDICT_TO_PROG(qcvm->edicts); } /* @@ -784,10 +815,10 @@ static int PF_newcheckclient (int check) // get the PVS for the entity VectorAdd (ent->v.origin, ent->v.view_ofs, org); - leaf = Mod_PointInLeaf (org, sv.worldmodel); - pvs = Mod_LeafPVS (leaf, sv.worldmodel); + leaf = Mod_PointInLeaf (org, qcvm->worldmodel); + pvs = Mod_LeafPVS (leaf, qcvm->worldmodel); - pvsbytes = (sv.worldmodel->numleafs+7)>>3; + pvsbytes = (qcvm->worldmodel->numleafs+7)>>3; if (checkpvs == NULL || pvsbytes > checkpvs_capacity) { checkpvs_capacity = pvsbytes; @@ -817,7 +848,7 @@ name checkclient () */ #define MAX_CHECK 16 static int c_invis, c_notvis; -static void PF_checkclient (void) +static void PF_sv_checkclient (void) { edict_t *ent, *self; mleaf_t *leaf; @@ -825,29 +856,29 @@ static void PF_checkclient (void) vec3_t view; // find a new check if on a new frame - if (sv.time - sv.lastchecktime >= 0.1) + if (qcvm->time - sv.lastchecktime >= 0.1) { sv.lastcheck = PF_newcheckclient (sv.lastcheck); - sv.lastchecktime = sv.time; + sv.lastchecktime = qcvm->time; } // return check if it might be visible ent = EDICT_NUM(sv.lastcheck); if (ent->free || ent->v.health <= 0) { - RETURN_EDICT(sv.edicts); + RETURN_EDICT(qcvm->edicts); return; } // if current entity can't possibly see the check entity, return 0 self = PROG_TO_EDICT(pr_global_struct->self); VectorAdd (self->v.origin, self->v.view_ofs, view); - leaf = Mod_PointInLeaf (view, sv.worldmodel); - l = (leaf - sv.worldmodel->leafs) - 1; + leaf = Mod_PointInLeaf (view, qcvm->worldmodel); + l = (leaf - qcvm->worldmodel->leafs) - 1; if ( (l < 0) || !(checkpvs[l>>3] & (1 << (l & 7))) ) { c_notvis++; - RETURN_EDICT(sv.edicts); + RETURN_EDICT(qcvm->edicts); return; } @@ -952,13 +983,13 @@ static void PF_findradius (void) vec3_t eorg; int i, j; - chain = (edict_t *)sv.edicts; + chain = (edict_t *)qcvm->edicts; org = G_VECTOR(OFS_PARM0); rad = G_FLOAT(OFS_PARM1); - ent = NEXT_EDICT(sv.edicts); - for (i = 1; i < sv.num_edicts; i++, ent = NEXT_EDICT(ent)) + ent = NEXT_EDICT(qcvm->edicts); + for (i = 1; i < qcvm->num_edicts; i++, ent = NEXT_EDICT(ent)) { if (ent->free) continue; @@ -1048,7 +1079,7 @@ static void PF_Find (void) if (!s) PR_RunError ("PF_Find: bad search string"); - for (e++ ; e < sv.num_edicts ; e++) + for (e++ ; e < qcvm->num_edicts ; e++) { ed = EDICT_NUM(e); if (ed->free) @@ -1063,7 +1094,7 @@ static void PF_Find (void) } } - RETURN_EDICT(sv.edicts); + RETURN_EDICT(qcvm->edicts); } static void PR_CheckEmptyString (const char *s) @@ -1106,7 +1137,7 @@ int SV_Precache_Sound(const char *s) return 0; } -static void PF_precache_sound (void) +static void PF_sv_precache_sound (void) { const char *s; @@ -1143,7 +1174,7 @@ int SV_Precache_Model(const char *s) return 0; } -static void PF_precache_model (void) +static void PF_sv_precache_model (void) { const char *s; int i; @@ -1187,12 +1218,12 @@ static void PF_coredump (void) static void PF_traceon (void) { - pr_trace = true; + qcvm->trace = true; } static void PF_traceoff (void) { - pr_trace = false; + qcvm->trace = false; } static void PF_eprint (void) @@ -1232,14 +1263,14 @@ static void PF_walkmove (void) move[2] = 0; // save program state, because SV_movestep may call other progs - oldf = pr_xfunction; + oldf = qcvm->xfunction; oldself = pr_global_struct->self; G_FLOAT(OFS_RETURN) = SV_movestep(ent, move, true); // restore program state - pr_xfunction = oldf; + qcvm->xfunction = oldf; pr_global_struct->self = oldself; } @@ -1282,7 +1313,7 @@ PF_lightstyle void(float style, string value) lightstyle =============== */ -static void PF_lightstyle (void) +static void PF_sv_lightstyle (void) { int style; const char *val; @@ -1382,9 +1413,9 @@ static void PF_nextent (void) while (1) { i++; - if (i == sv.num_edicts) + if (i == qcvm->num_edicts) { - RETURN_EDICT(sv.edicts); + RETURN_EDICT(qcvm->edicts); return; } ent = EDICT_NUM(i); @@ -1437,8 +1468,8 @@ static void PF_aim (void) bestdist = sv_aim.value; bestent = NULL; - check = NEXT_EDICT(sv.edicts); - for (i = 1; i < sv.num_edicts; i++, check = NEXT_EDICT(check) ) + check = NEXT_EDICT(qcvm->edicts); + for (i = 1; i < qcvm->num_edicts; i++, check = NEXT_EDICT(check) ) { if (check->v.takedamage != DAMAGE_AIM) continue; @@ -1564,50 +1595,50 @@ sizebuf_t *WriteDest (void) return NULL; } -static void PF_WriteByte (void) +static void PF_sv_WriteByte (void) { MSG_WriteByte (WriteDest(), G_FLOAT(OFS_PARM1)); } -static void PF_WriteChar (void) +static void PF_sv_WriteChar (void) { MSG_WriteChar (WriteDest(), G_FLOAT(OFS_PARM1)); } -static void PF_WriteShort (void) +static void PF_sv_WriteShort (void) { MSG_WriteShort (WriteDest(), G_FLOAT(OFS_PARM1)); } -static void PF_WriteLong (void) +static void PF_sv_WriteLong (void) { MSG_WriteLong (WriteDest(), G_FLOAT(OFS_PARM1)); } -static void PF_WriteAngle (void) +static void PF_sv_WriteAngle (void) { MSG_WriteAngle (WriteDest(), G_FLOAT(OFS_PARM1), sv.protocolflags); } -static void PF_WriteCoord (void) +static void PF_sv_WriteCoord (void) { MSG_WriteCoord (WriteDest(), G_FLOAT(OFS_PARM1), sv.protocolflags); } -static void PF_WriteString (void) +static void PF_sv_WriteString (void) { MSG_WriteString (WriteDest(), G_STRING(OFS_PARM1)); } #define MSG_WriteEntity MSG_WriteShort //fixme - replacement deltas encodes 0x8000+ in 24 bits -static void PF_WriteEntity (void) +static void PF_sv_WriteEntity (void) { MSG_WriteEntity (WriteDest(), G_EDICTNUM(OFS_PARM1)); } //============================================================================= -static void PF_makestatic (void) +static void PF_sv_makestatic (void) { entity_state_t *st; edict_t *ent; @@ -1642,7 +1673,7 @@ static void PF_makestatic (void) PF_setspawnparms ============== */ -static void PF_setspawnparms (void) +static void PF_sv_setspawnparms (void) { edict_t *ent; int i; @@ -1665,7 +1696,7 @@ static void PF_setspawnparms (void) PF_changelevel ============== */ -static void PF_changelevel (void) +static void PF_sv_changelevel (void) { const char *s; @@ -1696,19 +1727,19 @@ void PR_spawnfunc_misc_model(edict_t *self) //make sure the model is precached, to avoid errors. G_INT(OFS_PARM0) = self->v.model; - PF_precache_model(); + PF_sv_precache_model(); //and lets just call makestatic instead of worrying if it'll interfere with the rest of the qc. G_INT(OFS_PARM0) = EDICT_TO_PROG(self); - PF_makestatic(); + PF_sv_makestatic(); } -static builtin_t pr_builtin[] = +builtin_t pr_ssqcbuiltins[] = { PF_Fixme, PF_makevectors, // void(entity e) makevectors = #1 PF_setorigin, // void(entity e, vector o) setorigin = #2 - PF_setmodel, // void(entity e, string m) setmodel = #3 + PF_sv_setmodel, // void(entity e, string m) setmodel = #3 PF_setsize, // void(entity e, vector min, vector max) setsize = #4 PF_Fixme, // void(entity e, vector min, vector max) setabssize = #5 PF_break, // void() break = #6 @@ -1722,10 +1753,10 @@ static builtin_t pr_builtin[] = PF_Spawn, // entity() spawn = #14 PF_Remove, // void(entity e) remove = #15 PF_traceline, // float(vector v1, vector v2, float tryents) traceline = #16 - PF_checkclient, // entity() clientlist = #17 + PF_sv_checkclient, // entity() clientlist = #17 PF_Find, // entity(entity start, .string fld, string match) find = #18 - PF_precache_sound, // void(string s) precache_sound = #19 - PF_precache_model, // void(string s) precache_model = #20 + PF_sv_precache_sound, // void(string s) precache_sound = #19 + PF_sv_precache_model, // void(string s) precache_model = #20 PF_stuffcmd, // void(entity client, string s)stuffcmd = #21 PF_findradius, // entity(vector org, float rad) findradius = #22 PF_bprint, // void(string s) bprint = #23 @@ -1740,7 +1771,7 @@ static builtin_t pr_builtin[] = PF_walkmove, // float(float yaw, float dist) walkmove PF_Fixme, // float(float yaw, float dist) walkmove PF_droptofloor, - PF_lightstyle, + PF_sv_lightstyle, PF_rint, PF_floor, PF_ceil, @@ -1758,14 +1789,14 @@ static builtin_t pr_builtin[] = PF_Fixme, PF_vectoangles, - PF_WriteByte, - PF_WriteChar, - PF_WriteShort, - PF_WriteLong, - PF_WriteCoord, - PF_WriteAngle, - PF_WriteString, - PF_WriteEntity, + PF_sv_WriteByte, + PF_sv_WriteChar, + PF_sv_WriteShort, + PF_sv_WriteLong, + PF_sv_WriteCoord, + PF_sv_WriteAngle, + PF_sv_WriteString, + PF_sv_WriteEntity, PF_Fixme, PF_Fixme, @@ -1777,23 +1808,114 @@ static builtin_t pr_builtin[] = SV_MoveToGoal, PF_precache_file, - PF_makestatic, + PF_sv_makestatic, - PF_changelevel, + PF_sv_changelevel, PF_Fixme, PF_cvar_set, PF_centerprint, - PF_ambientsound, + PF_sv_ambientsound, - PF_precache_model, - PF_precache_sound, // precache_sound2 is different only for qcc + PF_sv_precache_model, + PF_sv_precache_sound, // precache_sound2 is different only for qcc PF_precache_file, - PF_setspawnparms + PF_sv_setspawnparms }; +int pr_ssqcnumbuiltins = sizeof(pr_ssqcbuiltins)/sizeof(pr_ssqcbuiltins[0]); -builtin_t *pr_builtins = pr_builtin; -int pr_numbuiltins = sizeof(pr_builtin)/sizeof(pr_builtin[0]); +#define PF_NoCSQC PF_Fixme +#define PF_CSQCToDo PF_Fixme +builtin_t pr_csqcbuiltins[] = +{ + PF_Fixme, + PF_makevectors, // void(entity e) makevectors = #1 + PF_setorigin, // void(entity e, vector o) setorigin = #2 + PF_CSQCToDo,//PF_setmodel, // void(entity e, string m) setmodel = #3 + PF_setsize, // void(entity e, vector min, vector max) setsize = #4 + PF_Fixme, // void(entity e, vector min, vector max) setabssize = #5 + PF_break, // void() break = #6 + PF_random, // float() random = #7 + PF_sound, // void(entity e, float chan, string samp) sound = #8 + PF_normalize, // vector(vector v) normalize = #9 + PF_error, // void(string e) error = #10 + PF_objerror, // void(string e) objerror = #11 + PF_vlen, // float(vector v) vlen = #12 + PF_vectoyaw, // float(vector v) vectoyaw = #13 + PF_Spawn, // entity() spawn = #14 + PF_Remove, // void(entity e) remove = #15 + PF_traceline, // float(vector v1, vector v2, float tryents) traceline = #16 + PF_NoCSQC, // entity() checkclient (was: clientlist, apparently) = #17 + PF_Find, // entity(entity start, .string fld, string match) find = #18 + PF_CSQCToDo,//PF_cl_precache_sound, // void(string s) precache_sound = #19 + PF_CSQCToDo,//PF_cl_precache_model, // void(string s) precache_model = #20 + PF_NoCSQC, // void(entity client, string s)stuffcmd = #21 + PF_findradius, // entity(vector org, float rad) findradius = #22 + PF_NoCSQC, // void(string s) bprint = #23 + PF_NoCSQC, // void(entity client, string s) sprint = #24 + PF_dprint, // void(string s) dprint = #25 + PF_ftos, // void(string s) ftos = #26 + PF_vtos, // void(string s) vtos = #27 + PF_coredump, + PF_traceon, + PF_traceoff, + PF_eprint, // void(entity e) debug print an entire entity + PF_walkmove, // float(float yaw, float dist) walkmove + PF_Fixme, // float(float yaw, float dist) walkmove + PF_droptofloor, + PF_CSQCToDo,//PF_cl_lightstyle, + PF_rint, + PF_floor, + PF_ceil, + PF_Fixme, + PF_checkbottom, + PF_pointcontents, + PF_Fixme, + PF_fabs, + PF_NoCSQC,//PF_aim, + PF_cvar, + PF_localcmd, + PF_nextent, + PF_CSQCToDo,//PF_cl_particle, + PF_changeyaw, + PF_Fixme, + PF_vectoangles, + PF_NoCSQC,//PF_WriteByte, + PF_NoCSQC,//PF_WriteChar, + PF_NoCSQC,//PF_WriteShort, + PF_NoCSQC,//PF_WriteLong, + PF_NoCSQC,//PF_WriteCoord, + PF_NoCSQC,//PF_WriteAngle, + PF_NoCSQC,//PF_WriteString, + PF_NoCSQC,//PF_WriteEntity, + + PF_Fixme, + PF_Fixme, + PF_Fixme, + PF_Fixme, + PF_Fixme, + PF_Fixme, + PF_Fixme, + + SV_MoveToGoal, + PF_precache_file, + PF_CSQCToDo,//PF_cl_makestatic, + + PF_NoCSQC,//PF_changelevel, + PF_Fixme, + + PF_cvar_set, + PF_NoCSQC,//PF_centerprint, + + PF_CSQCToDo,//PF_ambientsound, + + PF_CSQCToDo,//PF_cl_precache_model, + PF_CSQCToDo,//PF_cl_precache_sound, // precache_sound2 is different only for qcc + PF_precache_file, + + PF_NoCSQC,//PF_setspawnparms +}; +int pr_csqcnumbuiltins = sizeof(pr_csqcbuiltins)/sizeof(pr_csqcbuiltins[0]); diff --git a/Quake/pr_edict.c b/Quake/pr_edict.c index 354a8cf1..8527fb6e 100644 --- a/Quake/pr_edict.c +++ b/Quake/pr_edict.c @@ -23,27 +23,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "quakedef.h" -struct pr_extfields_s pr_extfields; - -dprograms_t *progs; -dfunction_t *pr_functions; - -static char *pr_strings; -static int pr_stringssize; -static const char **pr_knownstrings; -static int pr_maxknownstrings; -static int pr_numknownstrings; -static int pr_freeknownstrings; -ddef_t *pr_fielddefs; -static ddef_t *pr_globaldefs; - -dstatement_t *pr_statements; -globalvars_t *pr_global_struct; -float *pr_globals; // same as pr_global_struct -int pr_edict_size; // in bytes - -unsigned short pr_crc; - int type_size[8] = { 1, // ev_void 1, // sizeof(string_t) / 4 // ev_string @@ -79,7 +58,7 @@ Sets everything to NULL */ void ED_ClearEdict (edict_t *e) { - memset (&e->v, 0, progs->entityfields * 4); + memset (&e->v, 0, qcvm->progs->entityfields * 4); e->free = false; } @@ -99,24 +78,24 @@ edict_t *ED_Alloc (void) int i; edict_t *e; - for (i = svs.maxclients + 1; i < sv.num_edicts; i++) + for (i = qcvm->reserved_edicts; i < qcvm->num_edicts; i++) { e = EDICT_NUM(i); // the first couple seconds of server time can involve a lot of // freeing and allocating, so relax the replacement policy - if (e->free && ( e->freetime < 2 || sv.time - e->freetime > 0.5 ) ) + if (e->free && ( e->freetime < 2 || qcvm->time - e->freetime > 0.5 ) ) { ED_ClearEdict (e); return e; } } - if (i == sv.max_edicts) //johnfitz -- use sv.max_edicts instead of MAX_EDICTS - Host_Error ("ED_Alloc: no free edicts (max_edicts is %i)", sv.max_edicts); + if (i == qcvm->max_edicts) //johnfitz -- use sv.max_edicts instead of MAX_EDICTS + Host_Error ("ED_Alloc: no free edicts (max_edicts is %i)", qcvm->max_edicts); - sv.num_edicts++; + qcvm->num_edicts++; e = EDICT_NUM(i); - memset(e, 0, pr_edict_size); // ericw -- switched sv.edicts to malloc(), so we are accessing uninitialized memory and must fully zero it, not just ED_ClearEdict + memset(e, 0, qcvm->edict_size); // ericw -- switched sv.edicts to malloc(), so we are accessing uninitialized memory and must fully zero it, not just ED_ClearEdict return e; } @@ -146,7 +125,7 @@ void ED_Free (edict_t *ed) ed->v.solid = 0; ed->alpha = ENTALPHA_DEFAULT; //johnfitz -- reset alpha for next entity - ed->freetime = sv.time; + ed->freetime = qcvm->time; } //=========================================================================== @@ -161,9 +140,9 @@ static ddef_t *ED_GlobalAtOfs (int ofs) ddef_t *def; int i; - for (i = 0; i < progs->numglobaldefs; i++) + for (i = 0; i < qcvm->progs->numglobaldefs; i++) { - def = &pr_globaldefs[i]; + def = &qcvm->globaldefs[i]; if (def->ofs == ofs) return def; } @@ -180,9 +159,9 @@ static ddef_t *ED_FieldAtOfs (int ofs) ddef_t *def; int i; - for (i = 0; i < progs->numfielddefs; i++) + for (i = 0; i < qcvm->progs->numfielddefs; i++) { - def = &pr_fielddefs[i]; + def = &qcvm->fielddefs[i]; if (def->ofs == ofs) return def; } @@ -199,9 +178,9 @@ ddef_t *ED_FindField (const char *name) ddef_t *def; int i; - for (i = 0; i < progs->numfielddefs; i++) + for (i = 0; i < qcvm->progs->numfielddefs; i++) { - def = &pr_fielddefs[i]; + def = &qcvm->fielddefs[i]; if ( !strcmp(PR_GetString(def->s_name), name) ) return def; } @@ -228,9 +207,9 @@ ddef_t *ED_FindGlobal (const char *name) ddef_t *def; int i; - for (i = 0; i < progs->numglobaldefs; i++) + for (i = 0; i < qcvm->progs->numglobaldefs; i++) { - def = &pr_globaldefs[i]; + def = &qcvm->globaldefs[i]; if ( !strcmp(PR_GetString(def->s_name), name) ) return def; } @@ -248,9 +227,9 @@ dfunction_t *ED_FindFunction (const char *fn_name) dfunction_t *func; int i; - for (i = 0; i < progs->numfunctions; i++) + for (i = 0; i < qcvm->progs->numfunctions; i++) { - func = &pr_functions[i]; + func = &qcvm->functions[i]; if ( !strcmp(PR_GetString(func->s_name), fn_name) ) return func; } @@ -296,7 +275,7 @@ static const char *PR_ValueString (int type, eval_t *val) sprintf (line, "entity %i", NUM_FOR_EDICT(PROG_TO_EDICT(val->edict)) ); break; case ev_function: - f = pr_functions + val->function; + f = qcvm->functions + val->function; sprintf (line, "%s()", PR_GetString(f->s_name)); break; case ev_field: @@ -352,7 +331,7 @@ const char *PR_UglyValueString (int type, eval_t *val) q_snprintf (line, sizeof(line), "%i", NUM_FOR_EDICT(PROG_TO_EDICT(val->edict))); break; case ev_function: - f = pr_functions + val->function; + f = qcvm->functions + val->function; q_snprintf (line, sizeof(line), "%s", PR_GetString(f->s_name)); break; case ev_field: @@ -395,7 +374,7 @@ const char *PR_GlobalString (int ofs) ddef_t *def; void *val; - val = (void *)&pr_globals[ofs]; + val = (void *)&qcvm->globals[ofs]; def = ED_GlobalAtOfs(ofs); if (!def) sprintf (line,"%i(?)", ofs); @@ -456,9 +435,9 @@ void ED_Print (edict_t *ed) } Con_SafePrintf("\nEDICT %i:\n", NUM_FOR_EDICT(ed)); //johnfitz -- was Con_Printf - for (i = 1; i < progs->numfielddefs; i++) + for (i = 1; i < qcvm->progs->numfielddefs; i++) { - d = &pr_fielddefs[i]; + d = &qcvm->fielddefs[i]; name = PR_GetString(d->s_name); l = strlen (name); if (l > 1 && name[l - 2] == '_') @@ -508,9 +487,9 @@ void ED_Write (FILE *f, edict_t *ed) return; } - for (i = 1; i < progs->numfielddefs; i++) + for (i = 1; i < qcvm->progs->numfielddefs; i++) { - d = &pr_fielddefs[i]; + d = &qcvm->fielddefs[i]; name = PR_GetString(d->s_name); j = strlen (name); if (j > 1 && name[j - 2] == '_') @@ -533,7 +512,7 @@ void ED_Write (FILE *f, edict_t *ed) } //johnfitz -- save entity alpha manually when progs.dat doesn't know about alpha - if (pr_extfields.alpha<0 && ed->alpha != ENTALPHA_DEFAULT) + if (qcvm->extfields.alpha<0 && ed->alpha != ENTALPHA_DEFAULT) fprintf (f, "\"alpha\" \"%f\"\n", ENTALPHA_TOSAVE(ed->alpha)); //johnfitz @@ -559,9 +538,11 @@ void ED_PrintEdicts (void) if (!sv.active) return; - Con_Printf ("%i entities\n", sv.num_edicts); - for (i = 0; i < sv.num_edicts; i++) + PR_SwitchQCVM(&sv.qcvm); + Con_Printf ("%i entities\n", qcvm->num_edicts); + for (i = 0; i < qcvm->num_edicts; i++) ED_PrintNum (i); + PR_SwitchQCVM(NULL); } /* @@ -579,12 +560,12 @@ static void ED_PrintEdict_f (void) return; i = Q_atoi (Cmd_Argv(1)); - if (i < 0 || i >= sv.num_edicts) - { + PR_SwitchQCVM(&sv.qcvm); + if (i < 0 || i >= qcvm->num_edicts) Con_Printf("Bad edict number\n"); - return; - } - ED_PrintNum (i); + else + ED_PrintNum (i); + PR_SwitchQCVM(NULL); } /* @@ -602,8 +583,9 @@ static void ED_Count (void) if (!sv.active) return; + PR_SwitchQCVM(&sv.qcvm); active = models = solid = step = 0; - for (i = 0; i < sv.num_edicts; i++) + for (i = 0; i < qcvm->num_edicts; i++) { ent = EDICT_NUM(i); if (ent->free) @@ -617,11 +599,12 @@ static void ED_Count (void) step++; } - Con_Printf ("num_edicts:%3i\n", sv.num_edicts); + Con_Printf ("num_edicts:%3i\n", qcvm->num_edicts); Con_Printf ("active :%3i\n", active); Con_Printf ("view :%3i\n", models); Con_Printf ("touch :%3i\n", solid); Con_Printf ("step :%3i\n", step); + PR_SwitchQCVM(NULL); } @@ -647,9 +630,9 @@ void ED_WriteGlobals (FILE *f) int type; fprintf (f, "{\n"); - for (i = 0; i < progs->numglobaldefs; i++) + for (i = 0; i < qcvm->progs->numglobaldefs; i++) { - def = &pr_globaldefs[i]; + def = &qcvm->globaldefs[i]; type = def->type; if ( !(def->type & DEF_SAVEGLOBAL) ) continue; @@ -660,7 +643,7 @@ void ED_WriteGlobals (FILE *f) name = PR_GetString(def->s_name); fprintf (f, "\"%s\" ", name); - fprintf (f, "\"%s\"\n", PR_UglyValueString(type, (eval_t *)&pr_globals[def->ofs])); + fprintf (f, "\"%s\"\n", PR_UglyValueString(type, (eval_t *)&qcvm->globals[def->ofs])); } fprintf (f, "}\n"); } @@ -701,7 +684,7 @@ const char *ED_ParseGlobals (const char *data) continue; } - if (!ED_ParseEpair ((void *)pr_globals, key, com_token)) + if (!ED_ParseEpair ((void *)qcvm->globals, key, com_token)) Host_Error ("ED_ParseGlobals: parse error"); } return data; @@ -824,7 +807,7 @@ qboolean ED_ParseEpair (void *base, ddef_t *key, const char *s) Con_Printf ("Can't find function %s\n", s); return false; } - *(func_t *)d = func - pr_functions; + *(func_t *)d = func - qcvm->functions; break; default: @@ -852,8 +835,8 @@ const char *ED_ParseEdict (const char *data, edict_t *ent) init = false; // clear it - if (ent != sv.edicts) // hack - memset (&ent->v, 0, progs->entityfields * 4); + if (ent != qcvm->edicts) // hack + memset (&ent->v, 0, qcvm->progs->entityfields * 4); // go through all the dictionary pairs while (1) @@ -938,12 +921,12 @@ const char *ED_ParseEdict (const char *data, edict_t *ent) eval_t *val; if (!strcmp(keyname, "traileffect") && sv.state == ss_loading) { - if ((val = GetEdictFieldValue(ent, pr_extfields.traileffectnum))) + if ((val = GetEdictFieldValue(ent, qcvm->extfields.traileffectnum))) val->_float = PF_SV_ForceParticlePrecache(com_token); } else if (!strcmp(keyname, "emiteffect") && sv.state == ss_loading) { - if ((val = GetEdictFieldValue(ent, pr_extfields.emiteffectnum))) + if ((val = GetEdictFieldValue(ent, qcvm->extfields.emiteffectnum))) val->_float = PF_SV_ForceParticlePrecache(com_token); } //johnfitz -- HACK -- suppress error becuase fog/sky/alpha fields might not be mentioned in defs.qc @@ -994,7 +977,7 @@ void ED_LoadFromFile (const char *data) int inhibit = 0; int usingspawnfunc = 0; - pr_global_struct->time = sv.time; + pr_global_struct->time = qcvm->time; // parse ents while (1) @@ -1068,142 +1051,190 @@ void ED_LoadFromFile (const char *data) } pr_global_struct->self = EDICT_TO_PROG(ent); - PR_ExecuteProgram (func - pr_functions); + PR_ExecuteProgram (func - qcvm->functions); } Con_DPrintf ("%i entities inhibited\n", inhibit); } +#ifndef PR_SwitchQCVM +qcvm_t *qcvm; +globalvars_t *pr_global_struct; +void PR_SwitchQCVM(qcvm_t *nvm) +{ + if (qcvm && nvm) + Sys_Error("PR_SwitchQCVM: A qcvm was already active"); + qcvm = nvm; + if (qcvm) + pr_global_struct = (globalvars_t*)qcvm->globals; + else + pr_global_struct = NULL; +} +#endif + +void PR_ClearProgs(qcvm_t *vm) +{ + qcvm_t *oldvm = qcvm; + if (!vm->progs) + return; //wasn't loaded. + qcvm = NULL; + PR_SwitchQCVM(vm); + PR_ShutdownExtensions(); + + if (qcvm->knownstrings) + Z_Free ((void *)qcvm->knownstrings); + free(qcvm->edicts); // ericw -- sv.edicts switched to use malloc() + memset(qcvm, 0, sizeof(*qcvm)); + + qcvm = NULL; + PR_SwitchQCVM(oldvm); +} + /* =============== PR_LoadProgs =============== */ -void PR_LoadProgs (void) +qboolean PR_LoadProgs (const char *filename, qboolean fatal, builtin_t *builtins, size_t numbuiltins) { int i; unsigned int u; - PR_ShutdownExtensions(); + PR_ClearProgs(qcvm); //just in case. - CRC_Init (&pr_crc); - - progs = (dprograms_t *)COM_LoadHunkFile ("progs.dat", NULL); - if (!progs) - Host_Error ("PR_LoadProgs: couldn't load progs.dat"); - Con_DPrintf ("Programs occupy %iK.\n", com_filesize/1024); + qcvm->progs = (dprograms_t *)COM_LoadHunkFile (filename, NULL); + if (!qcvm->progs) + return false; + CRC_Init (&qcvm->crc); for (i = 0; i < com_filesize; i++) - CRC_ProcessByte (&pr_crc, ((byte *)progs)[i]); + CRC_ProcessByte (&qcvm->crc, ((byte *)qcvm->progs)[i]); // byte swap the header - for (i = 0; i < (int) sizeof(*progs) / 4; i++) - ((int *)progs)[i] = LittleLong ( ((int *)progs)[i] ); + for (i = 0; i < (int) sizeof(*qcvm->progs) / 4; i++) + ((int *)qcvm->progs)[i] = LittleLong ( ((int *)qcvm->progs)[i] ); - if (progs->version != PROG_VERSION) - Host_Error ("progs.dat has wrong version number (%i should be %i)", progs->version, PROG_VERSION); - if (progs->crc != PROGHEADER_CRC) - Host_Error ("progs.dat system vars have been modified, progdefs.h is out of date"); + if (qcvm->progs->version != PROG_VERSION) + { + if (fatal) + Host_Error ("%s has wrong version number (%i should be %i)", filename, qcvm->progs->version, PROG_VERSION); + else + { + Con_Printf("%s ABI set not supported", filename); + qcvm->progs = NULL; + return false; + } + } + if (qcvm->progs->crc != PROGHEADER_CRC) + { + if (fatal) + Host_Error ("%s system vars have been modified, progdefs.h is out of date", filename); + else + { + Con_Printf("%s system vars are not supported", filename); + qcvm->progs = NULL; + return false; + } + } + Con_DPrintf ("%s occupies %iK.\n", filename, com_filesize/1024); - pr_functions = (dfunction_t *)((byte *)progs + progs->ofs_functions); - pr_strings = (char *)progs + progs->ofs_strings; - if (progs->ofs_strings + progs->numstrings >= com_filesize) - Host_Error ("progs.dat strings go past end of file\n"); + qcvm->functions = (dfunction_t *)((byte *)qcvm->progs + qcvm->progs->ofs_functions); + qcvm->strings = (char *)qcvm->progs + qcvm->progs->ofs_strings; + if (qcvm->progs->ofs_strings + qcvm->progs->numstrings >= com_filesize) + Host_Error ("%s strings go past end of file\n", filename); - // initialize the strings - pr_numknownstrings = 0; - pr_freeknownstrings = 0; - pr_maxknownstrings = 0; - pr_stringssize = progs->numstrings; - if (pr_knownstrings) - Z_Free ((void *)pr_knownstrings); - pr_knownstrings = NULL; - PR_SetEngineString(""); + qcvm->globaldefs = (ddef_t *)((byte *)qcvm->progs + qcvm->progs->ofs_globaldefs); + qcvm->fielddefs = (ddef_t *)((byte *)qcvm->progs + qcvm->progs->ofs_fielddefs); + qcvm->statements = (dstatement_t *)((byte *)qcvm->progs + qcvm->progs->ofs_statements); - pr_globaldefs = (ddef_t *)((byte *)progs + progs->ofs_globaldefs); - pr_fielddefs = (ddef_t *)((byte *)progs + progs->ofs_fielddefs); - pr_statements = (dstatement_t *)((byte *)progs + progs->ofs_statements); + qcvm->globals = (float *)((byte *)qcvm->progs + qcvm->progs->ofs_globals); + pr_global_struct = (globalvars_t*)qcvm->globals; - pr_global_struct = (globalvars_t *)((byte *)progs + progs->ofs_globals); - pr_globals = (float *)pr_global_struct; + qcvm->stringssize = qcvm->progs->numstrings; // byte swap the lumps - for (i = 0; i < progs->numstatements; i++) + for (i = 0; i < qcvm->progs->numstatements; i++) { - pr_statements[i].op = LittleShort(pr_statements[i].op); - pr_statements[i].a = LittleShort(pr_statements[i].a); - pr_statements[i].b = LittleShort(pr_statements[i].b); - pr_statements[i].c = LittleShort(pr_statements[i].c); + qcvm->statements[i].op = LittleShort(qcvm->statements[i].op); + qcvm->statements[i].a = LittleShort(qcvm->statements[i].a); + qcvm->statements[i].b = LittleShort(qcvm->statements[i].b); + qcvm->statements[i].c = LittleShort(qcvm->statements[i].c); } - for (i = 0; i < progs->numfunctions; i++) + for (i = 0; i < qcvm->progs->numfunctions; i++) { - pr_functions[i].first_statement = LittleLong (pr_functions[i].first_statement); - pr_functions[i].parm_start = LittleLong (pr_functions[i].parm_start); - pr_functions[i].s_name = LittleLong (pr_functions[i].s_name); - pr_functions[i].s_file = LittleLong (pr_functions[i].s_file); - pr_functions[i].numparms = LittleLong (pr_functions[i].numparms); - pr_functions[i].locals = LittleLong (pr_functions[i].locals); + qcvm->functions[i].first_statement = LittleLong (qcvm->functions[i].first_statement); + qcvm->functions[i].parm_start = LittleLong (qcvm->functions[i].parm_start); + qcvm->functions[i].s_name = LittleLong (qcvm->functions[i].s_name); + qcvm->functions[i].s_file = LittleLong (qcvm->functions[i].s_file); + qcvm->functions[i].numparms = LittleLong (qcvm->functions[i].numparms); + qcvm->functions[i].locals = LittleLong (qcvm->functions[i].locals); } - for (i = 0; i < progs->numglobaldefs; i++) + for (i = 0; i < qcvm->progs->numglobaldefs; i++) { - pr_globaldefs[i].type = LittleShort (pr_globaldefs[i].type); - pr_globaldefs[i].ofs = LittleShort (pr_globaldefs[i].ofs); - pr_globaldefs[i].s_name = LittleLong (pr_globaldefs[i].s_name); + qcvm->globaldefs[i].type = LittleShort (qcvm->globaldefs[i].type); + qcvm->globaldefs[i].ofs = LittleShort (qcvm->globaldefs[i].ofs); + qcvm->globaldefs[i].s_name = LittleLong (qcvm->globaldefs[i].s_name); } - for (u = 0; u < sizeof(pr_extfields)/sizeof(int); u++) - ((int*)&pr_extfields)[u] = -1; + for (u = 0; u < sizeof(qcvm->extfields)/sizeof(int); u++) + ((int*)&qcvm->extfields)[u] = -1; - for (i = 0; i < progs->numfielddefs; i++) + for (i = 0; i < qcvm->progs->numfielddefs; i++) { - pr_fielddefs[i].type = LittleShort (pr_fielddefs[i].type); - if (pr_fielddefs[i].type & DEF_SAVEGLOBAL) + qcvm->fielddefs[i].type = LittleShort (qcvm->fielddefs[i].type); + if (qcvm->fielddefs[i].type & DEF_SAVEGLOBAL) Host_Error ("PR_LoadProgs: pr_fielddefs[i].type & DEF_SAVEGLOBAL"); - pr_fielddefs[i].ofs = LittleShort (pr_fielddefs[i].ofs); - pr_fielddefs[i].s_name = LittleLong (pr_fielddefs[i].s_name); + qcvm->fielddefs[i].ofs = LittleShort (qcvm->fielddefs[i].ofs); + qcvm->fielddefs[i].s_name = LittleLong (qcvm->fielddefs[i].s_name); } - for (i = 0; i < progs->numglobals; i++) - ((int *)pr_globals)[i] = LittleLong (((int *)pr_globals)[i]); + for (i = 0; i < qcvm->progs->numglobals; i++) + ((int *)qcvm->globals)[i] = LittleLong (((int *)qcvm->globals)[i]); + + memcpy(qcvm->builtins, builtins, numbuiltins*sizeof(qcvm->builtins[0])); + qcvm->numbuiltins = numbuiltins; //spike: detect extended fields from progs - pr_extfields.items2 = ED_FindFieldOffset("items2"); - pr_extfields.gravity = ED_FindFieldOffset("gravity"); - pr_extfields.alpha = ED_FindFieldOffset("alpha"); - pr_extfields.movement = ED_FindFieldOffset("movement"); - pr_extfields.traileffectnum = ED_FindFieldOffset("traileffectnum"); - pr_extfields.emiteffectnum = ED_FindFieldOffset("emiteffectnum"); - pr_extfields.viewmodelforclient = ED_FindFieldOffset("viewmodelforclient"); - pr_extfields.scale = ED_FindFieldOffset("scale"); - pr_extfields.colormod = ED_FindFieldOffset("colormod"); - pr_extfields.tag_entity = ED_FindFieldOffset("tag_entity"); - pr_extfields.tag_index = ED_FindFieldOffset("tag_index"); - pr_extfields.button3 = ED_FindFieldOffset("button3"); - pr_extfields.button4 = ED_FindFieldOffset("button4"); - pr_extfields.button5 = ED_FindFieldOffset("button5"); - pr_extfields.button6 = ED_FindFieldOffset("button6"); - pr_extfields.button7 = ED_FindFieldOffset("button7"); - pr_extfields.button8 = ED_FindFieldOffset("button8"); - pr_extfields.viewzoom = ED_FindFieldOffset("viewzoom"); - pr_extfields.modelflags = ED_FindFieldOffset("modelflags"); + qcvm->extfields.items2 = ED_FindFieldOffset("items2"); + qcvm->extfields.gravity = ED_FindFieldOffset("gravity"); + qcvm->extfields.alpha = ED_FindFieldOffset("alpha"); + qcvm->extfields.movement = ED_FindFieldOffset("movement"); + qcvm->extfields.traileffectnum = ED_FindFieldOffset("traileffectnum"); + qcvm->extfields.emiteffectnum = ED_FindFieldOffset("emiteffectnum"); + qcvm->extfields.viewmodelforclient = ED_FindFieldOffset("viewmodelforclient"); + qcvm->extfields.scale = ED_FindFieldOffset("scale"); + qcvm->extfields.colormod = ED_FindFieldOffset("colormod"); + qcvm->extfields.tag_entity = ED_FindFieldOffset("tag_entity"); + qcvm->extfields.tag_index = ED_FindFieldOffset("tag_index"); + qcvm->extfields.button3 = ED_FindFieldOffset("button3"); + qcvm->extfields.button4 = ED_FindFieldOffset("button4"); + qcvm->extfields.button5 = ED_FindFieldOffset("button5"); + qcvm->extfields.button6 = ED_FindFieldOffset("button6"); + qcvm->extfields.button7 = ED_FindFieldOffset("button7"); + qcvm->extfields.button8 = ED_FindFieldOffset("button8"); + qcvm->extfields.viewzoom = ED_FindFieldOffset("viewzoom"); + qcvm->extfields.modelflags = ED_FindFieldOffset("modelflags"); - i = progs->entityfields; - if (pr_extfields.emiteffectnum < 0) - pr_extfields.emiteffectnum = i++; - if (pr_extfields.traileffectnum < 0) - pr_extfields.traileffectnum = i++; + i = qcvm->progs->entityfields; + if (qcvm->extfields.emiteffectnum < 0) + qcvm->extfields.emiteffectnum = i++; + if (qcvm->extfields.traileffectnum < 0) + qcvm->extfields.traileffectnum = i++; - pr_edict_size = i * 4 + sizeof(edict_t) - sizeof(entvars_t); + qcvm->edict_size = i * 4 + sizeof(edict_t) - sizeof(entvars_t); // round off to next highest whole word address (esp for Alpha) // this ensures that pointers in the engine data area are always // properly aligned - pr_edict_size += sizeof(void *) - 1; - pr_edict_size &= ~(sizeof(void *) - 1); + qcvm->edict_size += sizeof(void *) - 1; + qcvm->edict_size &= ~(sizeof(void *) - 1); - PR_EnableExtensions(pr_globaldefs); + PR_SetEngineString(""); + PR_EnableExtensions(qcvm->globaldefs); + + return true; } @@ -1230,24 +1261,26 @@ void PR_Init (void) Cvar_RegisterVariable (&saved2); Cvar_RegisterVariable (&saved3); Cvar_RegisterVariable (&saved4); + + PR_InitExtensions(); } edict_t *EDICT_NUM(int n) { - if (n < 0 || n >= sv.max_edicts) + if (n < 0 || n >= qcvm->max_edicts) Host_Error ("EDICT_NUM: bad number %i", n); - return (edict_t *)((byte *)sv.edicts + (n)*pr_edict_size); + return (edict_t *)((byte *)qcvm->edicts + (n)*qcvm->edict_size); } int NUM_FOR_EDICT(edict_t *e) { int b; - b = (byte *)e - (byte *)sv.edicts; - b = b / pr_edict_size; + b = (byte *)e - (byte *)qcvm->edicts; + b = b / qcvm->edict_size; - if (b < 0 || b >= sv.num_edicts) + if (b < 0 || b >= qcvm->num_edicts) Host_Error ("NUM_FOR_EDICT: bad pointer"); return b; } @@ -1259,27 +1292,27 @@ int NUM_FOR_EDICT(edict_t *e) static void PR_AllocStringSlots (void) { - pr_maxknownstrings += PR_STRING_ALLOCSLOTS; - Con_DPrintf2("PR_AllocStringSlots: realloc'ing for %d slots\n", pr_maxknownstrings); - pr_knownstrings = (const char **) Z_Realloc ((void *)pr_knownstrings, pr_maxknownstrings * sizeof(char *)); + qcvm->maxknownstrings += PR_STRING_ALLOCSLOTS; + Con_DPrintf2("PR_AllocStringSlots: realloc'ing for %d slots\n", qcvm->maxknownstrings); + qcvm->knownstrings = (const char **) Z_Realloc ((void *)qcvm->knownstrings, qcvm->maxknownstrings * sizeof(char *)); } const char *PR_GetString (int num) { - if (num >= 0 && num < pr_stringssize) - return pr_strings + num; - else if (num < 0 && num >= -pr_numknownstrings) + if (num >= 0 && num < qcvm->stringssize) + return qcvm->strings + num; + else if (num < 0 && num >= -qcvm->numknownstrings) { - if (!pr_knownstrings[-1 - num]) + if (!qcvm->knownstrings[-1 - num]) { Host_Error ("PR_GetString: attempt to get a non-existant string %d\n", num); return ""; } - return pr_knownstrings[-1 - num]; + return qcvm->knownstrings[-1 - num]; } else { - return pr_strings; + return qcvm->strings; Host_Error("PR_GetString: invalid string offset %d\n", num); return ""; } @@ -1287,12 +1320,12 @@ const char *PR_GetString (int num) void PR_ClearEngineString(int num) { - if (num < 0 && num >= -pr_numknownstrings) + if (num < 0 && num >= -qcvm->numknownstrings) { num = -1 - num; - pr_knownstrings[num] = NULL; - if (pr_freeknownstrings > num) - pr_freeknownstrings = num; + qcvm->knownstrings[num] = NULL; + if (qcvm->freeknownstrings > num) + qcvm->freeknownstrings = num; } } @@ -1303,36 +1336,36 @@ int PR_SetEngineString (const char *s) if (!s) return 0; #if 0 /* can't: sv.model_precache & sv.sound_precache points to pr_strings */ - if (s >= pr_strings && s <= pr_strings + pr_stringssize) + if (s >= qcvm->strings && s <= qcvm->strings + qcvm->stringssize) Host_Error("PR_SetEngineString: \"%s\" in pr_strings area\n", s); #else - if (s >= pr_strings && s <= pr_strings + pr_stringssize - 2) - return (int)(s - pr_strings); + if (s >= qcvm->strings && s <= qcvm->strings + qcvm->stringssize - 2) + return (int)(s - qcvm->strings); #endif - for (i = 0; i < pr_numknownstrings; i++) + for (i = 0; i < qcvm->numknownstrings; i++) { - if (pr_knownstrings[i] == s) + if (qcvm->knownstrings[i] == s) return -1 - i; } // new unknown engine string //Con_DPrintf ("PR_SetEngineString: new engine string %p\n", s); - for (i = pr_freeknownstrings; ; i++) + for (i = qcvm->freeknownstrings; ; i++) { - if (i < pr_numknownstrings) + if (i < qcvm->numknownstrings) { - if (pr_knownstrings[i]) + if (qcvm->knownstrings[i]) continue; } else { - if (i >= pr_maxknownstrings) + if (i >= qcvm->maxknownstrings) PR_AllocStringSlots(); - pr_numknownstrings++; + qcvm->numknownstrings++; } break; } - pr_freeknownstrings = i+1; - pr_knownstrings[i] = s; + qcvm->freeknownstrings = i+1; + qcvm->knownstrings[i] = s; return -1 - i; } @@ -1342,20 +1375,20 @@ int PR_AllocString (int size, char **ptr) if (!size) return 0; - for (i = 0; i < pr_numknownstrings; i++) + for (i = 0; i < qcvm->numknownstrings; i++) { - if (!pr_knownstrings[i]) + if (!qcvm->knownstrings[i]) break; } // if (i >= pr_numknownstrings) // { - if (i >= pr_maxknownstrings) + if (i >= qcvm->maxknownstrings) PR_AllocStringSlots(); - pr_numknownstrings++; + qcvm->numknownstrings++; // } - pr_knownstrings[i] = (char *)Hunk_AllocName(size, "string"); + qcvm->knownstrings[i] = (char *)Hunk_AllocName(size, "string"); if (ptr) - *ptr = (char *) pr_knownstrings[i]; + *ptr = (char *) qcvm->knownstrings[i]; return -1 - i; } diff --git a/Quake/pr_exec.c b/Quake/pr_exec.c index 540c579f..c2d2ccbc 100644 --- a/Quake/pr_exec.c +++ b/Quake/pr_exec.c @@ -21,25 +21,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "quakedef.h" -typedef struct -{ - int s; - dfunction_t *f; -} prstack_t; - -#define MAX_STACK_DEPTH 64 /* was 32 */ -static prstack_t pr_stack[MAX_STACK_DEPTH]; -static int pr_depth; - -#define LOCALSTACK_SIZE 2048 -static int localstack[LOCALSTACK_SIZE]; -static int localstack_used; - -qboolean pr_trace; -dfunction_t *pr_xfunction; -int pr_xstatement; -int pr_argc; - static const char *pr_opnames[] = { "DONE", @@ -185,16 +166,16 @@ static void PR_StackTrace (void) int i; dfunction_t *f; - if (pr_depth == 0) + if (qcvm->depth == 0) { Con_Printf("\n"); return; } - pr_stack[pr_depth].f = pr_xfunction; - for (i = pr_depth; i >= 0; i--) + qcvm->stack[qcvm->depth].f = qcvm->xfunction; + for (i = qcvm->depth; i >= 0; i--) { - f = pr_stack[i].f; + f = qcvm->stack[i].f; if (!f) { Con_Printf("\n"); @@ -222,14 +203,16 @@ void PR_Profile_f (void) if (!sv.active) return; + PR_SwitchQCVM(&sv.qcvm); + num = 0; do { pmax = 0; best = NULL; - for (i = 0; i < progs->numfunctions; i++) + for (i = 0; i < qcvm->progs->numfunctions; i++) { - f = &pr_functions[i]; + f = &qcvm->functions[i]; if (f->profile > pmax) { pmax = f->profile; @@ -244,6 +227,8 @@ void PR_Profile_f (void) best->profile = 0; } } while (best); + + PR_SwitchQCVM(NULL); } @@ -263,12 +248,12 @@ void PR_RunError (const char *error, ...) q_vsnprintf (string, sizeof(string), error, argptr); va_end (argptr); - PR_PrintStatement(pr_statements + pr_xstatement); + PR_PrintStatement(qcvm->statements + qcvm->xstatement); PR_StackTrace(); Con_Printf("%s\n", string); - pr_depth = 0; // dump the stack so host_error can shutdown functions + qcvm->depth = 0; // dump the stack so host_error can shutdown functions Host_Error("Program error"); } @@ -284,20 +269,20 @@ static int PR_EnterFunction (dfunction_t *f) { int i, j, c, o; - pr_stack[pr_depth].s = pr_xstatement; - pr_stack[pr_depth].f = pr_xfunction; - pr_depth++; - if (pr_depth >= MAX_STACK_DEPTH) + qcvm->stack[qcvm->depth].s = qcvm->xstatement; + qcvm->stack[qcvm->depth].f = qcvm->xfunction; + qcvm->depth++; + if (qcvm->depth >= MAX_STACK_DEPTH) PR_RunError("stack overflow"); // save off any locals that the new function steps on c = f->locals; - if (localstack_used + c > LOCALSTACK_SIZE) + if (qcvm->localstack_used + c > LOCALSTACK_SIZE) PR_RunError("PR_ExecuteProgram: locals stack overflow\n"); for (i = 0; i < c ; i++) - localstack[localstack_used + i] = ((int *)pr_globals)[f->parm_start + i]; - localstack_used += c; + qcvm->localstack[qcvm->localstack_used + i] = ((int *)qcvm->globals)[f->parm_start + i]; + qcvm->localstack_used += c; // copy parameters o = f->parm_start; @@ -305,12 +290,12 @@ static int PR_EnterFunction (dfunction_t *f) { for (j = 0; j < f->parm_size[i]; j++) { - ((int *)pr_globals)[o] = ((int *)pr_globals)[OFS_PARM0 + i*3 + j]; + ((int *)qcvm->globals)[o] = ((int *)qcvm->globals)[OFS_PARM0 + i*3 + j]; o++; } } - pr_xfunction = f; + qcvm->xfunction = f; return f->first_statement - 1; // offset the s++ } @@ -323,22 +308,22 @@ static int PR_LeaveFunction (void) { int i, c; - if (pr_depth <= 0) + if (qcvm->depth <= 0) Host_Error("prog stack underflow"); // Restore locals from the stack - c = pr_xfunction->locals; - localstack_used -= c; - if (localstack_used < 0) + c = qcvm->xfunction->locals; + qcvm->localstack_used -= c; + if (qcvm->localstack_used < 0) PR_RunError("PR_ExecuteProgram: locals stack underflow"); for (i = 0; i < c; i++) - ((int *)pr_globals)[pr_xfunction->parm_start + i] = localstack[localstack_used + i]; + ((int *)qcvm->globals)[qcvm->xfunction->parm_start + i] = qcvm->localstack[qcvm->localstack_used + i]; // up stack - pr_depth--; - pr_xfunction = pr_stack[pr_depth].f; - return pr_stack[pr_depth].s; + qcvm->depth--; + qcvm->xfunction = qcvm->stack[qcvm->depth].f; + return qcvm->stack[qcvm->depth].s; } @@ -349,9 +334,9 @@ PR_ExecuteProgram The interpretation main loop ==================== */ -#define OPA ((eval_t *)&pr_globals[(unsigned short)st->a]) -#define OPB ((eval_t *)&pr_globals[(unsigned short)st->b]) -#define OPC ((eval_t *)&pr_globals[(unsigned short)st->c]) +#define OPA ((eval_t *)&qcvm->globals[(unsigned short)st->a]) +#define OPB ((eval_t *)&qcvm->globals[(unsigned short)st->b]) +#define OPC ((eval_t *)&qcvm->globals[(unsigned short)st->c]) void PR_ExecuteProgram (func_t fnum) { @@ -362,23 +347,23 @@ void PR_ExecuteProgram (func_t fnum) edict_t *ed; int exitdepth; - if (!fnum || fnum >= progs->numfunctions) + if (!fnum || fnum >= qcvm->progs->numfunctions) { if (pr_global_struct->self) ED_Print (PROG_TO_EDICT(pr_global_struct->self)); Host_Error ("PR_ExecuteProgram: NULL function"); } - f = &pr_functions[fnum]; + f = &qcvm->functions[fnum]; //FIXME: if this is a builtin, then we're going to crash. - pr_trace = false; + qcvm->trace = false; // make a stack frame - exitdepth = pr_depth; + exitdepth = qcvm->depth; - st = &pr_statements[PR_EnterFunction(f)]; + st = &qcvm->statements[PR_EnterFunction(f)]; startprofile = profile = 0; while (1) @@ -387,11 +372,11 @@ void PR_ExecuteProgram (func_t fnum) if (++profile > 10000000) //spike -- was 100000 { - pr_xstatement = st - pr_statements; + qcvm->xstatement = st - qcvm->statements; PR_RunError("runaway loop error"); } - if (pr_trace) + if (qcvm->trace) PR_PrintStatement(st); switch (st->op) @@ -477,7 +462,7 @@ void PR_ExecuteProgram (func_t fnum) OPC->_float = !OPA->function; break; case OP_NOT_ENT: - OPC->_float = (PROG_TO_EDICT(OPA->edict) == sv.edicts); + OPC->_float = (PROG_TO_EDICT(OPA->edict) == qcvm->edicts); break; case OP_EQ_F: @@ -534,11 +519,11 @@ void PR_ExecuteProgram (func_t fnum) case OP_STOREP_FLD: // integers case OP_STOREP_S: case OP_STOREP_FNC: // pointers - ptr = (eval_t *)((byte *)sv.edicts + OPB->_int); + ptr = (eval_t *)((byte *)qcvm->edicts + OPB->_int); ptr->_int = OPA->_int; break; case OP_STOREP_V: - ptr = (eval_t *)((byte *)sv.edicts + OPB->_int); + ptr = (eval_t *)((byte *)qcvm->edicts + OPB->_int); ptr->vector[0] = OPA->vector[0]; ptr->vector[1] = OPA->vector[1]; ptr->vector[2] = OPA->vector[2]; @@ -549,12 +534,12 @@ void PR_ExecuteProgram (func_t fnum) #ifdef PARANOID NUM_FOR_EDICT(ed); // Make sure it's in range #endif - if (ed == (edict_t *)sv.edicts && sv.state == ss_active) + if (ed == (edict_t *)qcvm->edicts && sv.state == ss_active) { - pr_xstatement = st - pr_statements; + qcvm->xstatement = st - qcvm->statements; PR_RunError("assignment to world entity"); } - OPC->_int = (byte *)((int *)&ed->v + OPB->_int) - (byte *)sv.edicts; + OPC->_int = (byte *)((int *)&ed->v + OPB->_int) - (byte *)qcvm->edicts; break; case OP_LOAD_F: @@ -603,35 +588,35 @@ void PR_ExecuteProgram (func_t fnum) case OP_CALL6: case OP_CALL7: case OP_CALL8: - pr_xfunction->profile += profile - startprofile; + qcvm->xfunction->profile += profile - startprofile; startprofile = profile; - pr_xstatement = st - pr_statements; - pr_argc = st->op - OP_CALL0; + qcvm->xstatement = st - qcvm->statements; + qcvm->argc = st->op - OP_CALL0; if (!OPA->function) PR_RunError("NULL function"); - newf = &pr_functions[OPA->function]; + newf = &qcvm->functions[OPA->function]; if (newf->first_statement < 0) { // Built-in function int i = -newf->first_statement; - if (i >= pr_numbuiltins) + if (i >= qcvm->numbuiltins) i = 0; //just invoke the fixme builtin. - pr_builtins[i](); + qcvm->builtins[i](); break; } // Normal function - st = &pr_statements[PR_EnterFunction(newf)]; + st = &qcvm->statements[PR_EnterFunction(newf)]; break; case OP_DONE: case OP_RETURN: - pr_xfunction->profile += profile - startprofile; + qcvm->xfunction->profile += profile - startprofile; startprofile = profile; - pr_xstatement = st - pr_statements; - pr_globals[OFS_RETURN] = pr_globals[(unsigned short)st->a]; - pr_globals[OFS_RETURN + 1] = pr_globals[(unsigned short)st->a + 1]; - pr_globals[OFS_RETURN + 2] = pr_globals[(unsigned short)st->a + 2]; - st = &pr_statements[PR_LeaveFunction()]; - if (pr_depth == exitdepth) + qcvm->xstatement = st - qcvm->statements; + qcvm->globals[OFS_RETURN] = qcvm->globals[(unsigned short)st->a]; + qcvm->globals[OFS_RETURN + 1] = qcvm->globals[(unsigned short)st->a + 1]; + qcvm->globals[OFS_RETURN + 2] = qcvm->globals[(unsigned short)st->a + 2]; + st = &qcvm->statements[PR_LeaveFunction()]; + if (qcvm->depth == exitdepth) { // Done return; } @@ -645,7 +630,7 @@ void PR_ExecuteProgram (func_t fnum) break; default: - pr_xstatement = st - pr_statements; + qcvm->xstatement = st - qcvm->statements; PR_RunError("Bad opcode %i", st->op); } } /* end of while(1) loop */ diff --git a/Quake/pr_ext.c b/Quake/pr_ext.c index 916530db..9ff39743 100644 --- a/Quake/pr_ext.c +++ b/Quake/pr_ext.c @@ -19,9 +19,15 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +//provides a few convienience extensions, primarily builtins, but also autocvars. +//Also note the set+seta features. + + #include "quakedef.h" #include "q_ctype.h" +#define countof(x) (sizeof(x)/sizeof((x)[0])) + //there's a few different aproaches to tempstrings... //the lame way is to just have a single one (vanilla). //the only slightly less lame way is to just cycle between 16 or so (most engines). @@ -29,9 +35,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. //alternatively, just allocate them persistently and purge them only when there appear to be no more references to it (fte). makes strzone redundant. cvar_t pr_checkextension = {"pr_checkextension", "1", CVAR_NONE}; //spike - enables qc extensions. if 0 then they're ALL BLOCKED! MWAHAHAHA! *cough* *splutter* -struct pr_extfuncs_s pr_extfuncs; int pr_ext_warned_particleeffectnum; //so these only spam once per map +static void *PR_FindExtGlobal(int type, const char *name); void SV_CheckVelocity (edict_t *ent); typedef enum multicast_e @@ -50,10 +56,185 @@ typedef enum multicast_e static void SV_Multicast(multicast_t to, float *org, int msg_entity, unsigned int requireext2); #define Z_StrDup(s) strcpy(Z_Malloc(strlen(s)+1), s) -#define RETURN_EDICT(e) (((int *)pr_globals)[OFS_RETURN] = EDICT_TO_PROG(e)) +#define RETURN_EDICT(e) (((int *)qcvm->globals)[OFS_RETURN] = EDICT_TO_PROG(e)) -//provides a few convienience extensions, primarily builtins, but also autocvars. -//Also note the set+seta features. +int PR_MakeTempString (const char *val) +{ + char *tmp = PR_GetTempString(); + q_strlcpy(tmp, val, STRINGTEMP_LENGTH); + return PR_SetEngineString(tmp); +} + +#define ishex(c) ((c>='0' && c<= '9') || (c>='a' && c<='f') || (c>='A' && c<='F')) +static int dehex(char c) +{ + if (c >= '0' && c <= '9') + return c-'0'; + if (c >= 'A' && c <= 'F') + return c-('A'-10); + if (c >= 'a' && c <= 'f') + return c-('a'-10); + return 0; +} +//returns the next char... +struct markup_s +{ + const unsigned char *txt; + vec4_t tint; //predefined colour that applies to the entire string + vec4_t colour; //colour for the specific glyph in question + unsigned char mask; +}; +void PR_Markup_Begin(struct markup_s *mu, const char *text, float *rgb, float alpha) +{ + if (*text == '\1' || *text == '\2') + { + mu->mask = 128; + text++; + } + else + mu->mask = 0; + mu->txt = text; + VectorCopy(rgb, mu->tint); + mu->tint[3] = alpha; + VectorCopy(rgb, mu->colour); + mu->colour[3] = alpha; +} +int PR_Markup_Parse(struct markup_s *mu) +{ + static const vec4_t q3rgb[10] = { + {0.00,0.00,0.00, 1.0}, + {1.00,0.33,0.33, 1.0}, + {0.00,1.00,0.33, 1.0}, + {1.00,1.00,0.33, 1.0}, + {0.33,0.33,1.00, 1.0}, + {0.33,1.00,1.00, 1.0}, + {1.00,0.33,1.00, 1.0}, + {1.00,1.00,1.00, 1.0}, + {1.00,1.00,1.00, 0.5}, + {0.50,0.50,0.50, 1.0} + }; + unsigned int c; + const float *f; + while ((c = *mu->txt)) + { + if (c == '^' && pr_checkextension.value) + { //parse markup like FTE/DP might. + switch(mu->txt[1]) + { + case '^': //doubled up char for escaping. + mu->txt++; + break; + case '0': //black + case '1': //red + case '2': //green + case '3': //yellow + case '4': //blue + case '5': //cyan + case '6': //magenta + case '7': //white + case '8': //white+half-alpha + case '9': //grey + f = q3rgb[mu->txt[1]-'0']; + mu->colour[0] = mu->tint[0] * f[0]; + mu->colour[1] = mu->tint[1] * f[1]; + mu->colour[2] = mu->tint[2] * f[2]; + mu->colour[3] = mu->tint[3] * f[3]; + mu->txt+=2; + continue; + case 'h': //toggle half-alpha + if (mu->colour[3] != mu->tint[3] * 0.5) + mu->colour[3] = mu->tint[3] * 0.5; + else + mu->colour[3] = mu->tint[3]; + mu->txt+=2; + continue; + case 'd': //reset to defaults (fixme: should reset ^m without resetting \1) + mu->colour[0] = mu->tint[0]; + mu->colour[1] = mu->tint[1]; + mu->colour[2] = mu->tint[2]; + mu->colour[3] = mu->tint[3]; + mu->mask = 0; + mu->txt+=2; + break; + case 'b': //blink + case 's': //modstack push + case 'r': //modstack restore + mu->txt+=2; + continue; + case 'x': //RGB 12-bit colour + if (ishex(mu->txt[2]) && ishex(mu->txt[3]) && ishex(mu->txt[4])) + { + mu->colour[0] = mu->tint[0] * dehex(mu->txt[2])/15.0; + mu->colour[1] = mu->tint[1] * dehex(mu->txt[3])/15.0; + mu->colour[2] = mu->tint[2] * dehex(mu->txt[4])/15.0; + mu->txt+=5; + continue; + } + break; //malformed + case '[': //start fte's ^[text\key\value\key\value^] links + case ']': //end link + break; //fixme... skip the keys, recolour properly, etc + // txt+=2; + // continue; + case '&': + if ((ishex(mu->txt[2])||mu->txt[2]=='-') && (ishex(mu->txt[3])||mu->txt[3]=='-')) + { //ignore fte's fore/back ansi colours + mu->txt += 4; + continue; + } + break; //malformed + case 'm': //toggle masking. + mu->txt+=2; + mu->mask ^= 128; + continue; + case 'U': //ucs-2 unicode codepoint + if (ishex(mu->txt[2]) && ishex(mu->txt[3]) && ishex(mu->txt[4]) && ishex(mu->txt[5])) + { + c = (dehex(mu->txt[2])<<12) | (dehex(mu->txt[3])<<8) | (dehex(mu->txt[4])<<4) | dehex(mu->txt[5]); + mu->txt += 6; + + if (c >= 0xe000 && c <= 0xe0ff) + c &= 0xff; //private-use 0xE0XX maps to quake's chars + else if (c >= 0x20 && c <= 0x7f) + c &= 0x7f; //ascii is okay too. + else + c = '?'; //otherwise its some unicode char that we don't know how to handle. + return c; + } + break; //malformed + case '{': //full unicode codepoint, for chars up to 0x10ffff + mu->txt += 2; + c = 0; //no idea + while(*mu->txt) + { + if (*mu->txt == '}') + { + mu->txt++; + break; + } + if (!ishex(*mu->txt)) + break; + c<<=4; + c |= dehex(*mu->txt++); + } + + if (c >= 0xe000 && c <= 0xe0ff) + c &= 0xff; //private-use 0xE0XX maps to quake's chars + else if (c >= 0x20 && c <= 0x7f) + c &= 0x7f; //ascii is okay too. + //it would be nice to include a table to de-accent latin scripts, as well as translate cyrilic somehow, but not really necessary. + else + c = '?'; //otherwise its some unicode char that we don't know how to handle. + return c; + } + } + + //regular char + mu->txt++; + return c|mu->mask; + } + return 0; +} #define D(typestr,desc) typestr,desc @@ -101,7 +282,7 @@ static void PF_Logarithm(void) //log2(v) = ln(v)/ln(2) double r; r = log(G_FLOAT(OFS_PARM0)); - if (pr_argc > 1) + if (qcvm->argc > 1) r /= log(G_FLOAT(OFS_PARM1)); G_FLOAT(OFS_RETURN) = r; } @@ -125,7 +306,7 @@ static void PF_min(void) { float r = G_FLOAT(OFS_PARM0); int i; - for (i = 1; i < pr_argc; i++) + for (i = 1; i < qcvm->argc; i++) { if (r > G_FLOAT(OFS_PARM0 + i*3)) r = G_FLOAT(OFS_PARM0 + i*3); @@ -136,7 +317,7 @@ static void PF_max(void) { float r = G_FLOAT(OFS_PARM0); int i; - for (i = 1; i < pr_argc; i++) + for (i = 1; i < qcvm->argc; i++) { if (r < G_FLOAT(OFS_PARM0 + i*3)) r = G_FLOAT(OFS_PARM0 + i*3); @@ -205,7 +386,7 @@ static void PF_ext_vectoangles(void) float *value1, *up; value1 = G_VECTOR(OFS_PARM0); - if (pr_argc >= 2) + if (qcvm->argc >= 2) up = G_VECTOR(OFS_PARM1); else up = NULL; @@ -228,7 +409,7 @@ static void PF_strcat(void) out[0] = 0; s = 0; - for (i = 0; i < pr_argc; i++) + for (i = 0; i < qcvm->argc; i++) { s = q_strlcat(out, G_STRING((OFS_PARM0+i*3)), STRINGTEMP_LENGTH); if (s >= STRINGTEMP_LENGTH) @@ -286,8 +467,6 @@ static void PF_substring(void) G_INT(OFS_RETURN) = PR_SetEngineString(string); } /*our zoned strings implementation is somewhat specific to quakespasm, so good luck porting*/ -unsigned char *knownzone; -size_t knownzonesize; static void PF_strzone(void) { char *buf; @@ -296,7 +475,7 @@ static void PF_strzone(void) size_t l[8]; int i; size_t id; - for (i = 0; i < pr_argc; i++) + for (i = 0; i < qcvm->argc; i++) { s[i] = G_STRING(OFS_PARM0+i*3); l[i] = strlen(s[i]); @@ -307,15 +486,15 @@ static void PF_strzone(void) buf = Z_Malloc(len); G_INT(OFS_RETURN) = PR_SetEngineString(buf); id = -1-G_INT(OFS_RETURN); - if (id >= knownzonesize) + if (id >= qcvm->knownzonesize) { - knownzonesize = (id+32)&~7; - knownzone = Z_Realloc(knownzone, knownzonesize>>3); + qcvm->knownzonesize = (id+32)&~7; + qcvm->knownzone = Z_Realloc(qcvm->knownzone, qcvm->knownzonesize>>3); } - knownzone[id>>3] |= 1u<<(id&7); + qcvm->knownzone[id>>3] |= 1u<<(id&7); len = 0; - for (i = 0; i < pr_argc; i++) + for (i = 0; i < qcvm->argc; i++) { memcpy(buf, s[i], l[i]); buf += l[i]; @@ -329,9 +508,9 @@ static void PF_strunzone(void) if (!G_INT(OFS_PARM0)) return; //don't bug out if they gave a null string id = -1-G_INT(OFS_PARM0); - if (id < knownzonesize && (knownzone[id>>3] & (1u<<(id&7)))) + if (id < qcvm->knownzonesize && (qcvm->knownzone[id>>3] & (1u<<(id&7)))) { - knownzone[id>>3] &= ~(1u<<(id&7)); + qcvm->knownzone[id>>3] &= ~(1u<<(id&7)); PR_ClearEngineString(G_INT(OFS_PARM0)); Z_Free((void*)foo); } @@ -340,26 +519,32 @@ static void PF_strunzone(void) } static void PR_UnzoneAll(void) { //called to clean up all zoned strings. - while (knownzonesize --> 0) + while (qcvm->knownzonesize --> 0) { - size_t id = knownzonesize; - if (knownzone[id>>3] & (1u<<(id&7))) + size_t id = qcvm->knownzonesize; + if (qcvm->knownzone[id>>3] & (1u<<(id&7))) { string_t s = -1-(int)id; char *ptr = (char*)PR_GetString(s); PR_ClearEngineString(s); Z_Free(ptr); } - } - if (knownzone) - Z_Free(knownzone); - knownzonesize = 0; - knownzone = NULL; + } + if (qcvm->knownzone) + Z_Free(qcvm->knownzone); + qcvm->knownzonesize = 0; + qcvm->knownzone = NULL; +} +static qboolean qc_isascii(unsigned int u) +{ + if (u < 256) //should be just \n and 32-127, but we don't actually support any actual unicode and we don't really want to make things worse. + return true; + return false; } static void PF_str2chr(void) { const char *instr = G_STRING(OFS_PARM0); - int ofs = (pr_argc>1)?G_FLOAT(OFS_PARM1):0; + int ofs = (qcvm->argc>1)?G_FLOAT(OFS_PARM1):0; if (ofs < 0) ofs = strlen(instr)+ofs; @@ -373,8 +558,16 @@ static void PF_chr2str(void) { char *ret = PR_GetTempString(), *out; int i; - for (i = 0, out=ret; i < pr_argc; i++) - *out++ = G_FLOAT(OFS_PARM0 + i*3); + for (i = 0, out=ret; out-ret < STRINGTEMP_LENGTH-6 && i < qcvm->argc; i++) + { + unsigned int u = G_FLOAT(OFS_PARM0 + i*3); + if (u >= 0xe000 && u < 0xe100) + *out++ = (unsigned char)u; //quake chars. + else if (qc_isascii(u)) + *out++ = u; + else + *out++ = '?'; //no unicode support + } *out = 0; G_INT(OFS_RETURN) = PR_SetEngineString(ret); } @@ -669,11 +862,11 @@ static void PF_strncmp(void) const char *a = G_STRING(OFS_PARM0); const char *b = G_STRING(OFS_PARM1); - if (pr_argc > 2) + if (qcvm->argc > 2) { int len = G_FLOAT(OFS_PARM2); - int aofs = pr_argc>3?G_FLOAT(OFS_PARM3):0; - int bofs = pr_argc>4?G_FLOAT(OFS_PARM4):0; + int aofs = qcvm->argc>3?G_FLOAT(OFS_PARM3):0; + int bofs = qcvm->argc>4?G_FLOAT(OFS_PARM4):0; if (aofs < 0 || (aofs && aofs > (int)strlen(a))) aofs = strlen(a); if (bofs < 0 || (bofs && bofs > (int)strlen(b))) @@ -688,11 +881,11 @@ static void PF_strncasecmp(void) const char *a = G_STRING(OFS_PARM0); const char *b = G_STRING(OFS_PARM1); - if (pr_argc > 2) + if (qcvm->argc > 2) { int len = G_FLOAT(OFS_PARM2); - int aofs = pr_argc>3?G_FLOAT(OFS_PARM3):0; - int bofs = pr_argc>4?G_FLOAT(OFS_PARM4):0; + int aofs = qcvm->argc>3?G_FLOAT(OFS_PARM3):0; + int bofs = qcvm->argc>4?G_FLOAT(OFS_PARM4):0; if (aofs < 0 || (aofs && aofs > (int)strlen(a))) aofs = strlen(a); if (bofs < 0 || (bofs && bofs > (int)strlen(b))) @@ -706,7 +899,7 @@ static void PF_strstrofs(void) { const char *instr = G_STRING(OFS_PARM0); const char *match = G_STRING(OFS_PARM1); - int firstofs = (pr_argc>2)?G_FLOAT(OFS_PARM2):0; + int firstofs = (qcvm->argc>2)?G_FLOAT(OFS_PARM2):0; if (firstofs && (firstofs < 0 || firstofs > (int)strlen(instr))) { @@ -828,11 +1021,11 @@ static void PF_sprintf_internal (const char *s, int firstarg, char *outbuf, int formatbuf[0] = '%'; -#define GETARG_FLOAT(a) (((a)>=firstarg && (a)=firstarg && (a)=firstarg && (a)=firstarg && (a)=firstarg && (a)=firstarg && (a)argc) ? (G_FLOAT(OFS_PARM0 + 3 * (a))) : 0) +#define GETARG_VECTOR(a) (((a)>=firstarg && (a)argc) ? (G_VECTOR(OFS_PARM0 + 3 * (a))) : dummyvec) +#define GETARG_INT(a) (((a)>=firstarg && (a)argc) ? (G_INT(OFS_PARM0 + 3 * (a))) : 0) +#define GETARG_INTVECTOR(a) (((a)>=firstarg && (a)argc) ? ((int*) G_VECTOR(OFS_PARM0 + 3 * (a))) : dummyivec) +#define GETARG_STRING(a) (((a)>=firstarg && (a)argc) ? (G_STRING(OFS_PARM0 + 3 * (a))) : "") for(;;) { @@ -1190,7 +1383,10 @@ static int tokenizeqc(const char *str, qboolean dpfuckage) break; qctoken[qctoken_count].start = str - start; - str = COM_Parse(str); +// if (dpfuckage) +// str = COM_ParseDPFuckage(str); +// else + str = COM_Parse(str); if (!str) break; @@ -1223,7 +1419,7 @@ static void PF_tokenizebyseparator(void) int tlen; qboolean found = true; - while (seps < pr_argc - 1 && seps < 7) + while (seps < qcvm->argc - 1 && seps < 7) { sep[seps] = G_STRING(OFS_PARM1 + seps*3); seplen[seps] = strlen(sep[seps]); @@ -1371,7 +1567,7 @@ static void PF_strftime(void) } static void PF_stof(void) { - G_FLOAT(OFS_RETURN) = atoi(G_STRING(OFS_PARM0)); + G_FLOAT(OFS_RETURN) = atof(G_STRING(OFS_PARM0)); } static void PF_stov(void) { @@ -1460,7 +1656,7 @@ static void PF_tracebox(void) if (trace.ent) pr_global_struct->trace_ent = EDICT_TO_PROG(trace.ent); else - pr_global_struct->trace_ent = EDICT_TO_PROG(sv.edicts); + pr_global_struct->trace_ent = EDICT_TO_PROG(qcvm->edicts); } static void PF_TraceToss(void) { @@ -1475,11 +1671,11 @@ static void PF_TraceToss(void) edict_t *tossent, *ignore; tossent = G_EDICT(OFS_PARM0); - if (tossent == sv.edicts) + if (tossent == qcvm->edicts) Con_Warning("tracetoss: can not use world entity\n"); ignore = G_EDICT(OFS_PARM1); - val = GetEdictFieldValue(tossent, pr_extfields.gravity); + val = GetEdictFieldValue(tossent, qcvm->extfields.gravity); if (val && val->_float) gravity = val->_float; else @@ -1523,15 +1719,57 @@ static void PF_TraceToss(void) if (trace.ent) pr_global_struct->trace_ent = EDICT_TO_PROG(trace.ent); else - pr_global_struct->trace_ent = EDICT_TO_PROG(sv.edicts); + pr_global_struct->trace_ent = EDICT_TO_PROG(qcvm->edicts); } //model stuff +void SetMinMaxSize (edict_t *e, float *minvec, float *maxvec, qboolean rotate); +static void PF_sv_setmodelindex(void) +{ + edict_t *e = G_EDICT(OFS_PARM0); + unsigned int newidx = G_FLOAT(OFS_PARM1); + qmodel_t *mod = qcvm->GetModel(newidx); + e->v.model = (newidxv.modelindex = newidx; + + if (mod) + //johnfitz -- correct physics cullboxes for bmodels + { + if (mod->type == mod_brush) + SetMinMaxSize (e, mod->clipmins, mod->clipmaxs, true); + else + SetMinMaxSize (e, mod->mins, mod->maxs, true); + } + //johnfitz + else + SetMinMaxSize (e, vec3_origin, vec3_origin, true); +} +static void PF_cl_setmodelindex(void) +{ + edict_t *e = G_EDICT(OFS_PARM0); + int newidx = G_FLOAT(OFS_PARM1); + qmodel_t *mod = qcvm->GetModel(newidx); + e->v.model = mod?PR_SetEngineString(mod->name):0; //FIXME: is this going to cause issues with vid_restart? + e->v.modelindex = newidx; + + if (mod) + //johnfitz -- correct physics cullboxes for bmodels + { + if (mod->type == mod_brush) + SetMinMaxSize (e, mod->clipmins, mod->clipmaxs, true); + else + SetMinMaxSize (e, mod->mins, mod->maxs, true); + } + //johnfitz + else + SetMinMaxSize (e, vec3_origin, vec3_origin, true); +} + static void PF_frameforname(void) { unsigned int modelindex = G_FLOAT(OFS_PARM0); const char *framename = G_STRING(OFS_PARM1); - qmodel_t *mod = (modelindex < MAX_MODELS)?sv.models[modelindex]:NULL; + qmodel_t *mod = qcvm->GetModel(modelindex); aliashdr_t *alias; G_FLOAT(OFS_RETURN) = -1; @@ -1552,7 +1790,7 @@ static void PF_frametoname(void) { unsigned int modelindex = G_FLOAT(OFS_PARM0); unsigned int framenum = G_FLOAT(OFS_PARM1); - qmodel_t *mod = (modelindex < MAX_MODELS)?sv.models[modelindex]:NULL; + qmodel_t *mod = qcvm->GetModel(modelindex); aliashdr_t *alias; if (mod && mod->type == mod_alias && (alias = Mod_Extradata(mod)) && framenum < (unsigned int)alias->numframes) @@ -1564,7 +1802,7 @@ static void PF_frameduration(void) { unsigned int modelindex = G_FLOAT(OFS_PARM0); unsigned int framenum = G_FLOAT(OFS_PARM1); - qmodel_t *mod = (modelindex < MAX_MODELS)?sv.models[modelindex]:NULL; + qmodel_t *mod = qcvm->GetModel(modelindex); aliashdr_t *alias; if (mod && mod->type == mod_alias && (alias = Mod_Extradata(mod)) && framenum < (unsigned int)alias->numframes) @@ -1575,7 +1813,7 @@ static void PF_getsurfacenumpoints(void) edict_t *ed = G_EDICT(OFS_PARM0); unsigned int surfidx = G_FLOAT(OFS_PARM1); unsigned int modelindex = ed->v.modelindex; - qmodel_t *mod = (modelindex < MAX_MODELS)?sv.models[modelindex]:NULL; + qmodel_t *mod = qcvm->GetModel(modelindex); if (mod && mod->type == mod_brush && !mod->needload && surfidx < (unsigned int)mod->nummodelsurfaces) { @@ -1598,8 +1836,7 @@ static void PF_getsurfacepoint(void) edict_t *ed = G_EDICT(OFS_PARM0); unsigned int surfidx = G_FLOAT(OFS_PARM1); unsigned int point = G_FLOAT(OFS_PARM2); - unsigned int modelindex = ed->v.modelindex; - qmodel_t *mod = (modelindex < MAX_MODELS)?sv.models[modelindex]:NULL; + qmodel_t *mod = qcvm->GetModel(ed->v.modelindex); if (mod && mod->type == mod_brush && !mod->needload && surfidx < (unsigned int)mod->nummodelsurfaces && point < (unsigned int)mod->surfaces[surfidx].numedges) { @@ -1617,8 +1854,7 @@ static void PF_getsurfacenumtriangles(void) { //for q3bsp compat (which this engine doesn't support, so its fairly simple) edict_t *ed = G_EDICT(OFS_PARM0); unsigned int surfidx = G_FLOAT(OFS_PARM1); - unsigned int modelindex = ed->v.modelindex; - qmodel_t *mod = (modelindex < MAX_MODELS)?sv.models[modelindex]:NULL; + qmodel_t *mod = qcvm->GetModel(ed->v.modelindex); if (mod && mod->type == mod_brush && !mod->needload && surfidx < (unsigned int)mod->nummodelsurfaces) G_FLOAT(OFS_RETURN) = (mod->surfaces[surfidx+mod->firstmodelsurface].numedges-2); //q1bsp is only triangle fans @@ -1630,8 +1866,7 @@ static void PF_getsurfacetriangle(void) edict_t *ed = G_EDICT(OFS_PARM0); unsigned int surfidx = G_FLOAT(OFS_PARM1); unsigned int triangleidx= G_FLOAT(OFS_PARM2); - unsigned int modelindex = ed->v.modelindex; - qmodel_t *mod = (modelindex < MAX_MODELS)?sv.models[modelindex]:NULL; + qmodel_t *mod = qcvm->GetModel(ed->v.modelindex); if (mod && mod->type == mod_brush && !mod->needload && surfidx < (unsigned int)mod->nummodelsurfaces && triangleidx < (unsigned int)mod->surfaces[surfidx].numedges-2) { @@ -1650,8 +1885,7 @@ static void PF_getsurfacenormal(void) { edict_t *ed = G_EDICT(OFS_PARM0); unsigned int surfidx = G_FLOAT(OFS_PARM1); - unsigned int modelindex = ed->v.modelindex; - qmodel_t *mod = (modelindex < MAX_MODELS)?sv.models[modelindex]:NULL; + qmodel_t *mod = qcvm->GetModel(ed->v.modelindex); if (mod && mod->type == mod_brush && !mod->needload && surfidx < (unsigned int)mod->nummodelsurfaces) { @@ -1667,8 +1901,7 @@ static void PF_getsurfacetexture(void) { edict_t *ed = G_EDICT(OFS_PARM0); unsigned int surfidx = G_FLOAT(OFS_PARM1); - unsigned int modelindex = ed->v.modelindex; - qmodel_t *mod = (modelindex < MAX_MODELS)?sv.models[modelindex]:NULL; + qmodel_t *mod = qcvm->GetModel(ed->v.modelindex); if (mod && mod->type == mod_brush && !mod->needload && surfidx < (unsigned int)mod->nummodelsurfaces) { @@ -1742,7 +1975,6 @@ static void PF_getsurfacenearpoint(void) msurface_t *surf; int i; float *point; - unsigned int u; vec3_t cpoint = {0,0,0}; float bestdist = 0x7fffffff, dist; @@ -1753,8 +1985,7 @@ static void PF_getsurfacenearpoint(void) G_FLOAT(OFS_RETURN) = -1; - u = ent->v.modelindex; - model = (u>=MAX_MODELS)?NULL:sv.models[u]; + model = qcvm->GetModel(ent->v.modelindex); if (!model || model->type != mod_brush || model->needload) return; @@ -1783,7 +2014,6 @@ static void PF_getsurfaceclippedpoint(void) msurface_t *surf; float *point; int surfnum; - unsigned int u; float *result = G_VECTOR(OFS_RETURN); @@ -1793,8 +2023,7 @@ static void PF_getsurfaceclippedpoint(void) VectorCopy(point, result); - u = ent->v.modelindex; - model = (u>=MAX_MODELS)?NULL:sv.models[u]; + model = qcvm->GetModel(ent->v.modelindex); if (!model || model->type != mod_brush || model->needload) return; @@ -1813,8 +2042,7 @@ static void PF_getsurfacepointattribute(void) unsigned int point = G_FLOAT(OFS_PARM2); unsigned int attribute = G_FLOAT(OFS_PARM3); - unsigned int modelindex = ed->v.modelindex; - qmodel_t *mod = (modelindex < MAX_MODELS)?sv.models[modelindex]:NULL; + qmodel_t *mod = qcvm->GetModel(ed->v.modelindex); if (mod && mod->type == mod_brush && !mod->needload && surfidx < (unsigned int)mod->nummodelsurfaces && point < (unsigned int)mod->surfaces[mod->firstmodelsurface+surfidx].numedges) { @@ -1957,7 +2185,7 @@ static void PF_spawnclient(void) SV_ConnectClient(i); svs.clients[i].spawned = true; ent = svs.clients[i].edict; - memset (&ent->v, 0, progs->entityfields * 4); + memset (&ent->v, 0, qcvm->progs->entityfields * 4); ent->v.colormap = NUM_FOR_EDICT(ent); ent->v.team = (svs.clients[i].colors & 15) + 1; ent->v.netname = PR_SetEngineString(svs.clients[i].name); @@ -1965,7 +2193,7 @@ static void PF_spawnclient(void) return; } } - RETURN_EDICT(sv.edicts); + RETURN_EDICT(qcvm->edicts); } static void PF_dropclient(void) { @@ -1984,7 +2212,7 @@ static void PF_dropclient(void) static void PF_print(void) { int i; - for (i = 0; i < pr_argc; i++) + for (i = 0; i < qcvm->argc; i++) Con_Printf("%s", G_STRING(OFS_PARM0 + i*3)); } static void PF_cvar_string(void) @@ -2040,7 +2268,7 @@ static void PF_cvar_description(void) static void PF_registercvar(void) { const char *name = G_STRING(OFS_PARM0); - const char *value = (pr_argc>1)?G_STRING(OFS_PARM0):""; + const char *value = (qcvm->argc>1)?G_STRING(OFS_PARM0):""; Cvar_Create(name, value); } @@ -2329,7 +2557,7 @@ static void PF_sv_pointsound(void) const char *sample = G_STRING(OFS_PARM1); float volume = G_FLOAT(OFS_PARM2); float attenuation = G_FLOAT(OFS_PARM3); - SV_StartSound (sv.edicts, origin, 0, sample, volume, attenuation); + SV_StartSound (qcvm->edicts, origin, 0, sample, volume, attenuation); } //file stuff @@ -2365,6 +2593,7 @@ static qboolean QC_FixFileName(const char *name, const char **result, const char //even libc mandates a seek between reading+writing, so no great loss there. static struct qcfile_s { + qcvm_t *owningvm; char cache[1024]; int cacheoffset, cachesize; FILE *file; @@ -2430,6 +2659,7 @@ static void PF_fopen(void) break; } qcfiles[i].filebase = ftell(file); + qcfiles[i].owningvm = qcvm; qcfiles[i].file = file; qcfiles[i].mode = fmode; //reading needs size info @@ -2457,7 +2687,7 @@ static void PF_fgets(void) char *end = ret+STRINGTEMP_LENGTH; for (;;) { - if (!f->cachesize) + if (f->cacheoffset == f->cachesize) { //figure out how much we can try to cache. int sz = f->filesize - f->fileoffset; @@ -2471,6 +2701,11 @@ static void PF_fgets(void) f->fileoffset += f->cachesize; if (!f->cachesize) { + if (s == ret) + { //absolutely nothing to spew + G_INT(OFS_RETURN) = 0; + return; + } //classic eof... break; } @@ -2512,6 +2747,20 @@ static void PF_fclose(void) { fclose(qcfiles[fileid].file); qcfiles[fileid].file = NULL; + qcfiles[fileid].owningvm = NULL; + } +} +static void PF_frikfile_shutdown(void) +{ + size_t i; + for (i = 0; i < qcfiles_max; i++) + { + if (qcfiles[i].owningvm == qcvm) + { + fclose(qcfiles[i].file); + qcfiles[i].file = NULL; + qcfiles[i].owningvm = NULL; + } } } static void PF_fseek(void) @@ -2528,7 +2777,7 @@ static void PF_fseek(void) G_INT(OFS_RETURN) = qcfiles[fileid].fileoffset; //when we're reading, use the cached read offset else G_INT(OFS_RETURN) = ftell(qcfiles[fileid].file)-qcfiles[fileid].filebase; - if (pr_argc>1) + if (qcvm->argc>1) { qcfiles[fileid].fileoffset = G_INT(OFS_PARM1); fseek(qcfiles[fileid].file, qcfiles[fileid].filebase+qcfiles[fileid].fileoffset, SEEK_SET); @@ -2609,41 +2858,126 @@ static void PF_fsize(void) } #endif +struct filesearch_s +{ + qcvm_t *owner; + size_t numfiles; + size_t maxfiles; + struct + { + char name[MAX_QPATH]; + time_t mtime; + size_t fsize; + } *file; +} searches[16]; +static qboolean PR_Search_AddFile(void *ctx, const char *fname, time_t mtime, size_t fsize) +{ + struct filesearch_s *c = ctx; + if (c->numfiles == c->maxfiles) + { + c->maxfiles = c->maxfiles*2+2; + c->file = realloc(c->file, c->maxfiles*sizeof(*c->file)); + } + q_strlcpy(c->file[c->numfiles].name, fname, sizeof(c->file[c->numfiles].name)); + c->file[c->numfiles].mtime = mtime; + c->file[c->numfiles].fsize = fsize; + c->numfiles++; + return true; +} +void COM_ListFiles(void *ctx, const char *gamedir, const char *pattern, qboolean (*cb)(void *ctx, const char *fname, time_t mtime, size_t fsize)); +static void PF_search_shutdown(void) +{ + size_t i; + for (i = 0; i < countof(searches); i++) + { + if (searches[i].owner == qcvm) + { + searches[i].owner = NULL; + searches[i].numfiles = 0; + searches[i].maxfiles = 0; + free(searches[i].file); + searches[i].file = NULL; + } + } +} static void PF_search_begin(void) { -// const char *pattern = G_STRING(OFS_PARM0); + size_t i; + const char *pattern = G_STRING(OFS_PARM0); // qboolean caseinsensitive = !!G_FLOAT(OFS_PARM0); // qboolaen quiet = !!G_FLOAT(OFS_PARM0); + for (i = 0; i < countof(searches); i++) + { + if (!searches[i].owner) + { + COM_ListFiles(&searches[i], com_gamedir, pattern, PR_Search_AddFile); + if (!searches[i].numfiles) + break; + searches[i].owner = qcvm; + G_FLOAT(OFS_RETURN) = i; + return; + } + } G_FLOAT(OFS_RETURN) = -1; } static void PF_search_end(void) { -// int handle = G_FLOAT(OFS_PARM0); + int handle = G_FLOAT(OFS_PARM0); + if (handle < 0 || handle >= countof(searches)) + return; //erk + searches[handle].owner = NULL; + searches[handle].numfiles = 0; + searches[handle].maxfiles = 0; + free(searches[handle].file); + searches[handle].file = NULL; } static void PF_search_getsize(void) { -// int handle = G_FLOAT(OFS_PARM0); - G_FLOAT(OFS_RETURN) = 0; + size_t handle = G_FLOAT(OFS_PARM0); + if (handle < 0 || handle >= countof(searches)) + { + G_FLOAT(OFS_RETURN) = 0; + return; //erk + } + G_FLOAT(OFS_RETURN) = searches[handle].numfiles; } static void PF_search_getfilename(void) { -// int handle = G_FLOAT(OFS_PARM0); -// int index = G_FLOAT(OFS_PARM1); - G_INT(OFS_RETURN) = PR_SetEngineString(""); + size_t handle = G_FLOAT(OFS_PARM0); + size_t index = G_FLOAT(OFS_PARM1); + G_INT(OFS_RETURN) = 0; + if (handle < 0 || handle >= countof(searches)) + return; //erk + if (index < 0 || index >= searches[handle].numfiles) + return; //erk + G_INT(OFS_RETURN) = PR_MakeTempString(searches[handle].file[index].name); } static void PF_search_getfilesize(void) { -// int handle = G_FLOAT(OFS_PARM0); -// int index = G_FLOAT(OFS_PARM1); + size_t handle = G_FLOAT(OFS_PARM0); + size_t index = G_FLOAT(OFS_PARM1); G_FLOAT(OFS_RETURN) = 0; + if (handle < 0 || handle >= countof(searches)) + return; //erk + if (index < 0 || index >= searches[handle].numfiles) + return; //erk + G_FLOAT(OFS_RETURN) = searches[handle].file[index].fsize; } static void PF_search_getfilemtime(void) { -// int handle = G_FLOAT(OFS_PARM0); -// int index = G_FLOAT(OFS_PARM1); - G_INT(OFS_RETURN) = PR_SetEngineString(""); + char *ret = PR_GetTempString(); + size_t handle = G_FLOAT(OFS_PARM0); + size_t index = G_FLOAT(OFS_PARM1); + G_INT(OFS_RETURN) = 0; + if (handle < 0 || handle >= countof(searches)) + return; //erk + if (index < 0 || index >= searches[handle].numfiles) + return; //erk + + strftime(ret, STRINGTEMP_LENGTH, "%Y-%m-%d %H:%M:%S", localtime(&searches[handle].file[index].mtime)); + G_INT(OFS_RETURN) = PR_SetEngineString(ret); } static void PF_whichpack(void) @@ -2677,7 +3011,7 @@ static void PF_whichpack(void) //string buffers struct strbuf { - qboolean isactive; + qcvm_t *owningvm; char **strings; unsigned int used; unsigned int allocated; @@ -2693,7 +3027,7 @@ static void PF_buf_shutdown(void) for (bufno = 0; bufno < NUMSTRINGBUFS; bufno++) { - if (!strbuflist[bufno].isactive) + if (strbuflist[bufno].owningvm != qcvm) continue; for (i = 0; i < strbuflist[bufno].used; i++) @@ -2711,7 +3045,7 @@ static void PF_buf_create(void) { unsigned int i; - const char *type = ((pr_argc>0)?G_STRING(OFS_PARM0):"string"); + const char *type = ((qcvm->argc>0)?G_STRING(OFS_PARM0):"string"); // unsigned int flags = ((pr_argc>1)?G_FLOAT(OFS_PARM1):1); if (!q_strcasecmp(type, "string")) @@ -2726,9 +3060,9 @@ static void PF_buf_create(void) for (i = 0; i < NUMSTRINGBUFS; i++) { - if (!strbuflist[i].isactive) + if (!strbuflist[i].owningvm) { - strbuflist[i].isactive = true; + strbuflist[i].owningvm = qcvm; strbuflist[i].used = 0; strbuflist[i].allocated = 0; strbuflist[i].strings = NULL; @@ -2746,7 +3080,7 @@ static void PF_buf_del(void) if (bufno >= NUMSTRINGBUFS) return; - if (!strbuflist[bufno].isactive) + if (!strbuflist[bufno].owningvm) return; for (i = 0; i < strbuflist[bufno].used; i++) @@ -2757,7 +3091,7 @@ static void PF_buf_del(void) strbuflist[bufno].used = 0; strbuflist[bufno].allocated = 0; - strbuflist[bufno].isactive = false; + strbuflist[bufno].owningvm = NULL; } // #442 float(float bufhandle) buf_getsize (DP_QC_STRINGBUFFERS) static void PF_buf_getsize(void) @@ -2766,7 +3100,7 @@ static void PF_buf_getsize(void) if ((unsigned int)bufno >= NUMSTRINGBUFS) return; - if (!strbuflist[bufno].isactive) + if (!strbuflist[bufno].owningvm) return; G_FLOAT(OFS_RETURN) = strbuflist[bufno].used; @@ -2782,11 +3116,11 @@ static void PF_buf_copy(void) return; if (buffrom >= NUMSTRINGBUFS) return; - if (!strbuflist[buffrom].isactive) + if (!strbuflist[buffrom].owningvm) return; if (bufto >= NUMSTRINGBUFS) return; - if (!strbuflist[bufto].isactive) + if (!strbuflist[bufto].owningvm) return; //obliterate any and all existing data. @@ -2820,7 +3154,7 @@ static void PF_buf_sort(void) if ((unsigned int)bufno >= NUMSTRINGBUFS) return; - if (!strbuflist[bufno].isactive) + if (!strbuflist[bufno].owningvm) return; if (sortprefixlen <= 0) @@ -2857,7 +3191,7 @@ static void PF_buf_implode(void) if ((unsigned int)bufno >= NUMSTRINGBUFS) return; - if (!strbuflist[bufno].isactive) + if (!strbuflist[bufno].owningvm) return; //count neededlength @@ -2917,7 +3251,7 @@ static void PF_bufstr_get(void) G_INT(OFS_RETURN) = 0; return; } - if (!strbuflist[bufno].isactive) + if (!strbuflist[bufno].owningvm) { G_INT(OFS_RETURN) = 0; return; @@ -2943,7 +3277,7 @@ static void PF_bufstr_set(void) if ((unsigned int)bufno >= NUMSTRINGBUFS) return; - if (!strbuflist[bufno].isactive) + if (!strbuflist[bufno].owningvm) return; if (index >= strbuflist[bufno].allocated) @@ -3009,7 +3343,7 @@ static void PF_bufstr_add(void) if ((unsigned int)bufno >= NUMSTRINGBUFS) return; - if (!strbuflist[bufno].isactive) + if (!strbuflist[bufno].owningvm) return; G_FLOAT(OFS_RETURN) = PF_bufstr_add_internal(bufno, string, ordered); @@ -3022,7 +3356,7 @@ static void PF_bufstr_free(void) if ((unsigned int)bufno >= NUMSTRINGBUFS) return; - if (!strbuflist[bufno].isactive) + if (!strbuflist[bufno].owningvm) return; if (index >= strbuflist[bufno].used) @@ -3076,7 +3410,7 @@ static void PF_buf_loadfile(void) if ((unsigned int)bufno >= NUMSTRINGBUFS) return; - if (!strbuflist[bufno].isactive) + if (!strbuflist[bufno].owningvm) return; if (!QC_FixFileName(fname, &fname, &fallback)) @@ -3113,7 +3447,7 @@ static void PF_buf_writefile(void) if ((unsigned int)bufno >= NUMSTRINGBUFS) return; - if (!strbuflist[bufno].isactive) + if (!strbuflist[bufno].owningvm) return; if (fnum >= qcfiles_max) @@ -3121,7 +3455,7 @@ static void PF_buf_writefile(void) if (!qcfiles[fnum].file) return; - if (pr_argc >= 3) + if (qcvm->argc >= 3) { if (G_FLOAT(OFS_PARM2) <= 0) idx = 0; @@ -3130,7 +3464,7 @@ static void PF_buf_writefile(void) } else idx = 0; - if (pr_argc >= 4) + if (qcvm->argc >= 4) midx = idx + G_FLOAT(OFS_PARM3); else midx = strbuflist[bufno].used - idx; @@ -3158,7 +3492,7 @@ static void PF_copyentity(void) edict_t *dst = G_EDICT(OFS_PARM1); if (src->free || dst->free) Con_Printf("PF_copyentity: entity is free\n"); - memcpy(&dst->v, &src->v, pr_edict_size - sizeof(edict_t)); + memcpy(&dst->v, &src->v, qcvm->edict_size - sizeof(edict_t)); dst->alpha = src->alpha; dst->sendinterval = src->sendinterval; SV_LinkEdict(dst, false); @@ -3171,19 +3505,19 @@ static void PF_num_for_edict(void) { G_FLOAT(OFS_RETURN) = G_EDICTNUM(OFS_PARM0); } -static void PF_sv_findchain(void) +static void PF_findchain(void) { edict_t *ent, *chain; int i, f; const char *s, *t; - chain = (edict_t *)sv.edicts; + chain = (edict_t *)qcvm->edicts; f = G_INT(OFS_PARM0); s = G_STRING(OFS_PARM1); - ent = NEXT_EDICT(sv.edicts); - for (i = 1; i < sv.num_edicts; i++, ent = NEXT_EDICT(ent)) + ent = NEXT_EDICT(qcvm->edicts); + for (i = 1; i < qcvm->num_edicts; i++, ent = NEXT_EDICT(ent)) { if (ent->free) continue; @@ -3196,7 +3530,7 @@ static void PF_sv_findchain(void) RETURN_EDICT(chain); } -static void PF_sv_findfloat(void) +static void PF_findfloat(void) { int e; int f; @@ -3207,7 +3541,7 @@ static void PF_sv_findfloat(void) f = G_INT(OFS_PARM1); s = G_FLOAT(OFS_PARM2); - for (e++ ; e < sv.num_edicts ; e++) + for (e++ ; e < qcvm->num_edicts ; e++) { ed = EDICT_NUM(e); if (ed->free) @@ -3220,21 +3554,21 @@ static void PF_sv_findfloat(void) } } - RETURN_EDICT(sv.edicts); + RETURN_EDICT(qcvm->edicts); } -static void PF_sv_findchainfloat(void) +static void PF_findchainfloat(void) { edict_t *ent, *chain; int i, f; float s, t; - chain = (edict_t *)sv.edicts; + chain = (edict_t *)qcvm->edicts; f = G_INT(OFS_PARM0); s = G_FLOAT(OFS_PARM1); - ent = NEXT_EDICT(sv.edicts); - for (i = 1; i < sv.num_edicts; i++, ent = NEXT_EDICT(ent)) + ent = NEXT_EDICT(qcvm->edicts); + for (i = 1; i < qcvm->num_edicts; i++, ent = NEXT_EDICT(ent)) { if (ent->free) continue; @@ -3247,7 +3581,7 @@ static void PF_sv_findchainfloat(void) RETURN_EDICT(chain); } -static void PF_sv_findflags(void) +static void PF_findflags(void) { int e; int f; @@ -3258,7 +3592,7 @@ static void PF_sv_findflags(void) f = G_INT(OFS_PARM1); s = G_FLOAT(OFS_PARM2); - for (e++ ; e < sv.num_edicts ; e++) + for (e++ ; e < qcvm->num_edicts ; e++) { ed = EDICT_NUM(e); if (ed->free) @@ -3271,21 +3605,21 @@ static void PF_sv_findflags(void) } } - RETURN_EDICT(sv.edicts); + RETURN_EDICT(qcvm->edicts); } -static void PF_sv_findchainflags(void) +static void PF_findchainflags(void) { edict_t *ent, *chain; int i, f; int s, t; - chain = (edict_t *)sv.edicts; + chain = (edict_t *)qcvm->edicts; f = G_INT(OFS_PARM0); s = G_FLOAT(OFS_PARM1); - ent = NEXT_EDICT(sv.edicts); - for (i = 1; i < sv.num_edicts; i++, ent = NEXT_EDICT(ent)) + ent = NEXT_EDICT(qcvm->edicts); + for (i = 1; i < qcvm->num_edicts; i++, ent = NEXT_EDICT(ent)) { if (ent->free) continue; @@ -3300,61 +3634,61 @@ static void PF_sv_findchainflags(void) } static void PF_numentityfields(void) { - G_FLOAT(OFS_RETURN) = progs->numfielddefs; + G_FLOAT(OFS_RETURN) = qcvm->progs->numfielddefs; } static void PF_findentityfield(void) { ddef_t *fld = ED_FindField(G_STRING(OFS_PARM0)); if (fld) - G_FLOAT(OFS_RETURN) = fld - pr_fielddefs; + G_FLOAT(OFS_RETURN) = fld - qcvm->fielddefs; else G_FLOAT(OFS_RETURN) = 0; //the first field is meant to be some dummy placeholder. or it could be modelindex... } static void PF_entityfieldref(void) { unsigned int fldidx = G_FLOAT(OFS_PARM0); - if (fldidx >= (unsigned int)progs->numfielddefs) + if (fldidx >= (unsigned int)qcvm->progs->numfielddefs) G_INT(OFS_RETURN) = 0; else - G_INT(OFS_RETURN) = pr_fielddefs[fldidx].ofs; + G_INT(OFS_RETURN) = qcvm->fielddefs[fldidx].ofs; } static void PF_entityfieldname(void) { unsigned int fldidx = G_FLOAT(OFS_PARM0); - if (fldidx < (unsigned int)progs->numfielddefs) - G_INT(OFS_RETURN) = pr_fielddefs[fldidx].s_name; + if (fldidx < (unsigned int)qcvm->progs->numfielddefs) + G_INT(OFS_RETURN) = qcvm->fielddefs[fldidx].s_name; else G_INT(OFS_RETURN) = 0; } static void PF_entityfieldtype(void) { unsigned int fldidx = G_FLOAT(OFS_PARM0); - if (fldidx >= (unsigned int)progs->numfielddefs) + if (fldidx >= (unsigned int)qcvm->progs->numfielddefs) G_INT(OFS_RETURN) = ev_void; else - G_INT(OFS_RETURN) = pr_fielddefs[fldidx].type; + G_INT(OFS_RETURN) = qcvm->fielddefs[fldidx].type; } -static void PF_getentityfieldstring(void) +static void PF_getentfldstr(void) { unsigned int fldidx = G_FLOAT(OFS_PARM0); edict_t *ent = G_EDICT(OFS_PARM1); - if (fldidx < (unsigned int)progs->numfielddefs) + if (fldidx < (unsigned int)qcvm->progs->numfielddefs) { char *ret = PR_GetTempString(); - const char *val = PR_UglyValueString (pr_fielddefs[fldidx].type, (eval_t*)((float*)&ent->v + pr_fielddefs[fldidx].ofs)); + const char *val = PR_UglyValueString (qcvm->fielddefs[fldidx].type, (eval_t*)((float*)&ent->v + qcvm->fielddefs[fldidx].ofs)); q_strlcpy(ret, val, STRINGTEMP_LENGTH); G_INT(OFS_RETURN) = PR_SetEngineString(ret); } else G_INT(OFS_RETURN) = 0; } -static void PF_putentityfieldstring(void) +static void PF_putentfldstr(void) { unsigned int fldidx = G_FLOAT(OFS_PARM0); edict_t *ent = G_EDICT(OFS_PARM1); const char *value = G_STRING(OFS_PARM2); - if (fldidx < (unsigned int)progs->numfielddefs) - G_FLOAT(OFS_RETURN) = ED_ParseEpair ((void *)&ent->v, pr_fielddefs+fldidx, value); + if (fldidx < (unsigned int)qcvm->progs->numfielddefs) + G_FLOAT(OFS_RETURN) = ED_ParseEpair ((void *)&ent->v, qcvm->fielddefs+fldidx, value); else G_FLOAT(OFS_RETURN) = false; } @@ -3371,7 +3705,7 @@ static void PF_parseentitydata(void) { edict_t *ed = G_EDICT(OFS_PARM0); const char *data = G_STRING(OFS_PARM1), *end; - unsigned int offset = (pr_argc>2)?G_FLOAT(OFS_PARM2):0; + unsigned int offset = (qcvm->argc>2)?G_FLOAT(OFS_PARM2):0; if (offset) { unsigned int len = strlen(data); @@ -3390,14 +3724,14 @@ static void PF_callfunction(void) { dfunction_t *fnc; const char *fname; - if (!pr_argc) + if (!qcvm->argc) return; - pr_argc--; - fname = G_STRING(OFS_PARM0 + pr_argc*3); + qcvm->argc--; + fname = G_STRING(OFS_PARM0 + qcvm->argc*3); fnc = ED_FindFunction(fname); if (fnc && fnc->first_statement > 0) { - PR_ExecuteProgram(fnc - pr_functions); + PR_ExecuteProgram(fnc - qcvm->functions); } } static void PF_isfunction(void) @@ -3409,7 +3743,7 @@ static void PF_isfunction(void) //other stuff static void PF_gettime (void) { - int timer = (pr_argc > 0)?G_FLOAT(OFS_PARM0):0; + int timer = (qcvm->argc > 0)?G_FLOAT(OFS_PARM0):0; switch(timer) { default: @@ -3566,8 +3900,8 @@ static void PF_multicast_internal(qboolean reliable, byte *pvs, unsigned int req continue; //figure out which cluster (read: pvs index) to use. - playerleaf = Mod_PointInLeaf(svs.clients[i].edict->v.origin, sv.worldmodel); - cluster = playerleaf - sv.worldmodel->leafs; + playerleaf = Mod_PointInLeaf(svs.clients[i].edict->v.origin, qcvm->worldmodel); + cluster = playerleaf - qcvm->worldmodel->leafs; cluster--; //pvs is 1-based, leaf 0 is discarded. if (cluster < 0 || (pvs[cluster>>3] & (1<<(cluster&7)))) { @@ -3602,11 +3936,11 @@ static void SV_Multicast(multicast_t to, float *org, int msg_entity, unsigned in break; case MULTICAST_PHS_R: case MULTICAST_PHS_U: - PF_multicast_internal(to==MULTICAST_PHS_R, NULL/*Mod_LeafPHS(Mod_PointInLeaf(org, sv.worldmodel), sv.worldmodel)*/, requireext2); //we don't support phs, that would require lots of pvs decompression+merging stuff, and many q1bsps have a LOT of leafs. + PF_multicast_internal(to==MULTICAST_PHS_R, NULL/*Mod_LeafPHS(Mod_PointInLeaf(org, qcvm->worldmodel), qcvm->worldmodel)*/, requireext2); //we don't support phs, that would require lots of pvs decompression+merging stuff, and many q1bsps have a LOT of leafs. break; case MULTICAST_PVS_R: case MULTICAST_PVS_U: - PF_multicast_internal(to==MULTICAST_PVS_R, Mod_LeafPVS(Mod_PointInLeaf(org, sv.worldmodel), sv.worldmodel), requireext2); + PF_multicast_internal(to==MULTICAST_PVS_R, Mod_LeafPVS(Mod_PointInLeaf(org, qcvm->worldmodel), qcvm->worldmodel), requireext2); break; case MULTICAST_ONE_R: case MULTICAST_ONE_U: @@ -3629,7 +3963,7 @@ static void PF_multicast(void) { float *org = G_VECTOR(OFS_PARM0); multicast_t to = G_FLOAT(OFS_PARM1); - SV_Multicast(to, org, 0, 0); + SV_Multicast(to, org, NUM_FOR_EDICT(PROG_TO_EDICT(pr_global_struct->msg_entity)), 0); } static void PF_randomvector(void) { @@ -3734,15 +4068,31 @@ static void PF_crc16(void) static void PF_strlennocol(void) { - //quakespasm doesn't support colour codes. that makes this a bit of a no-op. - //there's no single set either, so this stuff is a bit awkward in ssqc. at least nothing will crash. - G_FLOAT(OFS_RETURN) = strlen(G_STRING(OFS_PARM0)); + int r = 0; + struct markup_s mu; + + PR_Markup_Begin(&mu, G_STRING(OFS_PARM0), vec3_origin, 1); + while (PR_Markup_Parse(&mu)) + r++; + G_FLOAT(OFS_RETURN) = r; } static void PF_strdecolorize(void) { - //quakespasm doesn't support colour codes. that makes this a bit of a no-op. - //there's no single set either, so this stuff is a bit awkward in ssqc. at least nothing will crash. - G_INT(OFS_RETURN) = G_INT(OFS_PARM0); + int l, c; + char *r = PR_GetTempString(); + struct markup_s mu; + + PR_Markup_Begin(&mu, G_STRING(OFS_PARM0), vec3_origin, 1); + for (l = 0; l < STRINGTEMP_LENGTH-1; l++) + { + c = PR_Markup_Parse(&mu); + if (!c) + break; + r[l] = c; + } + r[l] = 0; + + G_INT(OFS_RETURN) = PR_SetEngineString(r); } static void PF_setattachment(void) { @@ -3757,9 +4107,9 @@ static void PF_setattachment(void) Con_DWarning("PF_setattachment: tag %s not found\n", tagname); } - if ((val = GetEdictFieldValue(ent, pr_extfields.tag_entity))) + if ((val = GetEdictFieldValue(ent, qcvm->extfields.tag_entity))) val->edict = EDICT_TO_PROG(tagent); - if ((val = GetEdictFieldValue(ent, pr_extfields.tag_index))) + if ((val = GetEdictFieldValue(ent, qcvm->extfields.tag_index))) val->_float = 0; } static void PF_void_stub(void) @@ -3767,6 +4117,87 @@ static void PF_void_stub(void) G_FLOAT(OFS_RETURN) = 0; } +static struct svcustomstat_s *PR_CustomStat(int idx, int type) +{ + size_t i; + if (idx < 0 || idx >= MAX_CL_STATS) + return NULL; + switch(type) + { + case ev_ext_integer: + case ev_float: + case ev_vector: + case ev_entity: + break; + default: + return NULL; + } + + for (i = 0; i < sv.numcustomstats; i++) + { + if (sv.customstats[i].idx == idx && (sv.customstats[i].type==ev_string) == (type==ev_string)) + break; + } + if (i == sv.numcustomstats) + sv.numcustomstats++; + sv.customstats[i].idx = idx; + sv.customstats[i].type = type; + sv.customstats[i].fld = 0; + sv.customstats[i].ptr = NULL; + return &sv.customstats[i]; +} +static void PF_clientstat(void) +{ + int idx = G_FLOAT(OFS_PARM0); + int type = G_FLOAT(OFS_PARM1); + int fldofs = G_INT(OFS_PARM2); + struct svcustomstat_s *stat = PR_CustomStat(idx, type); + if (!stat) + return; + stat->fld = fldofs; +} +static void PF_globalstat(void) +{ + int idx = G_FLOAT(OFS_PARM0); + int type = G_FLOAT(OFS_PARM1); + const char *globname = G_STRING(OFS_PARM2); + eval_t *ptr = PR_FindExtGlobal(type, globname); + struct svcustomstat_s *stat; + if (ptr) + { + stat = PR_CustomStat(idx, type); + if (!stat) + return; + stat->ptr = ptr; + } +} +static void PF_pointerstat(void) +{ + int idx = G_FLOAT(OFS_PARM0); + int type = G_FLOAT(OFS_PARM1); + int qcptr = G_INT(OFS_PARM2); + struct svcustomstat_s *stat; + if (qcptr < 0 || qcptr >= qcvm->max_edicts*qcvm->edict_size || (qcptr%qcvm->edict_size)ptr = (eval_t *)((byte *)qcvm->edicts + qcptr); +} + +static void PF_isbackbuffered(void) +{ + unsigned int plnum = G_EDICTNUM(OFS_PARM0)-1; + G_FLOAT(OFS_RETURN) = true; //assume the connection is clogged. + if (plnum > (unsigned int)svs.maxclients) + return; //make error? + if (!svs.clients[plnum].active) + return; //empty slot + if (svs.clients[plnum].message.cursize > DATAGRAM_MTU) + return; + G_FLOAT(OFS_RETURN) = false; //okay to spam with more reliables. +} + #ifdef PSET_SCRIPT int PF_SV_ForceParticlePrecache(const char *s) { @@ -3795,9 +4226,7 @@ static void PF_sv_particleeffectnum(void) { const char *s; unsigned int i; -#ifdef PSET_SCRIPT extern cvar_t r_particledesc; -#endif s = G_STRING(OFS_PARM0); G_FLOAT(OFS_RETURN) = 0; @@ -3806,10 +4235,8 @@ static void PF_sv_particleeffectnum(void) if (!*s) return; -#ifdef PSET_SCRIPT if (!sv.particle_precache[1] && (!strncmp(s, "effectinfo.", 11) || strstr(r_particledesc.string, "effectinfo"))) COM_Effectinfo_Enumerate(PF_SV_ForceParticlePrecache); -#endif for (i = 1; i < MAX_PARTICLETYPES; i++) { @@ -3851,7 +4278,7 @@ static void PF_sv_trailparticles(void) float *end = G_VECTOR(OFS_PARM3); /*DP gets this wrong, lets try to be compatible*/ - if (G_INT(OFS_PARM1) >= MAX_EDICTS) + if ((unsigned int)G_INT(OFS_PARM1) >= MAX_EDICTS*(unsigned int)qcvm->edict_size) { ednum = G_EDICTNUM(OFS_PARM0); efnum = G_FLOAT(OFS_PARM1); @@ -3881,8 +4308,8 @@ static void PF_sv_pointparticles(void) { int efnum = G_FLOAT(OFS_PARM0); float *org = G_VECTOR(OFS_PARM1); - float *vel = (pr_argc < 3)?vec3_origin:G_VECTOR(OFS_PARM2); - int count = (pr_argc < 4)?1:G_FLOAT(OFS_PARM3); + float *vel = (qcvm->argc < 3)?vec3_origin:G_VECTOR(OFS_PARM2); + int count = (qcvm->argc < 4)?1:G_FLOAT(OFS_PARM3); if (efnum <= 0) return; @@ -3912,14 +4339,706 @@ static void PF_sv_pointparticles(void) MSG_WriteShort(&sv.multicast, count); } - SV_Multicast(MULTICAST_PHS_U, org, 0, PEXT2_REPLACEMENTDELTAS); + SV_Multicast(MULTICAST_PVS_U, org, 0, PEXT2_REPLACEMENTDELTAS); +} + +int PF_CL_ForceParticlePrecache(const char *s) +{ + int i; + + //check if an ssqc one already exists with that name + for (i = 1; i < MAX_PARTICLETYPES; i++) + { + if (!cl.particle_precache[i].name) + break; //nope, no more known + if (!strcmp(cl.particle_precache[i].name, s)) + return i; + } + + //nope, check for a csqc one, and allocate if needed + for (i = 1; i < MAX_PARTICLETYPES; i++) + { + if (!cl.local_particle_precache[i].name) + { + cl.local_particle_precache[i].name = strcpy(Hunk_Alloc(strlen(s)+1), s); //weirdness to avoid issues with tempstrings + cl.local_particle_precache[i].index = PScript_FindParticleType(cl.local_particle_precache[i].name); + return -i; + } + if (!strcmp(cl.local_particle_precache[i].name, s)) + return -i; + } + + //err... too many. bum. + return 0; +} +int PF_CL_GetParticle(int idx) +{ //negatives are csqc-originated particles, positives are ssqc-originated, for consistency allowing networking of particles as identifiers + if (!idx) + return P_INVALID; + if (idx < 0) + { + idx = -idx; + if (idx >= MAX_PARTICLETYPES) + return P_INVALID; + return cl.local_particle_precache[idx].index; + } + else + { + if (idx >= MAX_PARTICLETYPES) + return P_INVALID; + return cl.particle_precache[idx].index; + } +} + +static void PF_cl_particleeffectnum(void) +{ + const char *s; + + s = G_STRING(OFS_PARM0); + G_FLOAT(OFS_RETURN) = 0; +// PR_CheckEmptyString (s); + + if (!*s) + return; + + G_FLOAT(OFS_RETURN) = PF_CL_ForceParticlePrecache(s); + if (!G_FLOAT(OFS_RETURN)) + PR_RunError ("PF_cl_particleeffectnum: overflow"); +} +static void PF_cl_trailparticles(void) +{ + int efnum; + edict_t *ent; + float *start = G_VECTOR(OFS_PARM2); + float *end = G_VECTOR(OFS_PARM3); + + if ((unsigned int)G_INT(OFS_PARM1) >= MAX_EDICTS*(unsigned int)qcvm->edict_size) + { /*DP gets this wrong, lets try to be compatible*/ + ent = G_EDICT(OFS_PARM0); + efnum = G_FLOAT(OFS_PARM1); + } + else + { + efnum = G_FLOAT(OFS_PARM0); + ent = G_EDICT(OFS_PARM1); + } + + if (efnum <= 0) + return; + efnum = PF_CL_GetParticle(efnum); + PScript_ParticleTrail(start, end, efnum, -NUM_FOR_EDICT(ent), NULL, NULL/*&ent->trailstate*/); +} +static void PF_cl_pointparticles(void) +{ + int efnum = G_FLOAT(OFS_PARM0); + float *org = G_VECTOR(OFS_PARM1); + float *vel = (qcvm->argc < 3)?vec3_origin:G_VECTOR(OFS_PARM2); + int count = (qcvm->argc < 4)?1:G_FLOAT(OFS_PARM3); + + if (efnum <= 0) + return; + if (count < 1) + return; + efnum = PF_CL_GetParticle(efnum); + PScript_RunParticleEffectState (org, vel, count, efnum, NULL); } #else #define PF_sv_particleeffectnum PF_void_stub #define PF_sv_trailparticles PF_void_stub #define PF_sv_pointparticles PF_void_stub +#define PF_cl_particleeffectnum PF_void_stub +#define PF_cl_trailparticles PF_void_stub +#define PF_cl_pointparticles PF_void_stub #endif + +static void PF_cl_getstat_int(void) +{ + int stnum = G_FLOAT(OFS_PARM0); + if (stnum < 0 || stnum > countof(cl.stats)) + G_INT(OFS_RETURN) = 0; + else + G_INT(OFS_RETURN) = cl.stats[stnum]; +} +static void PF_cl_getstat_float(void) +{ + int stnum = G_FLOAT(OFS_PARM0); + if (stnum < 0 || stnum > countof(cl.stats)) + G_FLOAT(OFS_RETURN) = 0; + else if (qcvm->argc > 1) + { + int firstbit = G_FLOAT(OFS_PARM1); + int bitcount = G_FLOAT(OFS_PARM2); + G_FLOAT(OFS_RETURN) = (cl.stats[stnum]>>firstbit) & ((1<= MAX_QPATH) + return NULL; //too long. get lost. + + if (cachetype < 0) + return NULL; //its a query, not actually needed. + + if (numqcpics+1 > maxqcpics) + { + maxqcpics = numqcpics + 32; + qcpics = realloc(qcpics, maxqcpics * sizeof(*qcpics)); + } + + strcpy(qcpics[numqcpics].name, picname); + qcpics[numqcpics].type = cachetype; + qcpics[numqcpics].pic = NULL; + + //try to load it from a wad if applicable. + //the extra gfx/ crap is because DP insists on it for wad images. and its a nightmare to get things working in all engines if we don't accept that quirk too. + if (cachetype == 1) + qcpics[numqcpics].pic = Draw_PicFromWad(picname + (strncmp(picname, "gfx/", 4)?0:4)); + else if (!strncmp(picname, "gfx/", 4) && !strchr(picname+4, '.')) + qcpics[numqcpics].pic = Draw_PicFromWad(picname+4); + + //okay, not a wad pic, try and load a lmp/tga/etc + if (!qcpics[numqcpics].pic) + qcpics[numqcpics].pic = Draw_TryCachePic(picname); + + return qcpics[numqcpics++].pic; +} +static void DrawQC_CharacterQuad (float x, float y, int num, float w, float h) +{ + float size = 0.0625; + float frow = (num>>4)*size; + float fcol = (num&15)*size; + size = 0.0624; //avoid rounding errors... + + glTexCoord2f (fcol, frow); + glVertex2f (x, y); + glTexCoord2f (fcol + size, frow); + glVertex2f (x+w, y); + glTexCoord2f (fcol + size, frow + size); + glVertex2f (x+w, y+h); + glTexCoord2f (fcol, frow + size); + glVertex2f (x, y+h); +} +static void PF_cl_drawcharacter(void) +{ + extern gltexture_t *char_texture; + + float *pos = G_VECTOR(OFS_PARM0); + int charcode= (int)G_FLOAT (OFS_PARM1) & 0xff; + float *size = G_VECTOR(OFS_PARM2); + float *rgb = G_VECTOR(OFS_PARM3); + float alpha = G_FLOAT (OFS_PARM4); +// int flags = G_FLOAT (OFS_PARM5); + + if (charcode == 32) + return; //don't waste time on spaces + + GL_Bind (char_texture); + glColor4f (rgb[0], rgb[1], rgb[2], alpha); + glBegin (GL_QUADS); + DrawQC_CharacterQuad (pos[0], pos[1], charcode, size[0], size[1]); + glEnd (); +} + +static void PF_cl_drawstring(void) +{ + extern gltexture_t *char_texture; + + float *pos = G_VECTOR(OFS_PARM0); + const unsigned char *text = G_STRING (OFS_PARM1); + float *size = G_VECTOR(OFS_PARM2); + float *rgb = G_VECTOR(OFS_PARM3); + float alpha = G_FLOAT (OFS_PARM4); +// int flags = G_FLOAT (OFS_PARM5); + + float x = pos[0]; + struct markup_s mu; + int c; + + if (!*text) + return; //don't waste time on spaces + + PR_Markup_Begin(&mu, text, rgb, alpha); + + GL_Bind (char_texture); + glBegin (GL_QUADS); + while ((c = PR_Markup_Parse(&mu))) + { + glColor4fv (mu.colour); + DrawQC_CharacterQuad (x, pos[1], c, size[0], size[1]); + x += size[0]; + } + glEnd (); +} +static void PF_cl_stringwidth(void) +{ + static const float defaultfontsize[] = {8,8}; + const unsigned char *text = G_STRING (OFS_PARM0); + qboolean uscolours = G_FLOAT(OFS_PARM1); + const float *fontsize = (qcvm->argc>2)?G_VECTOR (OFS_PARM2):defaultfontsize; + struct markup_s mu; + int r = 0; + + PR_Markup_Begin(&mu, text, vec3_origin, 1); + while (PR_Markup_Parse(&mu)) + { + r += 1; + } + + //primitive and lame, but hey. + G_FLOAT(OFS_RETURN) = fontsize[0] * r; +} + + +static void PF_cl_drawsetclip(void) +{ + float s = CLAMP (1.0, scr_sbarscale.value, (float)glwidth / 320.0); + + float x = G_FLOAT(OFS_PARM0)*s; + float y = G_FLOAT(OFS_PARM1)*s; + float w = G_FLOAT(OFS_PARM2)*s; + float h = G_FLOAT(OFS_PARM3)*s; + + glScissor(x, glheight-(y+h), w, h); + glEnable(GL_SCISSOR_TEST); +} +static void PF_cl_drawresetclip(void) +{ + glDisable(GL_SCISSOR_TEST); +} + +static void PF_cl_precachepic(void) +{ + const char *name = G_STRING(OFS_PARM0); + int trywad = (qcvm->argc>1)?!!G_FLOAT(OFS_PARM1):false; + + G_INT(OFS_RETURN) = G_INT(OFS_PARM0); //return input string, for convienience + + DrawQC_CachePic(name, trywad); +} +static void PF_cl_iscachedpic(void) +{ + const char *name = G_STRING(OFS_PARM0); + if (DrawQC_CachePic(name, -1)) + G_FLOAT(OFS_RETURN) = true; + else + G_FLOAT(OFS_RETURN) = false; +} + +static void PF_cl_drawpic(void) +{ + float *pos = G_VECTOR(OFS_PARM0); + qpic_t *pic = DrawQC_CachePic(G_STRING(OFS_PARM1), false); + float *size = G_VECTOR(OFS_PARM2); + float *rgb = G_VECTOR(OFS_PARM3); + float alpha = G_FLOAT (OFS_PARM4); +// int flags = G_FLOAT (OFS_PARM5); + + if (pic) + { + glColor4f (rgb[0], rgb[1], rgb[2], alpha); + Draw_SubPic (pos[0], pos[1], size[0], size[1], pic, 0, 0, 1, 1); + } +} + +static void PF_cl_getimagesize(void) +{ + qpic_t *pic = DrawQC_CachePic(G_STRING(OFS_PARM0), false); + if (pic) + G_VECTORSET(OFS_RETURN, pic->width, pic->height, 0); + else + G_VECTORSET(OFS_RETURN, 0, 0, 0); +} + +static void PF_cl_drawsubpic(void) +{ + float *pos = G_VECTOR(OFS_PARM0); + float *size = G_VECTOR(OFS_PARM1); + qpic_t *pic = DrawQC_CachePic(G_STRING(OFS_PARM2), false); + float *srcpos = G_VECTOR(OFS_PARM3); + float *srcsize = G_VECTOR(OFS_PARM4); + float *rgb = G_VECTOR(OFS_PARM5); + float alpha = G_FLOAT (OFS_PARM6); +// int flags = G_FLOAT (OFS_PARM7); + + if (pic) + { + glColor4f (rgb[0], rgb[1], rgb[2], alpha); + Draw_SubPic (pos[0], pos[1], size[0], size[1], pic, srcpos[0], srcpos[1], srcsize[0], srcsize[1]); + } +} + +static void PF_cl_drawfill(void) +{ + float *pos = G_VECTOR(OFS_PARM0); + float *size = G_VECTOR(OFS_PARM1); + float *rgb = G_VECTOR(OFS_PARM2); + float alpha = G_FLOAT (OFS_PARM3); +// int flags = G_FLOAT (OFS_PARM4); + + glDisable (GL_TEXTURE_2D); + + glColor4f (rgb[0], rgb[1], rgb[2], alpha); + + glBegin (GL_QUADS); + glVertex2f (pos[0], pos[1]); + glVertex2f (pos[0]+size[0], pos[1]); + glVertex2f (pos[0]+size[0], pos[1]+size[1]); + glVertex2f (pos[0], pos[1]+size[1]); + glEnd (); + + glEnable (GL_TEXTURE_2D); +} + +static void PF_cl_cprint(void) +{ + const char *str = PF_VarString(0); + SCR_CenterPrint (str); +} +static void PF_cl_keynumtostring(void) +{ + int keynum = Key_QCToNative(G_FLOAT(OFS_PARM0)); + char *s = PR_GetTempString(); + if (keynum < 0) + keynum = -1; + Q_strncpy(s, Key_KeynumToString(keynum), STRINGTEMP_LENGTH); + G_INT(OFS_RETURN) = PR_SetEngineString(s); +} +static void PF_cl_stringtokeynum(void) +{ + const char *keyname = G_STRING(OFS_PARM0); + G_FLOAT(OFS_RETURN) = Key_NativeToQC(Key_StringToKeynum(keyname)); +} +static void PF_cl_getkeybind(void) +{ + int keynum = Key_QCToNative(G_FLOAT(OFS_PARM0)); + + char *s = PR_GetTempString(); + if (keynum >= 0 && keynum < MAX_KEYS) + Q_strncpy(s, keybindings[keynum], STRINGTEMP_LENGTH); + else + Q_strncpy(s, "", STRINGTEMP_LENGTH); + G_INT(OFS_RETURN) = PR_SetEngineString(s); +} +static void PF_cl_findkeysforcommand(void) +{ + const char *command = G_STRING(OFS_PARM0); +// float bindmap = G_FLOAT(OFS_PARM1); + int keys[5]; + char gah[64]; + char *s = PR_GetTempString(); + int count = 0; + int j; + int l = strlen(command); + const char *b; + + for (j = 0; j < MAX_KEYS; j++) + { + b = keybindings[j]; + if (!b) + continue; + if (!strncmp (b, command, l) && (!b[l] || (!strchr(command, ' ') && (b[l] == ' ' || b[l] == '\t')))) + { + keys[count++] = Key_NativeToQC(j); + if (count == countof(keys)) + break; + } + } + + while (count < 2) + keys[count++] = -1; //always return at least two keys. DP behaviour. + + *s = 0; + for (j = 0; j < count; j++) + { + if (*s) + q_strlcat(s, " ", STRINGTEMP_LENGTH); + + //FIXME: This should be "'%i'", but our tokenize builtin doesn't support DP's fuckage, so this tends to break everything. So I'm just going to keep things sane by being less compatible-but-cleaner here. + q_snprintf(gah, sizeof(gah), "%i", keys[j]); + q_strlcat(s, gah, STRINGTEMP_LENGTH); + } + + G_INT(OFS_RETURN) = PR_SetEngineString(s); +} +//this extended version returns actual key names. which modifiers can be returned. +static void PF_cl_findkeysforcommandex(void) +{ + const char *command = G_STRING(OFS_PARM0); +// float bindmap = G_FLOAT(OFS_PARM1); + int keys[16]; + char *s = PR_GetTempString(); + int count = 0; + int j; + int l = strlen(command); + const char *b; + + for (j = 0; j < MAX_KEYS; j++) + { + b = keybindings[j]; + if (!b) + continue; + if (!strncmp (b, command, l) && (!b[l] || (!strchr(command, ' ') && (b[l] == ' ' || b[l] == '\t')))) + { + keys[count++] = j; + if (count == countof(keys)) + break; + } + } + + *s = 0; + for (j = 0; j < count; j++) + { + if (*s) + q_strlcat(s, " ", STRINGTEMP_LENGTH); + + q_strlcat(s, Key_KeynumToString(keys[j]), STRINGTEMP_LENGTH); + } + + G_INT(OFS_RETURN) = PR_SetEngineString(s); +} + +static void PF_cl_setcursormode(void) +{ + qboolean absmode = G_FLOAT(OFS_PARM0); +// const char *cursorname = (qcvm->argc<=1)?"":G_STRING(OFS_PARM1); +// float *hotspot = (qcvm->argc<=2)?NULL:G_VECTOR(OFS_PARM2); +// float cursorscale = (qcvm->argc<=3)?1:G_FLOAT(OFS_PARM3); + + cl.csqc_cursorforced = absmode; +} +static void PF_cl_getcursormode(void) +{ +// qboolean effectivemode = (qcvm->argc==0)?false:G_FLOAT(OFS_PARM0); + + G_FLOAT(OFS_RETURN) = cl.csqc_cursorforced; +} +static void PF_cl_setsensitivity(void) +{ + float sens = G_FLOAT(OFS_PARM0); + + cl.csqc_sensitivity = sens; +} +void PF_cl_playerkey_internal(int player, const char *key, qboolean retfloat) +{ + char buf[64]; + char *ret = buf; + extern int fragsort[MAX_SCOREBOARD]; + extern int scoreboardlines; + extern int Sbar_ColorForMap (int m); + if (player < 0 && player >= -scoreboardlines) + player = fragsort[-1-player]; + if (player < 0 || player > MAX_SCOREBOARD) + ret = NULL; + else if (!strcmp(key, "viewentity")) + q_snprintf(buf, sizeof(buf), "%i", player+1); //hack for DP compat. always returned even when the slot is empty (so long as its valid). + else if (!*cl.scores[player].name) + ret = NULL; + else if (!strcmp(key, "name")) + ret = cl.scores[player].name; + else if (!strcmp(key, "frags")) + q_snprintf(buf, sizeof(buf), "%i", cl.scores[player].frags); + else if (!strcmp(key, "ping")) + q_snprintf(buf, sizeof(buf), "%i", cl.scores[player].ping); + else if (!strcmp(key, "pl")) + ret = NULL; //unknown + else if (!strcmp(key, "entertime")) + q_snprintf(buf, sizeof(buf), "%g", cl.scores[player].entertime); + else if (!strcmp(key, "topcolor_rgb")) + { + byte *pal = (byte *)d_8to24table + 4*Sbar_ColorForMap((cl.scores[player].colors)&0xf0); //johnfitz -- use d_8to24table instead of host_basepal + q_snprintf(buf, sizeof(buf), "%g %g %g", pal[0]/255.0, pal[1]/255.0, pal[2]/255.0); + } + else if (!strcmp(key, "bottomcolor_rgb")) + { + byte *pal = (byte *)d_8to24table + 4*Sbar_ColorForMap((cl.scores[player].colors<<4)&0xf0); //johnfitz -- use d_8to24table instead of host_basepal + q_snprintf(buf, sizeof(buf), "%g %g %g", pal[0]/255.0, pal[1]/255.0, pal[2]/255.0); + } + else if (!strcmp(key, "topcolor")) + q_snprintf(buf, sizeof(buf), "%i", (cl.scores[player].colors>>4)&0xf); + else if (!strcmp(key, "bottomcolor")) + q_snprintf(buf, sizeof(buf), "%i", cl.scores[player].colors&0xf); + else if (!strcmp(key, "team")) //quakeworld uses team infokeys to decide teams (instead of colours). but NQ never did, so that's fun. Lets allow mods to use either so that they can favour QW and let the engine hide differences . + q_snprintf(buf, sizeof(buf), "%i", cl.scores[player].colors&0xf); + else if (!strcmp(key, "userid")) + ret = NULL; //unknown +// else if (!strcmp(key, "vignored")) //checks to see this player's voicechat is ignored. +// q_snprintf(buf, sizeof(buf), "%i", (int)cl.scores[player].vignored); + else if (!strcmp(key, "voipspeaking")) + q_snprintf(buf, sizeof(buf), "%i", S_Voip_Speaking(player)); + else if (!strcmp(key, "voiploudness")) + { + if (player == cl.viewentity-1) + q_snprintf(buf, sizeof(buf), "%i", S_Voip_Loudness(false)); + else + ret = NULL; + } + + else + ret = NULL; //no idea. + + if (retfloat) + G_FLOAT(OFS_RETURN) = ret?atof(ret):0; + else + G_INT(OFS_RETURN) = ret?PR_MakeTempString(ret):0; +} +static void PF_cl_playerkey_s(void) +{ + int playernum = G_FLOAT(OFS_PARM0); + const char *keyname = G_STRING(OFS_PARM1); + PF_cl_playerkey_internal(playernum, keyname, false); +} +static void PF_cl_playerkey_f(void) +{ + int playernum = G_FLOAT(OFS_PARM0); + const char *keyname = G_STRING(OFS_PARM1); + PF_cl_playerkey_internal(playernum, keyname, true); +} +static void PF_cl_isdemo(void) +{ + G_FLOAT(OFS_RETURN) = !!cls.demoplayback; +} +static void PF_cl_isserver(void) +{ + G_FLOAT(OFS_RETURN) = !!sv.active; +} +static void PF_cl_registercommand(void) +{ + const char *cmdname = G_STRING(OFS_PARM0); + Cmd_AddCommand(cmdname, NULL); +} +void PF_cl_serverkey_internal(const char *key, qboolean retfloat) +{ + const char *ret; + if (!strcmp(key, "constate")) + { + if (cls.state != ca_connected) + ret = "disconnected"; + else if (cls.signon == SIGNONS) + ret = "active"; + else + ret = "connecting"; + } + else + { + //FIXME + ret = ""; + } + + if (retfloat) + G_FLOAT(OFS_RETURN) = atof(ret); + else + G_INT(OFS_RETURN) = PR_SetEngineString(ret); +} +static void PF_cl_serverkey_s(void) +{ + const char *keyname = G_STRING(OFS_PARM0); + PF_cl_serverkey_internal(keyname, false); +} +static void PF_cl_serverkey_f(void) +{ + const char *keyname = G_STRING(OFS_PARM0); + PF_cl_serverkey_internal(keyname, true); +} + +static void PF_cl_readbyte(void) +{ + G_FLOAT(OFS_RETURN) = MSG_ReadByte(); +} +static void PF_cl_readchar(void) +{ + G_FLOAT(OFS_RETURN) = MSG_ReadChar(); +} +static void PF_cl_readshort(void) +{ + G_FLOAT(OFS_RETURN) = MSG_ReadShort(); +} +static void PF_cl_readlong(void) +{ + G_FLOAT(OFS_RETURN) = MSG_ReadLong(); +} +static void PF_cl_readcoord(void) +{ + G_FLOAT(OFS_RETURN) = MSG_ReadCoord(cl.protocolflags); +} +static void PF_cl_readangle(void) +{ + G_FLOAT(OFS_RETURN) = MSG_ReadAngle(cl.protocolflags); +} +static void PF_cl_readstring(void) +{ + G_INT(OFS_RETURN) = PR_MakeTempString(MSG_ReadString()); +} +static void PF_cl_readfloat(void) +{ + G_FLOAT(OFS_RETURN) = MSG_ReadFloat(); +} +static void PF_cl_readentitynum(void) +{ + G_FLOAT(OFS_RETURN) = MSG_ReadEntity(cl.protocol_pext2); +} +static void PF_cl_sendevent(void) +{ + const char *eventname = G_STRING(OFS_PARM0); + const char *eventargs = G_STRING(OFS_PARM1); + int a; + + MSG_WriteByte(&cls.message, clcfte_qcrequest); + for (a = 2; a < 8 && *eventargs; a++, eventargs++) + { + switch(*eventargs) + { + case 's': + MSG_WriteByte(&cls.message, ev_string); + MSG_WriteString(&cls.message, G_STRING(OFS_PARM0+a*3)); + break; + case 'f': + MSG_WriteByte(&cls.message, ev_float); + MSG_WriteFloat(&cls.message, G_FLOAT(OFS_PARM0+a*3)); + break; + case 'i': + MSG_WriteByte(&cls.message, ev_ext_integer); + MSG_WriteLong(&cls.message, G_INT(OFS_PARM0+a*3)); + break; + case 'v': + MSG_WriteByte(&cls.message, ev_vector); + MSG_WriteFloat(&cls.message, G_FLOAT(OFS_PARM0+a*3+0)); + MSG_WriteFloat(&cls.message, G_FLOAT(OFS_PARM0+a*3+1)); + MSG_WriteFloat(&cls.message, G_FLOAT(OFS_PARM0+a*3+2)); + break; +// case 'e': +// MSG_WriteByte(&cls.message, ev_entity); +// MSG_WriteEntity(&cls.message, ent->v.entnum); +// break; + } + } + MSG_WriteByte(&cls.message, 0); + MSG_WriteString(&cls.message, eventname); +} + +static void PF_cl_setwindowcaption(void) +{ + VID_SetWindowCaption(G_STRING(OFS_PARM0)); +} + //A quick note on number ranges. //0: automatically assigned. more complicated, but no conflicts over numbers, just names... // NOTE: #0 is potentially ambiguous - vanilla will interpret it as instruction 0 (which is normally reserved) rather than a builtin. @@ -3932,215 +5051,379 @@ static void PF_sv_pointparticles(void) static struct { const char *name; - builtin_t func; + builtin_t ssqcfunc; + builtin_t csqcfunc; int documentednumber; const char *typestr; const char *desc; int number; } extensionbuiltins[] = +#define PF_NoSSQC NULL +#define PF_NoCSQC NULL +#define PF_FullCSQCOnly NULL { - {"vectoangles2", PF_ext_vectoangles, 51, D("vector(vector fwd, optional vector up)", "Returns the angles (+x=UP) required to orient an entity to look in the given direction. The 'up' argument is required if you wish to set a roll angle, otherwise it will be limited to just monster-style turning.")}, + {"vectoangles2", PF_ext_vectoangles, PF_ext_vectoangles, 51, D("vector(vector fwd, optional vector up)", "Returns the angles (+x=UP) required to orient an entity to look in the given direction. The 'up' argument is required if you wish to set a roll angle, otherwise it will be limited to just monster-style turning.")}, - {"sin", PF_Sin, 60, "float(float angle)"}, //60 - {"cos", PF_Cos, 61, "float(float angle)"}, //61 - {"sqrt", PF_Sqrt, 62, "float(float value)"}, //62 - {"tracetoss", PF_TraceToss, 64, "void(entity ent, entity ignore)"}, - {"etos", PF_etos, 65, "string(entity ent)"}, + {"sin", PF_Sin, PF_Sin, 60, "float(float angle)"}, //60 + {"cos", PF_Cos, PF_Cos, 61, "float(float angle)"}, //61 + {"sqrt", PF_Sqrt, PF_Sqrt, 62, "float(float value)"}, //62 + {"tracetoss", PF_TraceToss, PF_TraceToss, 64, "void(entity ent, entity ignore)"}, + {"etos", PF_etos, PF_etos, 65, "string(entity ent)"}, - {"infokey", PF_infokey_s, 80, D("string(entity e, string key)", "If e is world, returns the field 'key' from either the serverinfo or the localinfo. If e is a player, returns the value of 'key' from the player's userinfo string. There are a few special exceptions, like 'ip' which is not technically part of the userinfo.")}, //80 - {"infokeyf", PF_infokey_f, 0, D("float(entity e, string key)", "Identical to regular infokey, but returns it as a float instead of creating new tempstrings.")}, //80 - {"stof", PF_stof, 81, "float(string)"}, //81 - {"multicast", PF_multicast, 82, D("#define unicast(pl,reli) do{msg_entity = pl; multicast('0 0 0', reli?MULITCAST_ONE_R:MULTICAST_ONE);}while(0)\n" + {"infokey", PF_infokey_s, PF_NoCSQC, 80, D("string(entity e, string key)", "If e is world, returns the field 'key' from either the serverinfo or the localinfo. If e is a player, returns the value of 'key' from the player's userinfo string. There are a few special exceptions, like 'ip' which is not technically part of the userinfo.")}, //80 + {"infokeyf", PF_infokey_f, PF_NoCSQC, 0, D("float(entity e, string key)", "Identical to regular infokey, but returns it as a float instead of creating new tempstrings.")}, //80 + {"stof", PF_stof, PF_stof, 81, "float(string)"}, //81 + {"multicast", PF_multicast, PF_NoCSQC, 82, D("#define unicast(pl,reli) do{msg_entity = pl; multicast('0 0 0', reli?MULITCAST_ONE_R:MULTICAST_ONE);}while(0)\n" "void(vector where, float set)", "Once the MSG_MULTICAST network message buffer has been filled with data, this builtin is used to dispatch it to the given target, filtering by pvs for reduced network bandwidth.")}, //82 - {"tracebox", PF_tracebox, 90, D("void(vector start, vector mins, vector maxs, vector end, float nomonsters, entity ent)", "Exactly like traceline, but a box instead of a uselessly thin point. Acceptable sizes are limited by bsp format, q1bsp has strict acceptable size values.")}, - {"randomvec", PF_randomvector, 91, D("vector()", "Returns a vector with random values. Each axis is independantly a value between -1 and 1 inclusive.")}, - {"getlight", PF_sv_getlight, 92, "vector(vector org)"},// (DP_QC_GETLIGHT), - {"registercvar", PF_registercvar, 93, D("float(string cvarname, string defaultvalue)", "Creates a new cvar on the fly. If it does not already exist, it will be given the specified value. If it does exist, this is a no-op.\nThis builtin has the limitation that it does not apply to configs or commandlines. Such configs will need to use the set or seta command causing this builtin to be a noop.\nIn engines that support it, you will generally find the autocvar feature easier and more efficient to use.")}, - {"min", PF_min, 94, D("float(float a, float b, ...)", "Returns the lowest value of its arguments.")},// (DP_QC_MINMAXBOUND) - {"max", PF_max, 95, D("float(float a, float b, ...)", "Returns the highest value of its arguments.")},// (DP_QC_MINMAXBOUND) - {"bound", PF_bound, 96, D("float(float minimum, float val, float maximum)", "Returns val, unless minimum is higher, or maximum is less.")},// (DP_QC_MINMAXBOUND) - {"pow", PF_pow, 97, "float(float value, float exp)"}, - {"findfloat", PF_sv_findfloat, 98, D("#define findentity findfloat\nentity(entity start, .__variant fld, __variant match)", "Equivelent to the find builtin, but instead of comparing strings contents, this builtin compares the raw values. This builtin requires multiple calls in order to scan all entities - set start to the previous call's return value.\nworld is returned when there are no more entities.")}, // #98 (DP_QC_FINDFLOAT) - {"checkextension", PF_checkextension, 99, D("float(string extname)", "Checks for an extension by its name (eg: checkextension(\"FRIK_FILE\") says that its okay to go ahead and use strcat).\nUse cvar(\"pr_checkextension\") to see if this builtin exists.")}, // #99 //darkplaces system - query a string to see if the mod supports X Y and Z. - {"checkbuiltin", PF_checkbuiltin, 0, D("float(__variant funcref)", "Checks to see if the specified builtin is supported/mapped. This is intended as a way to check for #0 functions, allowing for simple single-builtin functions.")}, - {"builtin_find", PF_builtinsupported,100,D("float(string builtinname)", "Looks to see if the named builtin is valid, and returns the builtin number it exists at.")}, // #100 //per builtin system. - {"anglemod", PF_anglemod, 102,"float(float value)"}, + {"tracebox", PF_tracebox, NULL, 90, D("void(vector start, vector mins, vector maxs, vector end, float nomonsters, entity ent)", "Exactly like traceline, but a box instead of a uselessly thin point. Acceptable sizes are limited by bsp format, q1bsp has strict acceptable size values.")}, + {"randomvec", PF_randomvector, PF_randomvector, 91, D("vector()", "Returns a vector with random values. Each axis is independantly a value between -1 and 1 inclusive.")}, + {"getlight", PF_sv_getlight, NULL, 92, "vector(vector org)"},// (DP_QC_GETLIGHT), + {"registercvar", PF_registercvar, PF_registercvar, 93, D("float(string cvarname, string defaultvalue)", "Creates a new cvar on the fly. If it does not already exist, it will be given the specified value. If it does exist, this is a no-op.\nThis builtin has the limitation that it does not apply to configs or commandlines. Such configs will need to use the set or seta command causing this builtin to be a noop.\nIn engines that support it, you will generally find the autocvar feature easier and more efficient to use.")}, + {"min", PF_min, PF_min, 94, D("float(float a, float b, ...)", "Returns the lowest value of its arguments.")},// (DP_QC_MINMAXBOUND) + {"max", PF_max, PF_max, 95, D("float(float a, float b, ...)", "Returns the highest value of its arguments.")},// (DP_QC_MINMAXBOUND) + {"bound", PF_bound, PF_bound, 96, D("float(float minimum, float val, float maximum)", "Returns val, unless minimum is higher, or maximum is less.")},// (DP_QC_MINMAXBOUND) + {"pow", PF_pow, PF_pow, 97, "float(float value, float exp)"}, + {"findfloat", PF_findfloat, PF_findfloat, 98, D("#define findentity findfloat\nentity(entity start, .__variant fld, __variant match)", "Equivelent to the find builtin, but instead of comparing strings contents, this builtin compares the raw values. This builtin requires multiple calls in order to scan all entities - set start to the previous call's return value.\nworld is returned when there are no more entities.")}, // #98 (DP_QC_FINDFLOAT) + {"checkextension", PF_checkextension, PF_checkextension, 99, D("float(string extname)", "Checks for an extension by its name (eg: checkextension(\"FRIK_FILE\") says that its okay to go ahead and use strcat).\nUse cvar(\"pr_checkextension\") to see if this builtin exists.")}, // #99 //darkplaces system - query a string to see if the mod supports X Y and Z. + {"checkbuiltin", PF_checkbuiltin, PF_checkbuiltin, 0, D("float(__variant funcref)", "Checks to see if the specified builtin is supported/mapped. This is intended as a way to check for #0 functions, allowing for simple single-builtin functions.")}, + {"builtin_find", PF_builtinsupported,PF_builtinsupported,100,D("float(string builtinname)", "Looks to see if the named builtin is valid, and returns the builtin number it exists at.")}, // #100 //per builtin system. + {"anglemod", PF_anglemod, PF_anglemod, 102,"float(float value)"}, - {"fopen", PF_fopen, 110, D("filestream(string filename, float mode, optional float mmapminsize)", "Opens a file, typically prefixed with \"data/\", for either read or write access.")}, // (FRIK_FILE) - {"fclose", PF_fclose, 111, "void(filestream fhandle)"}, // (FRIK_FILE) - {"fgets", PF_fgets, 112, D("string(filestream fhandle)", "Reads a single line out of the file. The new line character is not returned as part of the string. Returns the null string on EOF (use if not(string) to easily test for this, which distinguishes it from the empty string which is returned if the line being read is blank")}, // (FRIK_FILE) - {"fputs", PF_fputs, 113, D("void(filestream fhandle, string s, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7)", "Writes the given string(s) into the file. For compatibility with fgets, you should ensure that the string is terminated with a \\n - this will not otherwise be done for you. It is up to the engine whether dos or unix line endings are actually written.")}, // (FRIK_FILE) -// {"fread", PF_fread, 0, D("int(filestream fhandle, void *ptr, int size)", "Reads binary data out of the file. Returns truncated lengths if the read exceeds the length of the file.")}, -// {"fwrite", PF_fwrite, 0, D("int(filestream fhandle, void *ptr, int size)", "Writes binary data out of the file.")}, - {"fseek", PF_fseek, 0, D("#define ftell fseek //c-compat\nint(filestream fhandle, optional int newoffset)", "Changes the current position of the file, if specified. Returns prior position, in bytes.")}, -// {"fsize", PF_fsize, 0, D("int(filestream fhandle, optional int newsize)", "Reports the total size of the file, in bytes. Can also be used to truncate/extend the file")}, - {"strlen", PF_strlen, 114, "float(string s)"}, // (FRIK_FILE) - {"strcat", PF_strcat, 115, "string(string s1, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7, optional string s8)"}, // (FRIK_FILE) - {"substring", PF_substring, 116, "string(string s, float start, float length)"}, // (FRIK_FILE) - {"stov", PF_stov, 117, "vector(string s)"}, // (FRIK_FILE) - {"strzone", PF_strzone, 118, D("string(string s, ...)", "Create a semi-permanent copy of a string that only becomes invalid once strunzone is called on the string (instead of when the engine assumes your string has left scope).")}, // (FRIK_FILE) - {"strunzone", PF_strunzone, 119, D("void(string s)", "Destroys a string that was allocated by strunzone. Further references to the string MAY crash the game.")}, // (FRIK_FILE) + {"fopen", PF_fopen, PF_fopen, 110, D("filestream(string filename, float mode, optional float mmapminsize)", "Opens a file, typically prefixed with \"data/\", for either read or write access.")}, // (FRIK_FILE) + {"fclose", PF_fclose, PF_fclose, 111, "void(filestream fhandle)"}, // (FRIK_FILE) + {"fgets", PF_fgets, PF_fgets, 112, D("string(filestream fhandle)", "Reads a single line out of the file. The new line character is not returned as part of the string. Returns the null string on EOF (use if not(string) to easily test for this, which distinguishes it from the empty string which is returned if the line being read is blank")}, // (FRIK_FILE) + {"fputs", PF_fputs, PF_fputs, 113, D("void(filestream fhandle, string s, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7)", "Writes the given string(s) into the file. For compatibility with fgets, you should ensure that the string is terminated with a \\n - this will not otherwise be done for you. It is up to the engine whether dos or unix line endings are actually written.")}, // (FRIK_FILE) +// {"fread", PF_fread, PF_fread, 0, D("int(filestream fhandle, void *ptr, int size)", "Reads binary data out of the file. Returns truncated lengths if the read exceeds the length of the file.")}, +// {"fwrite", PF_fwrite, PF_fwrite, 0, D("int(filestream fhandle, void *ptr, int size)", "Writes binary data out of the file.")}, + {"fseek", PF_fseek, PF_fseek, 0, D("#define ftell fseek //c-compat\nint(filestream fhandle, optional int newoffset)", "Changes the current position of the file, if specified. Returns prior position, in bytes.")}, +// {"fsize", PF_fsize, PF_fsize, 0, D("int(filestream fhandle, optional int newsize)", "Reports the total size of the file, in bytes. Can also be used to truncate/extend the file")}, + {"strlen", PF_strlen, PF_strlen, 114, "float(string s)"}, // (FRIK_FILE) + {"strcat", PF_strcat, PF_strcat, 115, "string(string s1, optional string s2, optional string s3, optional string s4, optional string s5, optional string s6, optional string s7, optional string s8)"}, // (FRIK_FILE) + {"substring", PF_substring, PF_substring, 116, "string(string s, float start, float length)"}, // (FRIK_FILE) + {"stov", PF_stov, PF_stov, 117, "vector(string s)"}, // (FRIK_FILE) + {"strzone", PF_strzone, PF_strzone, 118, D("string(string s, ...)", "Create a semi-permanent copy of a string that only becomes invalid once strunzone is called on the string (instead of when the engine assumes your string has left scope).")}, // (FRIK_FILE) + {"strunzone", PF_strunzone, PF_strunzone, 119, D("void(string s)", "Destroys a string that was allocated by strunzone. Further references to the string MAY crash the game.")}, // (FRIK_FILE) - {"bitshift", PF_bitshift, 218, "float(float number, float quantity)"}, - {"te_lightningblood",PF_sv_te_lightningblood,219,"void(vector org)"}, - {"strstrofs", PF_strstrofs, 221, D("float(string s1, string sub, optional float startidx)", "Returns the 0-based offset of sub within the s1 string, or -1 if sub is not in s1.\nIf startidx is set, this builtin will ignore matches before that 0-based offset.")}, - {"str2chr", PF_str2chr, 222, D("float(string str, float index)", "Retrieves the character value at offset 'index'.")}, - {"chr2str", PF_chr2str, 223, D("string(float chr, ...)", "The input floats are considered character values, and are concatenated.")}, - {"strconv", PF_strconv, 224, D("string(float ccase, float redalpha, float redchars, string str, ...)", "Converts quake chars in the input string amongst different representations.\nccase specifies the new case for letters.\n 0: not changed.\n 1: forced to lower case.\n 2: forced to upper case.\nredalpha and redchars switch between colour ranges.\n 0: no change.\n 1: Forced white.\n 2: Forced red.\n 3: Forced gold(low) (numbers only).\n 4: Forced gold (high) (numbers only).\n 5+6: Forced to white and red alternately.\nYou should not use this builtin in combination with UTF-8.")}, - {"strpad", PF_strpad, 225, D("string(float pad, string str1, ...)", "Pads the string with spaces, to ensure its a specific length (so long as a fixed-width font is used, anyway). If pad is negative, the spaces are added on the left. If positive the padding is on the right.")}, //will be moved - {"infoadd", PF_infoadd, 226, D("string(infostring old, string key, string value)", "Returns a new tempstring infostring with the named value changed (or added if it was previously unspecified). Key and value may not contain the \\ character.")}, - {"infoget", PF_infoget, 227, D("string(infostring info, string key)", "Reads a named value from an infostring. The returned value is a tempstring")}, -// {"strcmp", PF_strncmp, 228, D("float(string s1, string s2)", "Compares the two strings exactly. s1ofs allows you to treat s2 as a substring to compare against, or should be 0.\nReturns 0 if the two strings are equal, a negative value if s1 appears numerically lower, and positive if s1 appears numerically higher.")}, - {"strncmp", PF_strncmp, 228, D("#define strcmp strncmp\nfloat(string s1, string s2, optional float len, optional float s1ofs, optional float s2ofs)", "Compares up to 'len' chars in the two strings. s1ofs allows you to treat s2 as a substring to compare against, or should be 0.\nReturns 0 if the two strings are equal, a negative value if s1 appears numerically lower, and positive if s1 appears numerically higher.")}, - {"strcasecmp", PF_strncasecmp, 229, D("float(string s1, string s2)", "Compares the two strings without case sensitivity.\nReturns 0 if they are equal. The sign of the return value may be significant, but should not be depended upon.")}, - {"strncasecmp", PF_strncasecmp, 230, D("float(string s1, string s2, float len, optional float s1ofs, optional float s2ofs)", "Compares up to 'len' chars in the two strings without case sensitivity. s1ofs allows you to treat s2 as a substring to compare against, or should be 0.\nReturns 0 if they are equal. The sign of the return value may be significant, but should not be depended upon.")}, - {"strtrim", PF_strtrim, 0, D("string(string s)", "Trims the whitespace from the start+end of the string.")}, - {"te_bloodqw", PF_sv_te_bloodqw, 239, "void(vector org, float count)"}, - {"mod", PF_mod, 245, "float(float a, float n)"}, - {"stoi", PF_stoi, 259, D("int(string)", "Converts the given string into a true integer. Base 8, 10, or 16 is determined based upon the format of the string.")}, - {"itos", PF_itos, 260, D("string(int)", "Converts the passed true integer into a base10 string.")}, - {"stoh", PF_stoh, 261, D("int(string)", "Reads a base-16 string (with or without 0x prefix) as an integer. Bugs out if given a base 8 or base 10 string. :P")}, - {"htos", PF_htos, 262, D("string(int)", "Formats an integer as a base16 string, with leading 0s and no prefix. Always returns 8 characters.")}, - {"ftoi", PF_ftoi, 0, D("int(float)", "Converts the given float into a true integer without depending on extended qcvm instructions.")}, - {"itof", PF_itof, 0, D("float(int)", "Converts the given true integer into a float without depending on extended qcvm instructions.")}, - {"crossproduct", PF_crossproduct, 0, D("#define dotproduct(v1,v2) ((vector)(v1)*(vector)(v2))\nvector(vector v1, vector v2)", "Small helper function to calculate the crossproduct of two vectors.")}, - {"frameforname", PF_frameforname, 276, D("float(float modidx, string framename)", "Looks up a framegroup from a model by name, avoiding the need for hardcoding. Returns -1 on error.")},// (FTE_CSQC_SKELETONOBJECTS) - {"frameduration", PF_frameduration, 277, D("float(float modidx, float framenum)", "Retrieves the duration (in seconds) of the specified framegroup.")},// (FTE_CSQC_SKELETONOBJECTS) - {"WriteFloat", PF_WriteFloat, 280, "void(float buf, float fl)"}, - {"frametoname", PF_frametoname, 284, "string(float modidx, float framenum)"}, - {"checkcommand", PF_checkcommand, 294, D("float(string name)", "Checks to see if the supplied name is a valid command, cvar, or alias. Returns 0 if it does not exist.")}, - {"particleeffectnum",PF_sv_particleeffectnum,335,D("float(string effectname)", "Precaches the named particle effect. If your effect name is of the form 'foo.bar' then particles/foo.cfg will be loaded by the client if foo.bar was not already defined.\nDifferent engines will have different particle systems, this specifies the QC API only.")},// (EXT_CSQC) - {"trailparticles", PF_sv_trailparticles,336, D("void(float effectnum, entity ent, vector start, vector end)", "Draws the given effect between the two named points. If ent is not world, distances will be cached in the entity in order to avoid framerate dependancies. The entity is not otherwise used.")},// (EXT_CSQC), - {"pointparticles", PF_sv_pointparticles,337, D("void(float effectnum, vector origin, optional vector dir, optional float count)", "Spawn a load of particles from the given effect at the given point traveling or aiming along the direction specified. The number of particles are scaled by the count argument.")},// (EXT_CSQC) - {"print", PF_print, 339, D("void(string s, ...)", "Unconditionally print on the local system's console, even in ssqc (doesn't care about the value of the developer cvar).")},//(EXT_CSQC) - {"wasfreed", PF_WasFreed, 353, D("float(entity ent)", "Quickly check to see if the entity is currently free. This function is only valid during the two-second non-reuse window, after that it may give bad results. Try one second to make it more robust.")},//(EXT_CSQC) (should be availabe on server too) +// {"getmodelindex", PF_getmodelindex, PF_getmodelindex, 200, D("float(string modelname, optional float queryonly)", "Acts as an alternative to precache_model(foo);setmodel(bar, foo); return bar.modelindex;\nIf queryonly is set and the model was not previously precached, the builtin will return 0 without needlessly precaching the model.")}, +// {"externcall", PF_externcall, PF_externcall, 201, D("__variant(float prnum, string funcname, ...)", "Directly call a function in a different/same progs by its name.\nprnum=0 is the 'default' or 'main' progs.\nprnum=-1 means current progs.\nprnum=-2 will scan through the active progs and will use the first it finds.")}, +// {"addprogs", PF_addprogs, PF_addprogs, 202, D("float(string progsname)", "Loads an additional .dat file into the current qcvm. The returned handle can be used with any of the externcall/externset/externvalue builtins.\nThere are cvars that allow progs to be loaded automatically.")}, +// {"externvalue", PF_externvalue, PF_externvalue, 203, D("__variant(float prnum, string varname)", "Reads a global in the named progs by the name of that global.\nprnum=0 is the 'default' or 'main' progs.\nprnum=-1 means current progs.\nprnum=-2 will scan through the active progs and will use the first it finds.")}, +// {"externset", PF_externset, PF_externset, 204, D("void(float prnum, __variant newval, string varname)", "Sets a global in the named progs by name.\nprnum=0 is the 'default' or 'main' progs.\nprnum=-1 means current progs.\nprnum=-2 will scan through the active progs and will use the first it finds.")}, +// {"externrefcall", PF_externrefcall, PF_externrefcall, 205, D("__variant(float prnum, void() func, ...)","Calls a function between progs by its reference. No longer needed as direct function calls now switch progs context automatically, and have done for a long time. There is no remaining merit for this function."), true}, +// {"instr", PF_instr, PF_instr, 206, D("float(string input, string token)", "Returns substring(input, strstrpos(input, token), -1), or the null string if token was not found in input. You're probably better off using strstrpos."), true}, +// {"openportal", PF_OpenPortal, PF_OpenPortal, 207, D("void(entity portal, float state)", "Opens or closes the portals associated with a door or some such on q2 or q3 maps. On Q2BSPs, the entity should be the 'func_areaportal' entity - its style field will say which portal to open. On Q3BSPs, the entity is the door itself, the portal will be determined by the two areas found from a preceding setorigin call.")}, +// {"RegisterTempEnt", PF_RegisterTEnt, PF_NoCSQC, 208, "float(float attributes, string effectname, ...)"}, +// {"CustomTempEnt", PF_CustomTEnt, PF_NoCSQC, 209, "void(float type, vector pos, ...)"}, +// {"fork", PF_Fork, PF_Fork, 210, D("float(optional float sleeptime)", "When called, this builtin simply returns. Twice.\nThe current 'thread' will return instantly with a return value of 0. The new 'thread' will return after sleeptime seconds with a return value of 1. See documentation for the 'sleep' builtin for limitations/requirements concerning the new thread. Note that QC should probably call abort in the new thread, as otherwise the function will return to the calling qc function twice also.")}, +// {"abort", PF_Abort, PF_Abort, 211, D("void(optional __variant ret)", "QC execution is aborted. Parent QC functions on the stack will be skipped, effectively this forces all QC functions to 'return ret' until execution returns to the engine. If ret is ommited, it is assumed to be 0.")}, +// {"sleep", PF_Sleep, PF_Sleep, 212, D("void(float sleeptime)", "Suspends the current QC execution thread for 'sleeptime' seconds.\nOther QC functions can and will be executed in the interim, including changing globals and field state (but not simultaneously).\nThe self and other globals will be restored when the thread wakes up (or set to world if they were removed since the thread started sleeping). Locals will be preserved, but will not be protected from remove calls.\nIf the engine is expecting the QC to return a value (even in the parent/root function), the value 0 shall be used instead of waiting for the qc to resume.")}, +// {"forceinfokey", PF_ForceInfoKey, PF_NoCSQC, 213, D("void(entity player, string key, string value)", "Directly changes a user's info without pinging off the client. Also allows explicitly setting * keys, including *spectator. Does not affect the user's config or other servers.")}, +// {"chat", PF_chat, PF_NoCSQC, 214, "void(string filename, float starttag, entity edict)"}, //(FTE_NPCCHAT) +// {"particle2", PF_sv_particle2, PF_cl_particle2, 215, "void(vector org, vector dmin, vector dmax, float colour, float effect, float count)"}, +// {"particle3", PF_sv_particle3, PF_cl_particle3, 216, "void(vector org, vector box, float colour, float effect, float count)"}, +// {"particle4", PF_sv_particle4, PF_cl_particle4, 217, "void(vector org, float radius, float colour, float effect, float count)"}, + {"bitshift", PF_bitshift, PF_bitshift, 218, "float(float number, float quantity)"}, + {"te_lightningblood",PF_sv_te_lightningblood,NULL, 219, "void(vector org)"}, +// {"map_builtin", PF_builtinsupported,PF_builtinsupported,220, D("float(string builtinname, float builtinnum)","Attempts to map the named builtin at a non-standard builtin number. Returns 0 on failure."), true}, //like #100 - takes 2 args. arg0 is builtinname, 1 is number to map to. + {"strstrofs", PF_strstrofs, PF_strstrofs, 221, D("float(string s1, string sub, optional float startidx)", "Returns the 0-based offset of sub within the s1 string, or -1 if sub is not in s1.\nIf startidx is set, this builtin will ignore matches before that 0-based offset.")}, + {"str2chr", PF_str2chr, PF_str2chr, 222, D("float(string str, float index)", "Retrieves the character value at offset 'index'.")}, + {"chr2str", PF_chr2str, PF_chr2str, 223, D("string(float chr, ...)", "The input floats are considered character values, and are concatenated.")}, + {"strconv", PF_strconv, PF_strconv, 224, D("string(float ccase, float redalpha, float redchars, string str, ...)", "Converts quake chars in the input string amongst different representations.\nccase specifies the new case for letters.\n 0: not changed.\n 1: forced to lower case.\n 2: forced to upper case.\nredalpha and redchars switch between colour ranges.\n 0: no change.\n 1: Forced white.\n 2: Forced red.\n 3: Forced gold(low) (numbers only).\n 4: Forced gold (high) (numbers only).\n 5+6: Forced to white and red alternately.\nYou should not use this builtin in combination with UTF-8.")}, + {"strpad", PF_strpad, PF_strpad, 225, D("string(float pad, string str1, ...)", "Pads the string with spaces, to ensure its a specific length (so long as a fixed-width font is used, anyway). If pad is negative, the spaces are added on the left. If positive the padding is on the right.")}, //will be moved + {"infoadd", PF_infoadd, PF_infoadd, 226, D("string(infostring old, string key, string value)", "Returns a new tempstring infostring with the named value changed (or added if it was previously unspecified). Key and value may not contain the \\ character.")}, + {"infoget", PF_infoget, PF_infoget, 227, D("string(infostring info, string key)", "Reads a named value from an infostring. The returned value is a tempstring")}, +// {"strcmp", PF_strncmp, PF_strncmp, 228, D("float(string s1, string s2)", "Compares the two strings exactly. s1ofs allows you to treat s2 as a substring to compare against, or should be 0.\nReturns 0 if the two strings are equal, a negative value if s1 appears numerically lower, and positive if s1 appears numerically higher.")}, + {"strncmp", PF_strncmp, PF_strncmp, 228, D("#define strcmp strncmp\nfloat(string s1, string s2, optional float len, optional float s1ofs, optional float s2ofs)", "Compares up to 'len' chars in the two strings. s1ofs allows you to treat s2 as a substring to compare against, or should be 0.\nReturns 0 if the two strings are equal, a negative value if s1 appears numerically lower, and positive if s1 appears numerically higher.")}, + {"strcasecmp", PF_strncasecmp, PF_strncasecmp, 229, D("float(string s1, string s2)", "Compares the two strings without case sensitivity.\nReturns 0 if they are equal. The sign of the return value may be significant, but should not be depended upon.")}, + {"strncasecmp", PF_strncasecmp, PF_strncasecmp, 230, D("float(string s1, string s2, float len, optional float s1ofs, optional float s2ofs)", "Compares up to 'len' chars in the two strings without case sensitivity. s1ofs allows you to treat s2 as a substring to compare against, or should be 0.\nReturns 0 if they are equal. The sign of the return value may be significant, but should not be depended upon.")}, + {"strtrim", PF_strtrim, PF_strtrim, 0, D("string(string s)", "Trims the whitespace from the start+end of the string.")}, +// {"calltimeofday", PF_calltimeofday, PF_calltimeofday, 231, D("void()", "Asks the engine to instantly call the qc's 'timeofday' function, before returning. For compatibility with mvdsv.\ntimeofday should have the prototype: void(float secs, float mins, float hour, float day, float mon, float year, string strvalue)\nThe strftime builtin is more versatile and less weird.")}, + {"clientstat", PF_clientstat, PF_NoCSQC, 232, D("void(float num, float type, .__variant fld)", "Specifies what data to use in order to send various stats, in a client-specific way.\n'num' should be a value between 32 and 127, other values are reserved.\n'type' must be set to one of the EV_* constants, one of EV_FLOAT, EV_STRING, EV_INTEGER, EV_ENTITY.\nfld must be a reference to the field used, each player will be sent only their own copy of these fields.")}, //EXT_CSQC + {"globalstat", PF_globalstat, PF_NoCSQC, 233, D("void(float num, float type, string name)", "Specifies what data to use in order to send various stats, in a non-client-specific way. num and type are as in clientstat, name however, is the name of the global to read in the form of a string (pass \"foo\").")}, //EXT_CSQC_1 actually + {"pointerstat", PF_pointerstat, PF_NoCSQC, 0, D("void(float num, float type, __variant *address)", "Specifies what data to use in order to send various stats, in a non-client-specific way. num and type are as in clientstat, address however, is the address of the variable you would like to use (pass &foo).")}, + {"isbackbuffered", PF_isbackbuffered, PF_NoCSQC, 234, D("float(entity player)", "Returns if the given player's network buffer will take multiple network frames in order to clear. If this builtin returns non-zero, you should delay or reduce the amount of reliable (and also unreliable) data that you are sending to that client.")}, +// {"rotatevectorsbyangle",PF_rotatevectorsbyangles,PF_rotatevectorsbyangles,235,D("void(vector angle)", "rotates the v_forward,v_right,v_up matrix by the specified angles.")}, // #235 +// {"rotatevectorsbyvectors",PF_rotatevectorsbymatrix,PF_rotatevectorsbymatrix,236,"void(vector fwd, vector right, vector up)"}, // #236 +// {"skinforname", PF_skinforname, PF_skinforname, 237, "float(float mdlindex, string skinname)"}, // #237 +// {"shaderforname", PF_Fixme, PF_Fixme, 238, D("float(string shadername, optional string defaultshader, ...)", "Caches the named shader and returns a handle to it.\nIf the shader could not be loaded from disk (missing file or ruleset_allow_shaders 0), it will be created from the 'defaultshader' string if specified, or a 'skin shader' default will be used.\ndefaultshader if not empty should include the outer {} that you would ordinarily find in a shader.")}, + {"te_bloodqw", PF_sv_te_bloodqw, NULL, 239, "void(vector org, float count)"}, +// {"te_muzzleflash", PF_te_muzzleflash, PF_clte_muzzleflash,0, "void(entity ent)"}, +// {"checkpvs", PF_checkpvs, PF_checkpvs, 240, "float(vector viewpos, entity entity)"}, +// {"matchclientname", PF_matchclient, PF_NoCSQC, 241, "entity(string match, optional float matchnum)"}, +// {"sendpacket", PF_SendPacket, PF_SendPacket, 242, "void(string destaddress, string content)"},// (FTE_QC_SENDPACKET) +// {"rotatevectorsbytag",PF_Fixme, PF_Fixme, 244, "vector(entity ent, float tagnum)"}, + {"mod", PF_mod, PF_mod, 245, "float(float a, float n)"}, + {"stoi", PF_stoi, PF_stoi, 259, D("int(string)", "Converts the given string into a true integer. Base 8, 10, or 16 is determined based upon the format of the string.")}, + {"itos", PF_itos, PF_itos, 260, D("string(int)", "Converts the passed true integer into a base10 string.")}, + {"stoh", PF_stoh, PF_stoh, 261, D("int(string)", "Reads a base-16 string (with or without 0x prefix) as an integer. Bugs out if given a base 8 or base 10 string. :P")}, + {"htos", PF_htos, PF_htos, 262, D("string(int)", "Formats an integer as a base16 string, with leading 0s and no prefix. Always returns 8 characters.")}, + {"ftoi", PF_ftoi, PF_ftoi, 0, D("int(float)", "Converts the given float into a true integer without depending on extended qcvm instructions.")}, + {"itof", PF_itof, PF_itof, 0, D("float(int)", "Converts the given true integer into a float without depending on extended qcvm instructions.")}, +// {"skel_create", PF_skel_create, PF_skel_create, 263, D("float(float modlindex, optional float useabstransforms)", "Allocates a new uninitiaised skeletal object, with enough bone info to animate the given model.\neg: self.skeletonobject = skel_create(self.modelindex);")}, // (FTE_CSQC_SKELETONOBJECTS) +// {"skel_build", PF_skel_build, PF_skel_build, 264, D("float(float skel, entity ent, float modelindex, float retainfrac, float firstbone, float lastbone, optional float addfrac)", "Animation data (according to the entity's frame info) is pulled from the specified model and blended into the specified skeletal object.\nIf retainfrac is set to 0 on the first call and 1 on the others, you can blend multiple animations together according to the addfrac value. The final weight should be 1. Other values will result in scaling and/or other weirdness. You can use firstbone and lastbone to update only part of the skeletal object, to allow legs to animate separately from torso, use 0 for both arguments to specify all, as bones are 1-based.")}, // (FTE_CSQC_SKELETONOBJECTS) +// {"skel_get_numbones",PF_skel_get_numbones,PF_skel_get_numbones,265, D("float(float skel)", "Retrives the number of bones in the model. The valid range is 1<=bone<=numbones.")}, // (FTE_CSQC_SKELETONOBJECTS) +// {"skel_get_bonename",PF_skel_get_bonename,PF_skel_get_bonename,266, D("string(float skel, float bonenum)", "Retrieves the name of the specified bone. Mostly only for debugging.")}, // (FTE_CSQC_SKELETONOBJECTS) (returns tempstring) +// {"skel_get_boneparent",PF_skel_get_boneparent,PF_skel_get_boneparent,267,D("float(float skel, float bonenum)", "Retrieves which bone this bone's position is relative to. Bone 0 refers to the entity's position rather than an actual bone")}, // (FTE_CSQC_SKELETONOBJECTS) +// {"skel_find_bone", PF_skel_find_bone, PF_skel_find_bone, 268, D("float(float skel, string tagname)", "Finds a bone by its name, from the model that was used to create the skeletal object.")}, // (FTE_CSQC_SKELETONOBJECTS) +// {"skel_get_bonerel",PF_skel_get_bonerel,PF_skel_get_bonerel,269, D("vector(float skel, float bonenum)", "Gets the bone position and orientation relative to the bone's parent. Return value is the offset, and v_forward, v_right, v_up contain the orientation.")}, // (FTE_CSQC_SKELETONOBJECTS) (sets v_forward etc) +// {"skel_get_boneabs",PF_skel_get_boneabs,PF_skel_get_boneabs,270, D("vector(float skel, float bonenum)", "Gets the bone position and orientation relative to the entity. Return value is the offset, and v_forward, v_right, v_up contain the orientation.\nUse gettaginfo for world coord+orientation.")}, // (FTE_CSQC_SKELETONOBJECTS) (sets v_forward etc) +// {"skel_set_bone", PF_skel_set_bone, PF_skel_set_bone, 271, D("void(float skel, float bonenum, vector org, optional vector fwd, optional vector right, optional vector up)", "Sets a bone position relative to its parent. If the orientation arguments are not specified, v_forward+v_right+v_up are used instead.")}, // (FTE_CSQC_SKELETONOBJECTS) (reads v_forward etc) +// {"skel_premul_bone",PF_skel_premul_bone,PF_skel_premul_bone,272, D("void(float skel, float bonenum, vector org, optional vector fwd, optional vector right, optional vector up)", "Transforms a single bone by a matrix. You can use makevectors to generate a rotation matrix from an angle.")}, // (FTE_CSQC_SKELETONOBJECTS) (reads v_forward etc) +// {"skel_premul_bones",PF_skel_premul_bones,PF_skel_premul_bones,273, D("void(float skel, float startbone, float endbone, vector org, optional vector fwd, optional vector right, optional vector up)", "Transforms an entire consecutive range of bones by a matrix. You can use makevectors to generate a rotation matrix from an angle, but you'll probably want to divide the angle by the number of bones.")}, // (FTE_CSQC_SKELETONOBJECTS) (reads v_forward etc) +// {"skel_postmul_bone",PF_skel_postmul_bone,PF_skel_postmul_bone,0, D("void(float skel, float bonenum, vector org, optional vector fwd, optional vector right, optional vector up)", "Transforms a single bone by a matrix. You can use makevectors to generate a rotation matrix from an angle.")}, // (FTE_CSQC_SKELETONOBJECTS) (reads v_forward etc) +// {"skel_postmul_bones",PF_skel_postmul_bones,PF_skel_postmul_bones,0,D("void(float skel, float startbone, float endbone, vector org, optional vector fwd, optional vector right, optional vector up)", "Transforms an entire consecutive range of bones by a matrix. You can use makevectors to generate a rotation matrix from an angle, but you'll probably want to divide the angle by the number of bones.")}, // (FTE_CSQC_SKELETONOBJECTS) (reads v_forward etc) +// {"skel_copybones", PF_skel_copybones, PF_skel_copybones, 274, D("void(float skeldst, float skelsrc, float startbone, float entbone)", "Copy bone data from one skeleton directly into another.")}, // (FTE_CSQC_SKELETONOBJECTS) +// {"skel_delete", PF_skel_delete, PF_skel_delete, 275, D("void(float skel)", "Deletes a skeletal object. The actual delete is delayed, allowing the skeletal object to be deleted in an entity's predraw function yet still be valid by the time the addentity+renderscene builtins need it. Also uninstanciates any ragdoll currently in effect on the skeletal object.")}, // (FTE_CSQC_SKELETONOBJECTS) + {"crossproduct", PF_crossproduct, PF_crossproduct, 0, D("#ifndef dotproduct\n#define dotproduct(v1,v2) ((vector)(v1)*(vector)(v2))\n#endif\nvector(vector v1, vector v2)", "Small helper function to calculate the crossproduct of two vectors.")}, +// {"pushmove", PF_pushmove, PF_pushmove, 0, "float(entity pusher, vector move, vector amove)"}, + {"frameforname", PF_frameforname, PF_frameforname, 276, D("float(float modidx, string framename)", "Looks up a framegroup from a model by name, avoiding the need for hardcoding. Returns -1 on error.")},// (FTE_CSQC_SKELETONOBJECTS) + {"frameduration", PF_frameduration, PF_frameduration, 277, D("float(float modidx, float framenum)", "Retrieves the duration (in seconds) of the specified framegroup.")},// (FTE_CSQC_SKELETONOBJECTS) +// {"processmodelevents",PF_processmodelevents,PF_processmodelevents,0,D("void(float modidx, float framenum, __inout float basetime, float targettime, void(float timestamp, int code, string data) callback)", "Calls a callback for each event that has been reached. Basetime is set to targettime.")}, +// {"getnextmodelevent",PF_getnextmodelevent,PF_getnextmodelevent,0, D("float(float modidx, float framenum, __inout float basetime, float targettime, __out int code, __out string data)", "Reports the next event within a model's animation. Returns a boolean if an event was found between basetime and targettime. Writes to basetime,code,data arguments (if an event was found, basetime is set to the event's time, otherwise to targettime).\nWARNING: this builtin cannot deal with multiple events with the same timestamp (only the first will be reported).")}, +// {"getmodeleventidx",PF_getmodeleventidx,PF_getmodeleventidx,0, D("float(float modidx, float framenum, int eventidx, __out float timestamp, __out int code, __out string data)", "Reports an indexed event within a model's animation. Writes to timestamp,code,data arguments on success. Returns false if the animation/event/model was out of range/invalid. Does not consider looping animations (retry from index 0 if it fails and you know that its a looping animation). This builtin is more annoying to use than getnextmodelevent, but can be made to deal with multiple events with the exact same timestamp.")}, +/// {"touchtriggers", PF_touchtriggers, PF_touchtriggers, 279, D("void(optional entity ent, optional vector neworigin)", "Triggers a touch events between self and every SOLID_TRIGGER entity that it is in contact with. This should typically just be the triggers touch functions. Also optionally updates the origin of the moved entity.")},// + {"WriteFloat", PF_WriteFloat, PF_NoCSQC, 280, "void(float buf, float fl)"}, +// {"skel_ragupdate", PF_skel_ragedit, PF_skel_ragedit, 281, D("float(entity skelent, string dollcmd, float animskel)", "Updates the skeletal object attached to the entity according to its origin and other properties.\nif animskel is non-zero, the ragdoll will animate towards the bone state in the animskel skeletal object, otherwise they will pick up the model's base pose which may not give nice results.\nIf dollcmd is not set, the ragdoll will update (this should be done each frame).\nIf the doll is updated without having a valid doll, the model's default .doll will be instanciated.\ncommands:\n doll foo.doll : sets up the entity to use the named doll file\n dollstring TEXT : uses the doll file directly embedded within qc, with that extra prefix.\n cleardoll : uninstanciates the doll without destroying the skeletal object.\n animate 0.5 : specifies the strength of the ragdoll as a whole \n animatebody somebody 0.5 : specifies the strength of the ragdoll on a specific body (0 will disable ragdoll animations on that body).\n enablejoint somejoint 1 : enables (or disables) a joint. Disabling joints will allow the doll to shatter.")}, // (FTE_CSQC_RAGDOLL) +// {"skel_mmap", PF_skel_mmap, PF_skel_mmap, 282, D("float*(float skel)", "Map the bones in VM memory. They can then be accessed via pointers. Each bone is 12 floats, the four vectors interleaved (sadly).")},// (FTE_QC_RAGDOLL) +// {"skel_set_bone_world",PF_skel_set_bone_world,PF_skel_set_bone_world,283,D("void(entity ent, float bonenum, vector org, optional vector angorfwd, optional vector right, optional vector up)", "Sets the world position of a bone within the given entity's attached skeletal object. The world position is dependant upon the owning entity's position. If no orientation argument is specified, v_forward+v_right+v_up are used for the orientation instead. If 1 is specified, it is understood as angles. If 3 are specified, they are the forawrd/right/up vectors to use.")}, + {"frametoname", PF_frametoname, PF_frametoname, 284, "string(float modidx, float framenum)"}, +// {"skintoname", PF_skintoname, PF_skintoname, 285, "string(float modidx, float skin)"}, +// {"resourcestatus", PF_NoSSQC, PF_resourcestatus, 286, D("float(float resourcetype, float tryload, string resourcename)", "resourcetype must be one of the RESTYPE_ constants. Returns one of the RESSTATE_ constants. Tryload 0 is a query only. Tryload 1 will attempt to reload the content if it was flushed.")}, +// {"hash_createtab", PF_hash_createtab, PF_hash_createtab, 287, D("hashtable(float tabsize, optional float defaulttype)", "Creates a hash table object with at least 'tabsize' slots. hash table with index 0 is a game-persistant table and will NEVER be returned by this builtin (except as an error return).")}, +// {"hash_destroytab", PF_hash_destroytab, PF_hash_destroytab, 288, D("void(hashtable table)", "Destroys a hash table object.")}, +// {"hash_add", PF_hash_add, PF_hash_add, 289, D("void(hashtable table, string name, __variant value, optional float typeandflags)", "Adds the given key with the given value to the table.\nIf flags&HASH_REPLACE, the old value will be removed, if not set then multiple values may be added for a single key, they won't overwrite.\nThe type argument describes how the value should be stored and saved to files. While you can claim that all variables are just vectors, being more precise can result in less issues with tempstrings or saved games.")}, +// {"hash_get", PF_hash_get, PF_hash_get, 290, D("__variant(hashtable table, string name, optional __variant deflt, optional float requiretype, optional float index)", "looks up the specified key name in the hash table. returns deflt if key was not found. If stringsonly=1, the return value will be in the form of a tempstring, otherwise it'll be the original value argument exactly as it was. If requiretype is specified, then values not of the specified type will be ignored. Hurrah for multiple types with the same name.")}, +// {"hash_delete", PF_hash_delete, PF_hash_delete, 291, D("__variant(hashtable table, string name)", "removes the named key. returns the value of the object that was destroyed, or 0 on error.")}, +// {"hash_getkey", PF_hash_getkey, PF_hash_getkey, 292, D("string(hashtable table, float idx)", "gets some random key name. add+delete can change return values of this, so don't blindly increment the key index if you're removing all.")}, +// {"hash_getcb", PF_hash_getcb, PF_hash_getcb, 293, D("void(hashtable table, void(string keyname, __variant val) callback, optional string name)", "For each item in the table that matches the name, call the callback. if name is omitted, will enumerate ALL keys."), true}, + {"checkcommand", PF_checkcommand, PF_checkcommand, 294, D("float(string name)", "Checks to see if the supplied name is a valid command, cvar, or alias. Returns 0 if it does not exist.")}, +// {"argescape", PF_argescape, PF_argescape, 295, D("string(string s)", "Marks up a string so that it can be reliably tokenized as a single argument later.")}, +// {"clusterevent", PF_clusterevent, PF_NoCSQC, 0, D("void(string dest, string from, string cmd, string info)", "Only functions in mapcluster mode. Sends an event to whichever server the named player is on. The destination server can then dispatch the event to the client or handle it itself via the SV_ParseClusterEvent entrypoint. If dest is empty, the event is broadcast to ALL servers. If the named player can't be found, the event will be returned to this server with the cmd prefixed with 'error:'.")}, +// {"clustertransfer", PF_clustertransfer, PF_NoCSQC, 0, D("string(entity player, optional string newnode)", "Only functions in mapcluster mode. Initiate transfer of the player to a different node. Can take some time. If dest is specified, returns null on error. Otherwise returns the current/new target node (or null if not transferring).")}, +// {"modelframecount", PF_modelframecount, PF_modelframecount, 0, D("float(float mdlidx)", "Retrieves the number of frames in the specified model.")}, - {"copyentity", PF_copyentity, 400, D("entity(entity from, optional entity to)", "Copies all fields from one entity to another.")},// (DP_QC_COPYENTITY) - {"setcolors", PF_setcolors, 401, D("void(entity ent, float colours)", "Changes a player's colours. The bits 0-3 are the lower/trouser colour, bits 4-7 are the upper/shirt colours.")},//DP_SV_SETCOLOR - {"findchain", PF_sv_findchain, 402, "entity(.string field, string match)"},// (DP_QC_FINDCHAIN) - {"findchainfloat", PF_sv_findchainfloat,403, "entity(.float fld, float match)"},// (DP_QC_FINDCHAINFLOAT) - {"effect", PF_sv_effect, 404, D("void(vector org, string modelname, float startframe, float endframe, float framerate)", "stub. Spawns a self-animating sprite")},// (DP_SV_EFFECT) - {"te_blood", PF_sv_te_blooddp, 405, "void(vector org, vector dir, float count)"},// #405 te_blood - {"te_bloodshower", PF_sv_te_bloodshower, 406, "void(vector mincorner, vector maxcorner, float explosionspeed, float howmany)", "stub."},// (DP_TE_BLOODSHOWER) - {"te_explosionrgb", PF_sv_te_explosionrgb, 407, "void(vector org, vector color)", "stub."},// (DP_TE_EXPLOSIONRGB) - {"te_particlecube", PF_sv_te_particlecube, 408, "void(vector mincorner, vector maxcorner, vector vel, float howmany, float color, float gravityflag, float randomveljitter)", "stub."},// (DP_TE_PARTICLECUBE) - {"te_particlerain", PF_sv_te_particlerain, 409, "void(vector mincorner, vector maxcorner, vector vel, float howmany, float color)"},// (DP_TE_PARTICLERAIN) - {"te_particlesnow", PF_sv_te_particlesnow, 410, "void(vector mincorner, vector maxcorner, vector vel, float howmany, float color)"},// (DP_TE_PARTICLESNOW) - {"te_spark", PF_sv_te_spark, 411, "void(vector org, vector vel, float howmany)", "stub."},// (DP_TE_SPARK) - {"te_gunshotquad", PF_sv_te_gunshotquad, 412, "void(vector org)", "stub."},// (DP_TE_QUADEFFECTS1) - {"te_spikequad", PF_sv_te_spikequad, 413, "void(vector org)", "stub."},// (DP_TE_QUADEFFECTS1) - {"te_superspikequad",PF_sv_te_superspikequad,414, "void(vector org)", "stub."},// (DP_TE_QUADEFFECTS1) - {"te_explosionquad",PF_sv_te_explosionquad,415, "void(vector org)", "stub."},// (DP_TE_QUADEFFECTS1) - {"te_smallflash", PF_sv_te_smallflash, 416, "void(vector org)", "stub."},// (DP_TE_SMALLFLASH) - {"te_customflash", PF_sv_te_customflash, 417, "void(vector org, float radius, float lifetime, vector color)", "stub."},// (DP_TE_CUSTOMFLASH) - {"te_gunshot", PF_sv_te_gunshot, 418, "void(vector org, optional float count)"},// #418 te_gunshot - {"te_spike", PF_sv_te_spike, 419, "void(vector org)"},// #419 te_spike - {"te_superspike", PF_sv_te_superspike,420, "void(vector org)"},// #420 te_superspike - {"te_explosion", PF_sv_te_explosion, 421, "void(vector org)"},// #421 te_explosion - {"te_tarexplosion", PF_sv_te_tarexplosion,422, "void(vector org)"},// #422 te_tarexplosion - {"te_wizspike", PF_sv_te_wizspike, 423, "void(vector org)"},// #423 te_wizspike - {"te_knightspike", PF_sv_te_knightspike,424, "void(vector org)"},// #424 te_knightspike - {"te_lavasplash", PF_sv_te_lavasplash,425, "void(vector org)"},// #425 te_lavasplash - {"te_teleport", PF_sv_te_teleport, 426, "void(vector org)"},// #426 te_teleport - {"te_explosion2", PF_sv_te_explosion2,427, "void(vector org, float color, float colorlength)"},// #427 te_explosion2 - {"te_lightning1", PF_sv_te_lightning1,428, "void(entity own, vector start, vector end)"},// #428 te_lightning1 - {"te_lightning2", PF_sv_te_lightning2,429, "void(entity own, vector start, vector end)"},// #429 te_lightning2 - {"te_lightning3", PF_sv_te_lightning3,430, "void(entity own, vector start, vector end)"},// #430 te_lightning3 - {"te_beam", PF_sv_te_beam, 431, "void(entity own, vector start, vector end)"},// #431 te_beam - {"vectorvectors", PF_vectorvectors, 432, "void(vector dir)"},// (DP_QC_VECTORVECTORS) - {"te_plasmaburn", PF_sv_te_plasmaburn,433, "void(vector org)", "stub."},// (DP_TE_PLASMABURN) - {"getsurfacenumpoints",PF_getsurfacenumpoints,434, "float(entity e, float s)"},// (DP_QC_GETSURFACE) - {"getsurfacepoint",PF_getsurfacepoint, 435, "vector(entity e, float s, float n)"},// (DP_QC_GETSURFACE) - {"getsurfacenormal",PF_getsurfacenormal,436, "vector(entity e, float s)"},// (DP_QC_GETSURFACE) - {"getsurfacetexture",PF_getsurfacetexture,437, "string(entity e, float s)"},// (DP_QC_GETSURFACE) - {"getsurfacenearpoint",PF_getsurfacenearpoint,438, "float(entity e, vector p)"},// (DP_QC_GETSURFACE) - {"getsurfaceclippedpoint",PF_getsurfaceclippedpoint,439, "vector(entity e, float s, vector p)"},// (DP_QC_GETSURFACE) - {"clientcommand", PF_clientcommand, 440, "void(entity e, string s)"},// (KRIMZON_SV_PARSECLIENTCOMMAND) - {"tokenize", PF_Tokenize, 441, "float(string s)"},// (KRIMZON_SV_PARSECLIENTCOMMAND) - {"argv", PF_ArgV, 442, "string(float n)"},// (KRIMZON_SV_PARSECLIENTCOMMAND - {"argc", PF_ArgC, 0, "float()"}, - {"setattachment", PF_setattachment, 443, "void(entity e, entity tagentity, string tagname)", ""},// (DP_GFX_QUAKE3MODELTAGS) - {"search_begin", PF_search_begin, 444, "searchhandle(string pattern, optional float caseinsensitive, optional float quiet)", "stub. initiate a filesystem scan based upon filenames. Be sure to call search_end on the returned handle."}, - {"search_end", PF_search_end, 445, "void(searchhandle handle)", "stub."}, - {"search_getsize", PF_search_getsize, 446, "float(searchhandle handle)", "stub. Retrieves the number of files that were found."}, - {"search_getfilename", PF_search_getfilename, 447, "string(searchhandle handle, float num)", "stub. Retrieves name of one of the files that was found by the initial search."}, - {"search_getfilesize", PF_search_getfilesize, 0, "float(searchhandle handle, float num)", "stub. Retrieves the size of one of the files that was found by the initial search."}, - {"search_getfilemtime", PF_search_getfilemtime, 0, "string(searchhandle handle, float num)", "stub. Retrieves modification time of one of the files in %Y-%m-%d %H:%M:%S format."}, - {"cvar_string", PF_cvar_string, 448, "string(string cvarname)"},//DP_QC_CVAR_STRING - {"findflags", PF_sv_findflags, 449, "entity(entity start, .float fld, float match)"},//DP_QC_FINDFLAGS - {"findchainflags", PF_sv_findchainflags,450, "entity(.float fld, float match)"},//DP_QC_FINDCHAINFLAGS - {"dropclient", PF_dropclient, 453, "void(entity player)"},//DP_SV_BOTCLIENT - {"spawnclient", PF_spawnclient, 454, "entity()", "Spawns a dummy player entity.\nNote that such dummy players will be carried from one map to the next.\nWarning: DP_SV_CLIENTCOLORS DP_SV_CLIENTNAME are not implemented in quakespasm, so use KRIMZON_SV_PARSECLIENTCOMMAND's clientcommand builtin to change the bot's name/colours/skin/team/etc, in the same way that clients would ask."},//DP_SV_BOTCLIENT - {"clienttype", PF_clienttype, 455, "float(entity client)"},//botclient - {"WriteUnterminatedString",PF_WriteString2,456, "void(float target, string str)"}, //writestring but without the null terminator. makes things a little nicer. - {"edict_num", PF_edict_for_num, 459, "entity(float entnum)"},//DP_QC_EDICT_NUM - {"buf_create", PF_buf_create, 460, "strbuf()"},//DP_QC_STRINGBUFFERS - {"buf_del", PF_buf_del, 461, "void(strbuf bufhandle)"},//DP_QC_STRINGBUFFERS - {"buf_getsize", PF_buf_getsize, 462, "float(strbuf bufhandle)"},//DP_QC_STRINGBUFFERS - {"buf_copy", PF_buf_copy, 463, "void(strbuf bufhandle_from, strbuf bufhandle_to)"},//DP_QC_STRINGBUFFERS - {"buf_sort", PF_buf_sort, 464, "void(strbuf bufhandle, float sortprefixlen, float backward)"},//DP_QC_STRINGBUFFERS - {"buf_implode", PF_buf_implode, 465, "string(strbuf bufhandle, string glue)"},//DP_QC_STRINGBUFFERS - {"bufstr_get", PF_bufstr_get, 466, "string(strbuf bufhandle, float string_index)"},//DP_QC_STRINGBUFFERS - {"bufstr_set", PF_bufstr_set, 467, "void(strbuf bufhandle, float string_index, string str)"},//DP_QC_STRINGBUFFERS - {"bufstr_add", PF_bufstr_add, 468, "float(strbuf bufhandle, string str, float order)"},//DP_QC_STRINGBUFFERS - {"bufstr_free", PF_bufstr_free, 469, "void(strbuf bufhandle, float string_index)"},//DP_QC_STRINGBUFFERS +// {"clearscene", PF_NoSSQC, PF_FullCSQCOnly, 300, D("void()", "Forgets all rentities, polygons, and temporary dlights. Resets all view properties to their default values.")},// (EXT_CSQC) +// {"addentities", PF_NoSSQC, PF_FullCSQCOnly, 301, D("void(float mask)", "Walks through all entities effectively doing this:\n if (ent.drawmask&mask){ if (!ent.predaw()) addentity(ent); }\nIf mask&MASK_DELTA, non-csqc entities, particles, and related effects will also be added to the rentity list.\n If mask&MASK_STDVIEWMODEL then the default view model will also be added.")},// (EXT_CSQC) +// {"addentity", PF_NoSSQC, PF_FullCSQCOnly, 302, D("void(entity ent)", "Copies the entity fields into a new rentity for later rendering via addscene.")},// (EXT_CSQC) +// {"removeentity", PF_NoSSQC, PF_FullCSQCOnly, 0, D("void(entity ent)", "Undoes all addentities added to the scene from the given entity, without removing ALL entities (useful for splitscreen/etc, readd modified versions as desired).")},// (EXT_CSQC) +// {"addtrisoup_simple",PF_NoSSQC, PF_FullCSQCOnly, 0, D("typedef float vec2[2];\ntypedef float vec3[3];\ntypedef float vec4[4];\ntypedef struct trisoup_simple_vert_s {vec3 xyz;vec2 st;vec4 rgba;} trisoup_simple_vert_t;\nvoid(string texturename, int flags, struct trisoup_simple_vert_s *verts, int *indexes, int numindexes)", "Adds the specified trisoup into the scene as additional geometry. This permits caching geometry to reduce builtin spam. Indexes are a triangle list (so eg quads will need 6 indicies to form two triangles). NOTE: this is not going to be a speedup over polygons if you're still generating lots of new data every frame.")}, +// {"setproperty", PF_NoSSQC, PF_FullCSQCOnly, 303, D("#define setviewprop setproperty\nfloat(float property, ...)", "Allows you to override default view properties like viewport, fov, and whether the engine hud will be drawn. Different VF_ values have slightly different arguments, some are vectors, some floats.")},// (EXT_CSQC) +// {"renderscene", PF_NoSSQC, PF_FullCSQCOnly, 304, D("void()", "Draws all entities, polygons, and particles on the rentity list (which were added via addentities or addentity), using the various view properties set via setproperty. There is no ordering dependancy.\nThe scene must generally be cleared again before more entities are added, as entities will persist even over to the next frame.\nYou may call this builtin multiple times per frame, but should only be called from CSQC_UpdateView.")},// (EXT_CSQC) +// {"dynamiclight_add",PF_NoSSQC, PF_FullCSQCOnly, 305, D("float(vector org, float radius, vector lightcolours, optional float style, optional string cubemapname, optional float pflags)", "Adds a temporary dlight, ready to be drawn via addscene. Cubemap orientation will be read from v_forward/v_right/v_up.")},// (EXT_CSQC) +// {"R_BeginPolygon", PF_NoSSQC, PF_R_PolygonBegin, 306, D("void(string texturename, optional float flags, optional float is2d)", "Specifies the shader to use for the following polygons, along with optional flags.\nIf is2d, the polygon will be drawn as soon as the EndPolygon call is made, rather than waiting for renderscene. This allows complex 2d effects.")},// (EXT_CSQC_???) +// {"R_PolygonVertex", PF_NoSSQC, PF_R_PolygonVertex, 307, D("void(vector org, vector texcoords, vector rgb, float alpha)", "Specifies a polygon vertex with its various properties.")},// (EXT_CSQC_???) +// {"R_EndPolygon", PF_NoSSQC, PF_R_PolygonEnd, 308, D("void()", "Ends the current polygon. At least 3 verticies must have been specified. You do not need to call beginpolygon if you wish to draw another polygon with the same shader.")}, +// {"getproperty", PF_NoSSQC, PF_FullCSQCOnly, 309, D("#define getviewprop getproperty\n__variant(float property)", "Retrieve a currently-set (typically view) property, allowing you to read the current viewport or other things. Due to cheat protection, certain values may be unretrievable.")},// (EXT_CSQC_1) +// {"unproject", PF_NoSSQC, PF_FullCSQCOnly, 310, D("vector (vector v)", "Transform a 2d screen-space point (with depth) into a 3d world-space point, according the various origin+angle+fov etc settings set via setproperty.")},// (EXT_CSQC) +// {"project", PF_NoSSQC, PF_FullCSQCOnly, 311, D("vector (vector v)", "Transform a 3d world-space point into a 2d screen-space point, according the various origin+angle+fov etc settings set via setproperty.")},// (EXT_CSQC) +// {"drawtextfield", PF_NoSSQC, PF_FullCSQCOnly, 0, D("void(vector pos, vector size, float alignflags, string text)", "Draws a multi-line block of text, including word wrapping and alignment. alignflags bits are RTLB, typically 3.")},// (EXT_CSQC) +// {"drawline", PF_NoSSQC, PF_FullCSQCOnly, 315, D("void(float width, vector pos1, vector pos2, vector rgb, float alpha, optional float drawflag)", "Draws a 2d line between the two 2d points.")},// (EXT_CSQC) + {"iscachedpic", PF_NoSSQC, PF_cl_iscachedpic, 316, D("float(string name)", "Checks to see if the image is currently loaded. Engines might lie, or cache between maps.")},// (EXT_CSQC) + {"precache_pic", PF_NoSSQC, PF_cl_precachepic, 317, D("string(string name, optional float trywad)", "Forces the engine to load the named image. If trywad is specified, the specified name must any lack path and extension.")},// (EXT_CSQC) +// {"r_uploadimage", PF_NoSSQC, PF_FullCSQCOnly, 0, D("void(string imagename, int width, int height, int *pixeldata)", "Updates a texture with the specified rgba data. Will be created if needed.")}, +// {"r_readimage", PF_NoSSQC, PF_FullCSQCOnly, 0, D("int*(string filename, __out int width, __out int height)", "Reads and decodes an image from disk, providing raw pixel data. Returns __NULL__ if the image could not be read for any reason. Use memfree to free the data once you're done with it.")}, + {"drawgetimagesize",PF_NoSSQC, PF_cl_getimagesize, 318, D("#define draw_getimagesize drawgetimagesize\nvector(string picname)", "Returns the dimensions of the named image. Images specified with .lmp should give the original .lmp's dimensions even if texture replacements use a different resolution.")},// (EXT_CSQC) +// {"freepic", PF_NoSSQC, PF_FullCSQCOnly, 319, D("void(string name)", "Tells the engine that the image is no longer needed. The image will appear to be new the next time its needed.")},// (EXT_CSQC) + {"drawcharacter", PF_NoSSQC, PF_cl_drawcharacter,320, D("float(vector position, float character, vector size, vector rgb, float alpha, optional float drawflag)", "Draw the given quake character at the given position.\nIf flag&4, the function will consider the char to be a unicode char instead (or display as a ? if outside the 32-127 range).\nsize should normally be something like '8 8 0'.\nrgb should normally be '1 1 1'\nalpha normally 1.\nSoftware engines may assume the named defaults.\nNote that ALL text may be rescaled on the X axis due to variable width fonts. The X axis may even be ignored completely.")},// (EXT_CSQC, [EXT_CSQC_???]) +// {"drawrawstring", PF_NoSSQC, PF_FullCSQCOnly, 321, D("float(vector position, string text, vector size, vector rgb, float alpha, optional float drawflag)", "Draws the specified string without using any markup at all, even in engines that support it.\nIf UTF-8 is globally enabled in the engine, then that encoding is used (without additional markup), otherwise it is raw quake chars.\nSoftware engines may assume a size of '8 8 0', rgb='1 1 1', alpha=1, flag&3=0, but it is not an error to draw out of the screen.")},// (EXT_CSQC, [EXT_CSQC_???]) + {"drawpic", PF_NoSSQC, PF_cl_drawpic, 322, D("float(vector position, string pic, vector size, vector rgb, float alpha, optional float drawflag)", "Draws an shader within the given 2d screen box. Software engines may omit support for rgb+alpha, but must support rescaling, and must clip to the screen without crashing.")},// (EXT_CSQC, [EXT_CSQC_???]) + {"drawfill", PF_NoSSQC, PF_cl_drawfill, 323, D("float(vector position, vector size, vector rgb, float alpha, optional float drawflag)", "Draws a solid block over the given 2d box, with given colour, alpha, and blend mode (specified via flags).\nflags&3=0 simple blend.\nflags&3=1 additive blend")},// (EXT_CSQC, [EXT_CSQC_???]) + {"drawsetcliparea", PF_NoSSQC, PF_cl_drawsetclip, 324, D("void(float x, float y, float width, float height)", "Specifies a 2d clipping region (aka: scissor test). 2d draw calls will all be clipped to this 2d box, the area outside will not be modified by any 2d draw call (even 2d polygons).")},// (EXT_CSQC_???) + {"drawresetcliparea",PF_NoSSQC, PF_cl_drawresetclip,325, D("void(void)", "Reverts the scissor/clip area to the whole screen.")},// (EXT_CSQC_???) + {"drawstring", PF_NoSSQC, PF_cl_drawstring, 326, D("float(vector position, string text, vector size, vector rgb, float alpha, float drawflag)", "Draws a string, interpreting markup and recolouring as appropriate.")},// #326 + {"stringwidth", PF_NoSSQC, PF_cl_stringwidth, 327, D("float(string text, float usecolours, optional vector fontsize)", "Calculates the width of the screen in virtual pixels. If usecolours is 1, markup that does not affect the string width will be ignored. Will always be decoded as UTF-8 if UTF-8 is globally enabled.\nIf the char size is not specified, '8 8 0' will be assumed.")},// EXT_CSQC_'DARKPLACES' + {"drawsubpic", PF_NoSSQC, PF_cl_drawsubpic, 328, D("void(vector pos, vector sz, string pic, vector srcpos, vector srcsz, vector rgb, float alpha, optional float drawflag)", "Draws a rescaled subsection of an image to the screen.")},// #328 EXT_CSQC_'DARKPLACES' +// {"drawrotpic", PF_NoSSQC, PF_FullCSQCOnly, 0, D("void(vector pivot, vector mins, vector maxs, string pic, vector rgb, float alpha, float angle)", "Draws an image rotating at the pivot. To rotate in the center, use mins+maxs of half the size with mins negated. Angle is in degrees.")}, +// {"drawrotsubpic", PF_NoSSQC, PF_FullCSQCOnly, 0, D("void(vector pivot, vector mins, vector maxs, string pic, vector txmin, vector txsize, vector rgb, vector alphaandangles)", "Overcomplicated draw function for over complicated people. Positions follow drawrotpic, while texture coords follow drawsubpic. Due to argument count limitations in builtins, the alpha value and angles are combined into separate fields of a vector (tip: use fteqcc's [alpha, angle] feature.")}, + {"getstati", PF_NoSSQC, PF_cl_getstat_int, 330, D("#define getstati_punf(stnum) (float)(__variant)getstati(stnum)\nint(float stnum)", "Retrieves the numerical value of the given EV_INTEGER or EV_ENTITY stat. Use getstati_punf if you wish to type-pun a float stat as an int to avoid truncation issues in DP.")},// (EXT_CSQC) + {"getstatf", PF_NoSSQC, PF_cl_getstat_float,331, D("#define getstatbits getstatf\nfloat(float stnum, optional float firstbit, optional float bitcount)", "Retrieves the numerical value of the given EV_FLOAT stat. If firstbit and bitcount are specified, retrieves the upper bits of the STAT_ITEMS stat (converted into a float, so there are no VM dependancies).")},// (EXT_CSQC) +// {"getstats", PF_NoSSQC, PF_cl_getstat_str, 332, D("string(float stnum)", "Retrieves the value of the given EV_STRING stat, as a tempstring.\nOlder engines may use 4 consecutive integer stats, with a limit of 15 chars (yes, really. 15.), but "FULLENGINENAME" uses a separate namespace for string stats and has a much higher length limit.")}, +// {"getplayerstat", PF_NoSSQC, PF_FullCSQCOnly, 0, D("__variant(float playernum, float statnum, float stattype)", "Retrieves a specific player's stat, matching the type specified on the server. This builtin is primarily intended for mvd playback where ALL players are known. For EV_ENTITY, world will be returned if the entity is not in the pvs, use type-punning with EV_INTEGER to get the entity number if you just want to see if its set. STAT_ITEMS should be queried as an EV_INTEGER on account of runes and items2 being packed into the upper bits.")}, + {"setmodelindex", PF_sv_setmodelindex,PF_cl_setmodelindex,333, D("void(entity e, float mdlindex)", "Sets a model by precache index instead of by name. Otherwise identical to setmodel.")},// +// {"modelnameforindex",PF_modelnameforidx,PF_modelnameforidx, 334, D("string(float mdlindex)", "Retrieves the name of the model based upon a precache index. This can be used to reduce csqc network traffic by enabling model matching.")},// + {"particleeffectnum",PF_sv_particleeffectnum,PF_cl_particleeffectnum,335,D("float(string effectname)", "Precaches the named particle effect. If your effect name is of the form 'foo.bar' then particles/foo.cfg will be loaded by the client if foo.bar was not already defined.\nDifferent engines will have different particle systems, this specifies the QC API only.")},// (EXT_CSQC) + {"trailparticles", PF_sv_trailparticles,PF_cl_trailparticles,336, D("void(float effectnum, entity ent, vector start, vector end)", "Draws the given effect between the two named points. If ent is not world, distances will be cached in the entity in order to avoid framerate dependancies. The entity is not otherwise used.")},// (EXT_CSQC), + {"pointparticles", PF_sv_pointparticles,PF_cl_pointparticles,337, D("void(float effectnum, vector origin, optional vector dir, optional float count)", "Spawn a load of particles from the given effect at the given point traveling or aiming along the direction specified. The number of particles are scaled by the count argument.")},// (EXT_CSQC) + {"cprint", PF_NoSSQC, PF_cl_cprint, 338, D("void(string s, ...)", "Print into the center of the screen just as ssqc's centerprint would appear.")},//(EXT_CSQC) + {"print", PF_print, PF_print, 339, D("void(string s, ...)", "Unconditionally print on the local system's console, even in ssqc (doesn't care about the value of the developer cvar).")},//(EXT_CSQC) + {"keynumtostring", NULL, PF_cl_keynumtostring,340, D("string(float keynum)", "Returns a hunam-readable name for the given keycode, as a tempstring.")},// (EXT_CSQC) + {"stringtokeynum", NULL, PF_cl_stringtokeynum,341, D("float(string keyname)", "Looks up the key name in the same way that the bind command would, returning the keycode for that key.")},// (EXT_CSQC) + {"getkeybind", NULL, PF_cl_getkeybind, 342, D("string(float keynum)", "Returns the current binding for the given key (returning only the command executed when no modifiers are pressed).")},// (EXT_CSQC) + {"setcursormode", PF_NoSSQC, PF_cl_setcursormode,343, D("void(float usecursor, optional string cursorimage, optional vector hotspot, optional float scale)", "Pass TRUE if you want the engine to release the mouse cursor (absolute input events + touchscreen mode). Pass FALSE if you want the engine to grab the cursor (relative input events + standard looking). If the image name is specified, the engine will use that image for a cursor (use an empty string to clear it again), in a way that will not conflict with the console. Images specified this way will be hardware accelerated, if supported by the platform/port.")}, + {"getcursormode", PF_NoSSQC, PF_cl_getcursormode,0, D("float(float effective)", "Reports the cursor mode this module previously attempted to use. If 'effective' is true, reports the cursor mode currently active (if was overriden by a different module which has precidence, for instance, or if there is only a touchscreen and no mouse).")}, +// {"getmousepos", PF_NoSSQC, PF_FullCSQCOnly, 344, D("vector()", "Nasty convoluted DP extension. Typically returns deltas instead of positions. Use CSQC_InputEvent for such things in csqc mods.")}, // #344 This is a DP extension +// {"getinputstate", PF_NoSSQC, PF_FullCSQCOnly, 345, D("float(float inputsequencenum)", "Looks up an input frame from the log, setting the input_* globals accordingly.\nThe sequence number range used for prediction should normally be servercommandframe < sequence <= clientcommandframe.\nThe sequence equal to clientcommandframe will change between input frames.")},// (EXT_CSQC) + {"setsensitivityscaler",PF_NoSSQC, PF_cl_setsensitivity,346, D("void(float sens)", "Temporarily scales the player's mouse sensitivity based upon something like zoom, avoiding potential cvar saving and thus corruption.")},// (EXT_CSQC) +// {"runstandardplayerphysics",NULL, PF_FullCSQCOnly, 347, D("void(entity ent)", "Perform the engine's standard player movement prediction upon the given entity using the input_* globals to describe movement.")}, + {"getplayerkeyvalue",NULL, PF_cl_playerkey_s, 348, D("string(float playernum, string keyname)", "Look up a player's userinfo, to discover things like their name, topcolor, bottomcolor, skin, team, *ver.\nAlso includes scoreboard info like frags, ping, pl, userid, entertime, as well as voipspeaking and voiploudness.")},// (EXT_CSQC) + {"getplayerkeyfloat",NULL, PF_cl_playerkey_f, 0, D("float(float playernum, string keyname, optional float assumevalue)", "Cheaper version of getplayerkeyvalue that avoids the need for so many tempstrings.")}, + {"isdemo", PF_NoSSQC, PF_cl_isdemo, 349, D("float()", "Returns if the client is currently playing a demo or not")},// (EXT_CSQC) + {"isserver", PF_NoSSQC, PF_cl_isserver, 350, D("float()", "Returns if the client is acting as the server (aka: listen server)")},//(EXT_CSQC) +// {"SetListener", NULL, PF_FullCSQCOnly, 351, D("void(vector origin, vector forward, vector right, vector up, optional float reverbtype)", "Sets the position of the view, as far as the audio subsystem is concerned. This should be called once per CSQC_UpdateView as it will otherwise revert to default. For reverbtype, see setup_reverb or treat as 'underwater'.")},// (EXT_CSQC) +// {"setup_reverb", PF_NoSSQC, PF_FullCSQCOnly, 0, D("typedef struct {\n\tfloat flDensity;\n\tfloat flDiffusion;\n\tfloat flGain;\n\tfloat flGainHF;\n\tfloat flGainLF;\n\tfloat flDecayTime;\n\tfloat flDecayHFRatio;\n\tfloat flDecayLFRatio;\n\tfloat flReflectionsGain;\n\tfloat flReflectionsDelay;\n\tvector flReflectionsPan;\n\tfloat flLateReverbGain;\n\tfloat flLateReverbDelay;\n\tvector flLateReverbPan;\n\tfloat flEchoTime;\n\tfloat flEchoDepth;\n\tfloat flModulationTime;\n\tfloat flModulationDepth;\n\tfloat flAirAbsorptionGainHF;\n\tfloat flHFReference;\n\tfloat flLFReference;\n\tfloat flRoomRolloffFactor;\n\tint iDecayHFLimit;\n} reverbinfo_t;\nvoid(float reverbslot, reverbinfo_t *reverbinfo, int sizeofreverinfo_t)", "Reconfigures a reverb slot for weird effects. Slot 0 is reserved for no effects. Slot 1 is reserved for underwater effects. Reserved slots will be reinitialised on snd_restart, but can otherwise be changed. These reverb slots can be activated with SetListener. Note that reverb will currently only work when using OpenAL.")}, + {"registercommand", NULL, PF_cl_registercommand,352, D("void(string cmdname)", "Register the given console command, for easy console use.\nConsole commands that are later used will invoke CSQC_ConsoleCommand.")},//(EXT_CSQC) + {"wasfreed", PF_WasFreed, PF_WasFreed, 353, D("float(entity ent)", "Quickly check to see if the entity is currently free. This function is only valid during the two-second non-reuse window, after that it may give bad results. Try one second to make it more robust.")},//(EXT_CSQC) (should be availabe on server too) + {"serverkey", NULL, PF_cl_serverkey_s, 354, D("string(string key)", "Look up a key in the server's public serverinfo string")},// + {"serverkeyfloat", NULL, PF_cl_serverkey_f, 0, D("float(string key, optional float assumevalue)", "Version of serverkey that returns the value as a float (which avoids tempstrings).")},// +// {"getentitytoken", NULL, PF_FullCSQCOnly, 355, D("string(optional string resetstring)", "Grab the next token in the map's entity lump.\nIf resetstring is not specified, the next token will be returned with no other sideeffects.\nIf empty, will reset from the map before returning the first token, probably {.\nIf not empty, will tokenize from that string instead.\nAlways returns tempstrings.")},//; +// {"findfont", PF_NoSSQC, PF_FullCSQCOnly, 356, D("float(string s)", "Looks up a named font slot. Matches the actual font name as a last resort.")},//; +// {"loadfont", PF_NoSSQC, PF_FullCSQCOnly, 357, D("float(string fontname, string fontmaps, string sizes, float slot, optional float fix_scale, optional float fix_voffset)", "too convoluted for me to even try to explain correct usage. Try drawfont = loadfont(\"\", \"cour\", \"16\", -1, 0, 0); to switch to the courier font (optimised for 16 virtual pixels high), if you have the freetype2 library in windows..")}, + {"sendevent", PF_NoSSQC, PF_cl_sendevent, 359, D("void(string evname, string evargs, ...)", "Invoke Cmd_evname_evargs in ssqc. evargs must be a string of initials refering to the types of the arguments to pass. v=vector, e=entity(.entnum field is sent), f=float, i=int. 6 arguments max - you can get more if you pack your floats into vectors.")},// (EXT_CSQC_1) + {"readbyte", PF_NoSSQC, PF_cl_readbyte, 360, "float()"},// (EXT_CSQC) + {"readchar", PF_NoSSQC, PF_cl_readchar, 361, "float()"},// (EXT_CSQC) + {"readshort", PF_NoSSQC, PF_cl_readshort, 362, "float()"},// (EXT_CSQC) + {"readlong", PF_NoSSQC, PF_cl_readlong, 363, "float()"},// (EXT_CSQC) + {"readcoord", PF_NoSSQC, PF_cl_readcoord, 364, "float()"},// (EXT_CSQC) + {"readangle", PF_NoSSQC, PF_cl_readangle, 365, "float()"},// (EXT_CSQC) + {"readstring", PF_NoSSQC, PF_cl_readstring, 366, "string()"},// (EXT_CSQC) + {"readfloat", PF_NoSSQC, PF_cl_readfloat, 367, "float()"},// (EXT_CSQC) + {"readentitynum", PF_NoSSQC, PF_cl_readentitynum,368, "float()"},// (EXT_CSQC) +// {"deltalisten", NULL, PF_FullCSQCOnly, 371, D("float(string modelname, float(float isnew) updatecallback, float flags)", "Specifies a per-modelindex callback to listen for engine-networking entity updates. Such entities are automatically interpolated by the engine (unless flags specifies not to).\nThe various standard entity fields will be overwritten each frame before the updatecallback function is called.")},// (EXT_CSQC_1) +// {"dynamiclight_get",PF_NoSSQC, PF_FullCSQCOnly, 372, D("__variant(float lno, float fld)", "Retrieves a property from the given dynamic/rt light. Return type depends upon the light field requested.")}, +// {"dynamiclight_set",PF_NoSSQC, PF_FullCSQCOnly, 373, D("void(float lno, float fld, __variant value)", "Changes a property on the given dynamic/rt light. Value type depends upon the light field to be changed.")}, +// {"particleeffectquery",PF_NoSSQC, PF_FullCSQCOnly, 374, D("string(float efnum, float body)", "Retrieves either the name or the body of the effect with the given number. The effect body is regenerated from internal state, and can be changed before being reapplied via the localcmd builtin.")}, +// {"adddecal", PF_NoSSQC, PF_FullCSQCOnly, 375, D("void(string shadername, vector origin, vector up, vector side, vector rgb, float alpha)", "Adds a temporary clipped decal shader to the scene, centered at the given point with given orientation. Will be drawn by the next renderscene call, and freed by the next clearscene call.")}, +// {"setcustomskin", PF_NoSSQC, PF_FullCSQCOnly, 376, D("void(entity e, string skinfilename, optional string skindata)", "Sets an entity's skin overrides. These are custom per-entity surface->shader lookups. The skinfilename/data should be in .skin format:\nsurfacename,shadername - makes the named surface use the named shader\nreplace \"surfacename\" \"shadername\" - same.\nqwskin \"foo\" - use an unmodified quakeworld player skin (including crop+repalette rules)\nq1lower 0xff0000 - specify an override for the entity's lower colour, in this case to red\nq1upper 0x0000ff - specify an override for the entity's lower colour, in this case to blue\ncompose \"surfacename\" \"shader\" \"imagename@x,y:w,h$s,t,s2,t2?r,g,b,a\" - compose a skin texture from multiple images.\n The texture is determined to be sufficient to hold the first named image, additional images can be named as extra tokens on the same line.\n Use a + at the end of the line to continue reading image tokens from the next line also, the named shader must use 'map $diffuse' to read the composed texture (compatible with the defaultskin shader).")}, +// {"memalloc", PF_memalloc, PF_memalloc, 384, D("__variant*(int size)", "Allocate an arbitary block of memory")}, +// {"memfree", PF_memfree, PF_memfree, 385, D("void(__variant *ptr)", "Frees a block of memory that was allocated with memfree")}, +// {"memcpy", PF_memcpy, PF_memcpy, 386, D("void(__variant *dst, __variant *src, int size)", "Copys memory from one location to another")}, +// {"memfill8", PF_memfill8, PF_memfill8, 387, D("void(__variant *dst, int val, int size)", "Sets an entire block of memory to a specified value. Pretty much always 0.")}, +// {"memgetval", PF_memgetval, PF_memgetval, 388, D("__variant(__variant *dst, float ofs)", "Looks up the 32bit value stored at a pointer-with-offset.")}, +// {"memsetval", PF_memsetval, PF_memsetval, 389, D("void(__variant *dst, float ofs, __variant val)", "Changes the 32bit value stored at the specified pointer-with-offset.")}, +// {"memptradd", PF_memptradd, PF_memptradd, 390, D("__variant*(__variant *base, float ofs)", "Perform some pointer maths. Woo.")}, +// {"memstrsize", PF_memstrsize, PF_memstrsize, 0, D("float(string s)", "strlen, except ignores utf-8")}, +// {"con_getset", PF_NoSSQC, PF_FullCSQCOnly, 391, D("string(string conname, string field, optional string newvalue)", "Reads or sets a property from a console object. The old value is returned. Iterrate through consoles with the 'next' field. Valid properties: title, name, next, unseen, markup, forceutf8, close, clear, hidden, linecount")}, +// {"con_printf", PF_NoSSQC, PF_FullCSQCOnly, 392, D("void(string conname, string messagefmt, ...)", "Prints onto a named console.")}, +// {"con_draw", PF_NoSSQC, PF_FullCSQCOnly, 393, D("void(string conname, vector pos, vector size, float fontsize)", "Draws the named console.")}, +// {"con_input", PF_NoSSQC, PF_FullCSQCOnly, 394, D("float(string conname, float inevtype, float parama, float paramb, float paramc)", "Forwards input events to the named console. Mouse updates should be absolute only.")}, + {"setwindowcaption",PF_NoSSQC, PF_cl_setwindowcaption,0, D("void(string newcaption)", "Replaces the title of the game window, as seen when task switching or just running in windowed mode.")}, +// {"cvars_haveunsaved",PF_NoSSQC, PF_FullCSQCOnly, 0, D("float()", "Returns true if any archived cvar has an unsaved value.")}, +// {"entityprotection",NULL, NULL, 0, D("float(entity e, float nowreadonly)", "Changes the protection on the specified entity to protect it from further edits from QC. The return value is the previous setting. Note that this can be used to unprotect the world, but doing so long term is not advised as you will no longer be able to detect invalid entity references. Also, world is not networked, so results might not be seen by clients (or in other words, world.avelocity_y=64 is a bad idea).")}, + {"copyentity", PF_copyentity, PF_copyentity, 400, D("entity(entity from, optional entity to)", "Copies all fields from one entity to another.")},// (DP_QC_COPYENTITY) + {"setcolors", PF_setcolors, PF_NoCSQC, 401, D("void(entity ent, float colours)", "Changes a player's colours. The bits 0-3 are the lower/trouser colour, bits 4-7 are the upper/shirt colours.")},//DP_SV_SETCOLOR + {"findchain", PF_findchain, PF_findchain, 402, "entity(.string field, string match)"},// (DP_QC_FINDCHAIN) + {"findchainfloat", PF_findchainfloat, PF_findchainfloat, 403, "entity(.float fld, float match)"},// (DP_QC_FINDCHAINFLOAT) + {"effect", PF_sv_effect, NULL, 404, D("void(vector org, string modelname, float startframe, float endframe, float framerate)", "stub. Spawns a self-animating sprite")},// (DP_SV_EFFECT) + {"te_blood", PF_sv_te_blooddp, NULL, 405, "void(vector org, vector dir, float count)"},// #405 te_blood + {"te_bloodshower", PF_sv_te_bloodshower, NULL, 406, "void(vector mincorner, vector maxcorner, float explosionspeed, float howmany)", "stub."},// (DP_TE_BLOODSHOWER) + {"te_explosionrgb", PF_sv_te_explosionrgb, NULL, 407, "void(vector org, vector color)", "stub."},// (DP_TE_EXPLOSIONRGB) + {"te_particlecube", PF_sv_te_particlecube, NULL, 408, "void(vector mincorner, vector maxcorner, vector vel, float howmany, float color, float gravityflag, float randomveljitter)", "stub."},// (DP_TE_PARTICLECUBE) + {"te_particlerain", PF_sv_te_particlerain, NULL, 409, "void(vector mincorner, vector maxcorner, vector vel, float howmany, float color)"},// (DP_TE_PARTICLERAIN) + {"te_particlesnow", PF_sv_te_particlesnow, NULL, 410, "void(vector mincorner, vector maxcorner, vector vel, float howmany, float color)"},// (DP_TE_PARTICLESNOW) + {"te_spark", PF_sv_te_spark, NULL, 411, "void(vector org, vector vel, float howmany)", "stub."},// (DP_TE_SPARK) + {"te_gunshotquad", PF_sv_te_gunshotquad, NULL, 412, "void(vector org)", "stub."},// (DP_TE_QUADEFFECTS1) + {"te_spikequad", PF_sv_te_spikequad, NULL, 413, "void(vector org)", "stub."},// (DP_TE_QUADEFFECTS1) + {"te_superspikequad",PF_sv_te_superspikequad,NULL, 414, "void(vector org)", "stub."},// (DP_TE_QUADEFFECTS1) + {"te_explosionquad",PF_sv_te_explosionquad, NULL, 415, "void(vector org)", "stub."},// (DP_TE_QUADEFFECTS1) + {"te_smallflash", PF_sv_te_smallflash, NULL, 416, "void(vector org)", "stub."},// (DP_TE_SMALLFLASH) + {"te_customflash", PF_sv_te_customflash, NULL, 417, "void(vector org, float radius, float lifetime, vector color)", "stub."},// (DP_TE_CUSTOMFLASH) + {"te_gunshot", PF_sv_te_gunshot, NULL, 418, "void(vector org, optional float count)"},// #418 te_gunshot + {"te_spike", PF_sv_te_spike, NULL, 419, "void(vector org)"},// #419 te_spike + {"te_superspike", PF_sv_te_superspike,NULL, 420, "void(vector org)"},// #420 te_superspike + {"te_explosion", PF_sv_te_explosion, NULL, 421, "void(vector org)"},// #421 te_explosion + {"te_tarexplosion", PF_sv_te_tarexplosion,NULL, 422, "void(vector org)"},// #422 te_tarexplosion + {"te_wizspike", PF_sv_te_wizspike, NULL, 423, "void(vector org)"},// #423 te_wizspike + {"te_knightspike", PF_sv_te_knightspike,NULL, 424, "void(vector org)"},// #424 te_knightspike + {"te_lavasplash", PF_sv_te_lavasplash,NULL, 425, "void(vector org)"},// #425 te_lavasplash + {"te_teleport", PF_sv_te_teleport, NULL, 426, "void(vector org)"},// #426 te_teleport + {"te_explosion2", PF_sv_te_explosion2,NULL, 427, "void(vector org, float color, float colorlength)"},// #427 te_explosion2 + {"te_lightning1", PF_sv_te_lightning1,NULL, 428, "void(entity own, vector start, vector end)"},// #428 te_lightning1 + {"te_lightning2", PF_sv_te_lightning2,NULL, 429, "void(entity own, vector start, vector end)"},// #429 te_lightning2 + {"te_lightning3", PF_sv_te_lightning3,NULL, 430, "void(entity own, vector start, vector end)"},// #430 te_lightning3 + {"te_beam", PF_sv_te_beam, NULL, 431, "void(entity own, vector start, vector end)"},// #431 te_beam + {"vectorvectors", PF_vectorvectors, PF_vectorvectors, 432, "void(vector dir)"},// (DP_QC_VECTORVECTORS) + {"te_plasmaburn", PF_sv_te_plasmaburn,NULL, 433, "void(vector org)", "stub."},// (DP_TE_PLASMABURN) + {"getsurfacenumpoints",PF_getsurfacenumpoints,PF_getsurfacenumpoints,434,"float(entity e, float s)"},// (DP_QC_GETSURFACE) + {"getsurfacepoint", PF_getsurfacepoint, PF_getsurfacepoint, 435, "vector(entity e, float s, float n)"},// (DP_QC_GETSURFACE) + {"getsurfacenormal",PF_getsurfacenormal,PF_getsurfacenormal,436, "vector(entity e, float s)"},// (DP_QC_GETSURFACE) + {"getsurfacetexture",PF_getsurfacetexture,PF_getsurfacetexture,437, "string(entity e, float s)"},// (DP_QC_GETSURFACE) + {"getsurfacenearpoint",PF_getsurfacenearpoint,PF_getsurfacenearpoint,438,"float(entity e, vector p)"},// (DP_QC_GETSURFACE) + {"getsurfaceclippedpoint",PF_getsurfaceclippedpoint,PF_getsurfaceclippedpoint,439,"vector(entity e, float s, vector p)"},// (DP_QC_GETSURFACE) + {"clientcommand", PF_clientcommand, PF_NoCSQC, 440, "void(entity e, string s)"},// (KRIMZON_SV_PARSECLIENTCOMMAND) + {"tokenize", PF_Tokenize, PF_Tokenize, 441, "float(string s)"},// (KRIMZON_SV_PARSECLIENTCOMMAND) + {"argv", PF_ArgV, PF_ArgV, 442, "string(float n)"},// (KRIMZON_SV_PARSECLIENTCOMMAND + {"argc", PF_ArgC, PF_ArgC, 0, "float()"}, + {"setattachment", PF_setattachment, PF_setattachment, 443, "void(entity e, entity tagentity, string tagname)", ""},// (DP_GFX_QUAKE3MODELTAGS) + {"search_begin", PF_search_begin, PF_search_begin, 444, "searchhandle(string pattern, optional float caseinsensitive, optional float quiet)", "initiate a filesystem scan based upon filenames. Be sure to call search_end on the returned handle."}, + {"search_end", PF_search_end, PF_search_end, 445, "void(searchhandle handle)", ""}, + {"search_getsize", PF_search_getsize, PF_search_getsize, 446, "float(searchhandle handle)", " Retrieves the number of files that were found."}, + {"search_getfilename",PF_search_getfilename,PF_search_getfilename,447,"string(searchhandle handle, float num)", "Retrieves name of one of the files that was found by the initial search."}, +// {"search_getfilesize",PF_search_getfilesize,PF_search_getfilesize,0,"float(searchhandle handle, float num)", "Retrieves the size of one of the files that was found by the initial search."}, +// {"search_getfilemtime",PF_search_getfilemtime,PF_search_getfilemtime,0,"string(searchhandle handle, float num)", "Retrieves modification time of one of the files in %Y-%m-%d %H:%M:%S format."}, + {"cvar_string", PF_cvar_string, PF_cvar_string, 448, "string(string cvarname)"},//DP_QC_CVAR_STRING + {"findflags", PF_findflags, PF_findflags, 449, "entity(entity start, .float fld, float match)"},//DP_QC_FINDFLAGS + {"findchainflags", PF_findchainflags, PF_findchainflags, 450, "entity(.float fld, float match)"},//DP_QC_FINDCHAINFLAGS + {"dropclient", PF_dropclient, PF_NoCSQC, 453, "void(entity player)"},//DP_SV_BOTCLIENT + {"spawnclient", PF_spawnclient, PF_NoCSQC, 454, "entity()", "Spawns a dummy player entity.\nNote that such dummy players will be carried from one map to the next.\nWarning: DP_SV_CLIENTCOLORS DP_SV_CLIENTNAME are not implemented in quakespasm, so use KRIMZON_SV_PARSECLIENTCOMMAND's clientcommand builtin to change the bot's name/colours/skin/team/etc, in the same way that clients would ask."},//DP_SV_BOTCLIENT + {"clienttype", PF_clienttype, PF_NoCSQC, 455, "float(entity client)"},//botclient + {"WriteUnterminatedString",PF_WriteString2,PF_NoCSQC, 456, "void(float target, string str)"}, //writestring but without the null terminator. makes things a little nicer. + {"edict_num", PF_edict_for_num, PF_edict_for_num, 459, "entity(float entnum)"},//DP_QC_EDICT_NUM + {"buf_create", PF_buf_create, PF_buf_create, 460, "strbuf()"},//DP_QC_STRINGBUFFERS + {"buf_del", PF_buf_del, PF_buf_del, 461, "void(strbuf bufhandle)"},//DP_QC_STRINGBUFFERS + {"buf_getsize", PF_buf_getsize, PF_buf_getsize, 462, "float(strbuf bufhandle)"},//DP_QC_STRINGBUFFERS + {"buf_copy", PF_buf_copy, PF_buf_copy, 463, "void(strbuf bufhandle_from, strbuf bufhandle_to)"},//DP_QC_STRINGBUFFERS + {"buf_sort", PF_buf_sort, PF_buf_sort, 464, "void(strbuf bufhandle, float sortprefixlen, float backward)"},//DP_QC_STRINGBUFFERS + {"buf_implode", PF_buf_implode, PF_buf_implode, 465, "string(strbuf bufhandle, string glue)"},//DP_QC_STRINGBUFFERS + {"bufstr_get", PF_bufstr_get, PF_bufstr_get, 466, "string(strbuf bufhandle, float string_index)"},//DP_QC_STRINGBUFFERS + {"bufstr_set", PF_bufstr_set, PF_bufstr_set, 467, "void(strbuf bufhandle, float string_index, string str)"},//DP_QC_STRINGBUFFERS + {"bufstr_add", PF_bufstr_add, PF_bufstr_add, 468, "float(strbuf bufhandle, string str, float order)"},//DP_QC_STRINGBUFFERS + {"bufstr_free", PF_bufstr_free, PF_bufstr_free, 469, "void(strbuf bufhandle, float string_index)"},//DP_QC_STRINGBUFFERS + {"asin", PF_asin, PF_asin, 471, "float(float s)"},//DP_QC_ASINACOSATANATAN2TAN + {"acos", PF_acos, PF_acos, 472, "float(float c)"},//DP_QC_ASINACOSATANATAN2TAN + {"atan", PF_atan, PF_atan, 473, "float(float t)"},//DP_QC_ASINACOSATANATAN2TAN + {"atan2", PF_atan2, PF_atan2, 474, "float(float c, float s)"},//DP_QC_ASINACOSATANATAN2TAN + {"tan", PF_tan, PF_tan, 475, "float(float a)"},//DP_QC_ASINACOSATANATAN2TAN + {"strlennocol", PF_strlennocol, PF_strlennocol, 476, D("float(string s)", "Returns the number of characters in the string after any colour codes or other markup has been parsed.")},//DP_QC_STRINGCOLORFUNCTIONS + {"strdecolorize", PF_strdecolorize, PF_strdecolorize, 477, D("string(string s)", "Flattens any markup/colours, removing them from the string.")},//DP_QC_STRINGCOLORFUNCTIONS + {"strftime", PF_strftime, PF_strftime, 478, "string(float uselocaltime, string format, ...)"}, //DP_QC_STRFTIME + {"tokenizebyseparator",PF_tokenizebyseparator,PF_tokenizebyseparator, 479, "float(string s, string separator1, ...)"}, //DP_QC_TOKENIZEBYSEPARATOR + {"strtolower", PF_strtolower, PF_strtolower, 480, "string(string s)"}, //DP_QC_STRING_CASE_FUNCTIONS + {"strtoupper", PF_strtoupper, PF_strtoupper, 481, "string(string s)"}, //DP_QC_STRING_CASE_FUNCTIONS + {"cvar_defstring", PF_cvar_defstring, PF_cvar_defstring, 482, "string(string s)"}, //DP_QC_CVAR_DEFSTRING + {"pointsound", PF_sv_pointsound, NULL, 483, "void(vector origin, string sample, float volume, float attenuation)"},//DP_SV_POINTSOUND + {"strreplace", PF_strreplace, PF_strreplace, 484, "string(string search, string replace, string subject)"},//DP_QC_STRREPLACE + {"strireplace", PF_strireplace, PF_strireplace, 485, "string(string search, string replace, string subject)"},//DP_QC_STRREPLACE + {"getsurfacepointattribute",PF_getsurfacepointattribute,PF_getsurfacepointattribute, 486, "vector(entity e, float s, float n, float a)"},//DP_QC_GETSURFACEPOINTATTRIBUTE - - - {"asin", PF_asin, 471, "float(float s)"},//DP_QC_ASINACOSATANATAN2TAN - {"acos", PF_acos, 472, "float(float c)"},//DP_QC_ASINACOSATANATAN2TAN - {"atan", PF_atan, 473, "float(float t)"},//DP_QC_ASINACOSATANATAN2TAN - {"atan2", PF_atan2, 474, "float(float c, float s)"},//DP_QC_ASINACOSATANATAN2TAN - {"tan", PF_tan, 475, "float(float a)"},//DP_QC_ASINACOSATANATAN2TAN - {"strlennocol", PF_strlennocol, 476, D("float(string s)", "stub. Returns the number of characters in the string after any colour codes or other markup has been parsed.")},//DP_QC_STRINGCOLORFUNCTIONS - {"strdecolorize", PF_strdecolorize, 477, D("string(string s)", "stub. Flattens any markup/colours, removing them from the string.")},//DP_QC_STRINGCOLORFUNCTIONS - {"strftime", PF_strftime, 478, "string(float uselocaltime, string format, ...)"}, //DP_QC_STRFTIME - {"tokenizebyseparator",PF_tokenizebyseparator,479, "float(string s, string separator1, ...)"}, //DP_QC_TOKENIZEBYSEPARATOR - {"strtolower", PF_strtolower, 480, "string(string s)"}, //DP_QC_STRING_CASE_FUNCTIONS - {"strtoupper", PF_strtoupper, 481, "string(string s)"}, //DP_QC_STRING_CASE_FUNCTIONS - {"cvar_defstring", PF_cvar_defstring, 482, "string(string s)"}, //DP_QC_CVAR_DEFSTRING - {"pointsound", PF_sv_pointsound, 483, "void(vector origin, string sample, float volume, float attenuation)"},//DP_SV_POINTSOUND - {"strreplace", PF_strreplace, 484, "string(string search, string replace, string subject)"},//DP_QC_STRREPLACE - {"strireplace", PF_strireplace, 485, "string(string search, string replace, string subject)"},//DP_QC_STRREPLACE - {"getsurfacepointattribute",PF_getsurfacepointattribute,486, "vector(entity e, float s, float n, float a)"},//DP_QC_GETSURFACEPOINTATTRIBUTE - - {"crc16", PF_crc16, 494, "float(float caseinsensitive, string s, ...)"},//DP_QC_CRC16 - {"cvar_type", PF_cvar_type, 495, "float(string name)"},//DP_QC_CVAR_TYPE - {"numentityfields", PF_numentityfields, 496, D("float()", "Gives the number of named entity fields. Note that this is not the size of an entity, but rather just the number of unique names (ie: vectors use 4 names rather than 3).")},//DP_QC_ENTITYDATA - {"findentityfield", PF_findentityfield, 0, D("float(string fieldname)", "Find a field index by name.")}, - {"entityfieldref", PF_entityfieldref, 0, D("typedef .__variant field_t;\nfield_t(float fieldnum)", "Returns a field value that can be directly used to read entity fields. Be sure to validate the type with entityfieldtype before using.")},//DP_QC_ENTITYDATA - {"entityfieldname", PF_entityfieldname, 497, D("string(float fieldnum)", "Retrieves the name of the given entity field.")},//DP_QC_ENTITYDATA - {"entityfieldtype", PF_entityfieldtype, 498, D("float(float fieldnum)", "Provides information about the type of the field specified by the field num. Returns one of the EV_ values.")},//DP_QC_ENTITYDATA - {"getentityfieldstring",PF_getentityfieldstring,499, "string(float fieldnum, entity ent)"},//DP_QC_ENTITYDATA - {"putentityfieldstring",PF_putentityfieldstring,500, "float(float fieldnum, entity ent, string s)"},//DP_QC_ENTITYDATA - {"whichpack", PF_whichpack, 503, D("string(string filename, optional float makereferenced)", "Returns the pak file name that contains the file specified. progs/player.mdl will generally return something like 'pak0.pak'. If makereferenced is true, clients will automatically be told that the returned package should be pre-downloaded and used, even if allow_download_refpackages is not set.")},//DP_QC_WHICHPACK - {"uri_escape", PF_uri_escape, 510, "string(string in)"},//DP_QC_URI_ESCAPE - {"uri_unescape", PF_uri_unescape, 511, "string(string in)"},//DP_QC_URI_ESCAPE - {"num_for_edict", PF_num_for_edict, 512, "float(entity ent)"},//DP_QC_NUM_FOR_EDICT - {"tokenize_console",PF_tokenize_console,514, D("float(string str)", "Tokenize a string exactly as the console's tokenizer would do so. The regular tokenize builtin became bastardized for convienient string parsing, which resulted in a large disparity that can be exploited to bypass checks implemented in a naive SV_ParseClientCommand function, therefore you can use this builtin to make sure it exactly matches.")}, - {"argv_start_index",PF_argv_start_index,515, D("float(float idx)", "Returns the character index that the tokenized arg started at.")}, - {"argv_end_index", PF_argv_end_index, 516, D("float(float idx)", "Returns the character index that the tokenized arg stopped at.")}, -// {"buf_cvarlist", PF_buf_cvarlist, 517, "void(strbuf strbuf, string pattern, string antipattern)"}, - {"cvar_description",PF_cvar_description,518, D("string(string cvarname)", "Retrieves the description of a cvar, which might be useful for tooltips or help files. This may still not be useful.")}, - {"gettime", PF_gettime, 519, "float(optional float timetype)"}, -// {"loadfromdata", PF_loadfromdata, 529, D("void(string s)", "Reads a set of entities from the given string. This string should have the same format as a .ent file or a saved game. Entities will be spawned as required. If you need to see the entities that were created, you should use parseentitydata instead.")}, -// {"loadfromfile", PF_loadfromfile, 530, D("void(string s)", "Reads a set of entities from the named file. This file should have the same format as a .ent file or a saved game. Entities will be spawned as required. If you need to see the entities that were created, you should use parseentitydata instead.")}, - {"log", PF_Logarithm, 532, D("float(float v, optional float base)", "Determines the logarithm of the input value according to the specified base. This can be used to calculate how much something was shifted by.")}, - {"buf_loadfile", PF_buf_loadfile, 535, D("float(string filename, strbuf bufhandle)", "Appends the named file into a string buffer (which must have been created in advance). The return value merely says whether the file was readable.")}, - {"buf_writefile", PF_buf_writefile, 536, D("float(filestream filehandle, strbuf bufhandle, optional float startpos, optional float numstrings)", "Writes the contents of a string buffer onto the end of the supplied filehandle (you must have already used fopen). Additional optional arguments permit you to constrain the writes to a subsection of the stringbuffer.")}, - {"callfunction", PF_callfunction, 605, D("void(.../*, string funcname*/)", "Invokes the named function. The function name is always passed as the last parameter and must always be present. The others are passed to the named function as-is")}, - {"isfunction", PF_isfunction, 607, D("float(string s)", "Returns true if the named function exists and can be called with the callfunction builtin.")}, - {"parseentitydata", PF_parseentitydata, 613, D("float(entity e, string s, optional float offset)", "Reads a single entity's fields into an already-spawned entity. s should contain field pairs like in a saved game: {\"foo1\" \"bar\" \"foo2\" \"5\"}. Returns <=0 on failure, otherwise returns the offset in the string that was read to.")}, -// {"generateentitydata",PF_generateentitydata,0, D("string(entity e)", "Dumps the entities fields into a string which can later be parsed with parseentitydata."}), - {"sprintf", PF_sprintf, 627, "string(string fmt, ...)"}, - {"getsurfacenumtriangles",PF_getsurfacenumtriangles,628,"float(entity e, float s)"}, - {"getsurfacetriangle",PF_getsurfacetriangle,629,"vector(entity e, float s, float n)"}, -// {"digest_hex", PF_digest_hex, 639, "string(string digest, string data, ...)"}, + {"crc16", PF_crc16, PF_crc16, 494, "float(float caseinsensitive, string s, ...)"},//DP_QC_CRC16 + {"cvar_type", PF_cvar_type, PF_cvar_type, 495, "float(string name)"},//DP_QC_CVAR_TYPE + {"numentityfields", PF_numentityfields, PF_numentityfields, 496, D("float()", "Gives the number of named entity fields. Note that this is not the size of an entity, but rather just the number of unique names (ie: vectors use 4 names rather than 3).")},//DP_QC_ENTITYDATA + {"findentityfield", PF_findentityfield, PF_findentityfield, 0, D("float(string fieldname)", "Find a field index by name.")}, + {"entityfieldref", PF_entityfieldref, PF_entityfieldref, 0, D("typedef .__variant field_t;\nfield_t(float fieldnum)", "Returns a field value that can be directly used to read entity fields. Be sure to validate the type with entityfieldtype before using.")},//DP_QC_ENTITYDATA + {"entityfieldname", PF_entityfieldname, PF_entityfieldname, 497, D("string(float fieldnum)", "Retrieves the name of the given entity field.")},//DP_QC_ENTITYDATA + {"entityfieldtype", PF_entityfieldtype, PF_entityfieldtype, 498, D("float(float fieldnum)", "Provides information about the type of the field specified by the field num. Returns one of the EV_ values.")},//DP_QC_ENTITYDATA + {"getentityfieldstring",PF_getentfldstr,PF_getentfldstr, 499, "string(float fieldnum, entity ent)"},//DP_QC_ENTITYDATA + {"putentityfieldstring",PF_putentfldstr,PF_putentfldstr, 500, "float(float fieldnum, entity ent, string s)"},//DP_QC_ENTITYDATA + {"whichpack", PF_whichpack, PF_whichpack, 503, D("string(string filename, optional float makereferenced)", "Returns the pak file name that contains the file specified. progs/player.mdl will generally return something like 'pak0.pak'. If makereferenced is true, clients will automatically be told that the returned package should be pre-downloaded and used, even if allow_download_refpackages is not set.")},//DP_QC_WHICHPACK + {"uri_escape", PF_uri_escape, PF_uri_escape, 510, "string(string in)"},//DP_QC_URI_ESCAPE + {"uri_unescape", PF_uri_unescape, PF_uri_unescape, 511, "string(string in)"},//DP_QC_URI_ESCAPE + {"num_for_edict", PF_num_for_edict, PF_num_for_edict, 512, "float(entity ent)"},//DP_QC_NUM_FOR_EDICT + {"tokenize_console",PF_tokenize_console,PF_tokenize_console,514, D("float(string str)", "Tokenize a string exactly as the console's tokenizer would do so. The regular tokenize builtin became bastardized for convienient string parsing, which resulted in a large disparity that can be exploited to bypass checks implemented in a naive SV_ParseClientCommand function, therefore you can use this builtin to make sure it exactly matches.")}, + {"argv_start_index",PF_argv_start_index,PF_argv_start_index,515, D("float(float idx)", "Returns the character index that the tokenized arg started at.")}, + {"argv_end_index", PF_argv_end_index, PF_argv_end_index, 516, D("float(float idx)", "Returns the character index that the tokenized arg stopped at.")}, +// {"buf_cvarlist", PF_buf_cvarlist, PF_buf_cvarlist, 517, "void(strbuf strbuf, string pattern, string antipattern)"}, + {"cvar_description",PF_cvar_description,PF_cvar_description,518, D("string(string cvarname)", "Retrieves the description of a cvar, which might be useful for tooltips or help files. This may still not be useful.")}, + {"gettime", PF_gettime, PF_gettime, 519, "float(optional float timetype)"}, + {"findkeysforcommand",PF_NoSSQC, PF_cl_findkeysforcommand,521,D("string(string command, optional float bindmap)", "Returns a list of keycodes that perform the given console command in a format that can only be parsed via tokenize (NOT tokenize_console). This always returns at least two values - if only one key is actually bound, -1 will be returned. The bindmap argument is listed for compatibility with dp-specific defs, but is ignored in FTE.")}, + {"findkeysforcommandex",PF_NoSSQC, PF_cl_findkeysforcommandex,0,D("string(string command, optional float bindmap)", "Returns a list of key bindings in keyname format instead of keynums. Use tokenize to parse. This list may contain modifiers. May return large numbers of keys.")}, +// {"loadfromdata", PF_loadfromdata, PF_loadfromdata, 529, D("void(string s)", "Reads a set of entities from the given string. This string should have the same format as a .ent file or a saved game. Entities will be spawned as required. If you need to see the entities that were created, you should use parseentitydata instead.")}, +// {"loadfromfile", PF_loadfromfile, PF_loadfromfile, 530, D("void(string s)", "Reads a set of entities from the named file. This file should have the same format as a .ent file or a saved game. Entities will be spawned as required. If you need to see the entities that were created, you should use parseentitydata instead.")}, + {"log", PF_Logarithm, PF_Logarithm, 532, D("float(float v, optional float base)", "Determines the logarithm of the input value according to the specified base. This can be used to calculate how much something was shifted by.")}, + {"buf_loadfile", PF_buf_loadfile, NULL, 535, D("float(string filename, strbuf bufhandle)", "Appends the named file into a string buffer (which must have been created in advance). The return value merely says whether the file was readable.")}, + {"buf_writefile", PF_buf_writefile, NULL, 536, D("float(filestream filehandle, strbuf bufhandle, optional float startpos, optional float numstrings)", "Writes the contents of a string buffer onto the end of the supplied filehandle (you must have already used fopen). Additional optional arguments permit you to constrain the writes to a subsection of the stringbuffer.")}, + {"callfunction", PF_callfunction, PF_callfunction, 605, D("void(.../*, string funcname*/)", "Invokes the named function. The function name is always passed as the last parameter and must always be present. The others are passed to the named function as-is")}, + {"isfunction", PF_isfunction, PF_isfunction, 607, D("float(string s)", "Returns true if the named function exists and can be called with the callfunction builtin.")}, + {"parseentitydata", PF_parseentitydata, NULL, 613, D("float(entity e, string s, optional float offset)", "Reads a single entity's fields into an already-spawned entity. s should contain field pairs like in a saved game: {\"foo1\" \"bar\" \"foo2\" \"5\"}. Returns <=0 on failure, otherwise returns the offset in the string that was read to.")}, +// {"generateentitydata",PF_generateentitydata,NULL, 0, D("string(entity e)", "Dumps the entities fields into a string which can later be parsed with parseentitydata."}), + {"sprintf", PF_sprintf, PF_sprintf, 627, "string(string fmt, ...)"}, + {"getsurfacenumtriangles",PF_getsurfacenumtriangles,PF_getsurfacenumtriangles,628,"float(entity e, float s)"}, + {"getsurfacetriangle",PF_getsurfacetriangle,PF_getsurfacetriangle,629,"vector(entity e, float s, float n)"}, +// {"digest_hex", PF_digest_hex, PF_digest_hex, 639, "string(string digest, string data, ...)"}, }; static const char *extnames[] = @@ -4182,7 +5465,7 @@ static const char *extnames[] = "DP_QC_STRFTIME", "DP_QC_STRING_CASE_FUNCTIONS", "DP_QC_STRINGBUFFERS", -// "DP_QC_STRINGCOLORFUNCTIONS", //the functions are provided only as stubs. the client has absolutely no support. + "DP_QC_STRINGCOLORFUNCTIONS", "DP_QC_STRREPLACE", "DP_QC_TOKENIZEBYSEPARATOR", "DP_QC_TRACEBOX", @@ -4231,8 +5514,6 @@ static const char *extnames[] = }; -static builtin_t extbuiltins[1024]; - static void PF_checkextension(void) { const char *extname = G_STRING(OFS_PARM0); @@ -4252,17 +5533,6 @@ static void PF_checkextension(void) G_FLOAT(OFS_RETURN) = false; } -static void PF_EnableExtensionBuiltins(void) -{ - if (pr_builtins != extbuiltins) - { //first time we're using an extension! woo, everything is new!... - memcpy(extbuiltins, pr_builtins, sizeof(pr_builtins[0])*pr_numbuiltins); - pr_builtins = extbuiltins; - for (; (unsigned int)pr_numbuiltins < sizeof(extbuiltins)/sizeof(extbuiltins[0]); ) - extbuiltins[pr_numbuiltins++] = PF_Fixme; - } -} - static void PF_builtinsupported(void) { const char *biname = G_STRING(OFS_PARM0); @@ -4282,17 +5552,17 @@ static void PF_builtinsupported(void) static void PF_checkbuiltin (void) { func_t funcref = G_INT(OFS_PARM0); - if ((unsigned int)funcref < (unsigned int)progs->numfunctions) + if ((unsigned int)funcref < (unsigned int)qcvm->progs->numfunctions) { - dfunction_t *fnc = &pr_functions[(unsigned int)funcref]; + dfunction_t *fnc = &qcvm->functions[(unsigned int)funcref]; // const char *funcname = PR_GetString(fnc->s_name); int binum = -fnc->first_statement; unsigned int i; //qc defines the function at least. nothing weird there... - if (binum > 0 && binum < pr_numbuiltins) + if (binum > 0 && binum < qcvm->numbuiltins) { - if (pr_builtins[binum] == PF_Fixme) + if (qcvm->builtins[binum] == PF_Fixme) { G_FLOAT(OFS_RETURN) = false; //the builtin with that number isn't defined. for (i = 0; i < sizeof(extensionbuiltins) / sizeof(extensionbuiltins[0]); i++) @@ -4326,11 +5596,11 @@ static void PF_checkbuiltin (void) void PF_Fixme (void) { //interrogate the vm to try to figure out exactly which builtin they just tried to execute. - dstatement_t *st = &pr_statements[pr_xstatement]; - eval_t *glob = (eval_t*)&pr_globals[st->a]; - if ((unsigned int)glob->function < (unsigned int)progs->numfunctions) + dstatement_t *st = &qcvm->statements[qcvm->xstatement]; + eval_t *glob = (eval_t*)&qcvm->globals[st->a]; + if ((unsigned int)glob->function < (unsigned int)qcvm->progs->numfunctions) { - dfunction_t *fnc = &pr_functions[(unsigned int)glob->function]; + dfunction_t *fnc = &qcvm->functions[(unsigned int)glob->function]; const char *funcname = PR_GetString(fnc->s_name); int binum = -fnc->first_statement; unsigned int i; @@ -4341,13 +5611,20 @@ void PF_Fixme (void) { if (extensionbuiltins[i].number == binum) { //set it up so we're faster next time - PF_EnableExtensionBuiltins(); + builtin_t bi = NULL; + if (qcvm == &sv.qcvm) + bi = extensionbuiltins[i].ssqcfunc; + else if (qcvm == &cl.qcvm) + bi = extensionbuiltins[i].csqcfunc; + if (!bi) + continue; + if (!pr_checkextension.value || (extensionbuiltins[i].desc && !strncmp(extensionbuiltins[i].desc, "stub.", 5))) Con_Warning("Mod is using builtin #%u - %s\n", extensionbuiltins[i].documentednumber, extensionbuiltins[i].name); else Con_DPrintf2("Mod uses builtin #%u - %s\n", extensionbuiltins[i].documentednumber, extensionbuiltins[i].name); - extbuiltins[binum] = extensionbuiltins[i].func; - extensionbuiltins[i].func(); + qcvm->builtins[binum] = bi; + qcvm->builtins[binum](); return; } } @@ -4363,34 +5640,75 @@ void PF_Fixme (void) void PR_ShutdownExtensions(void) { PR_UnzoneAll(); + PF_frikfile_shutdown(); + PF_search_shutdown(); PF_buf_shutdown(); tokenize_flush(); pr_ext_warned_particleeffectnum = 0; } -static func_t PR_FindExtFunction(const char *entryname) +func_t PR_FindExtFunction(const char *entryname) { //depends on 0 being an invalid function, dfunction_t *func = ED_FindFunction(entryname); if (func) - return func - pr_functions; + return func - qcvm->functions; return 0; } +static void *PR_FindExtGlobal(int type, const char *name) +{ + ddef_t *def = ED_FindGlobal(name); + if (def && (def->type&~DEF_SAVEGLOBAL) == type && def->ofs < qcvm->progs->numglobals) + return qcvm->globals + def->ofs; + return NULL; +} void PR_AutoCvarChanged(cvar_t *var) { char *n; ddef_t *glob; - - if (!sv.active) - return; //someone flushed our globals!.. - - n = va("autocvar_%s", var->name); - glob = ED_FindGlobal(n); - if (glob) + qcvm_t *oldqcvm = qcvm; + PR_SwitchQCVM(NULL); + if (sv.active) { - if (!ED_ParseEpair ((void *)pr_globals, glob, var->string)) - Con_Warning("EXT: Unable to configure %s\n", n); + PR_SwitchQCVM(&sv.qcvm); + n = va("autocvar_%s", var->name); + glob = ED_FindGlobal(n); + if (glob) + { + if (!ED_ParseEpair ((void *)qcvm->globals, glob, var->string)) + Con_Warning("EXT: Unable to configure %s\n", n); + } + PR_SwitchQCVM(NULL); + } + if (cl.qcvm.globals) + { + PR_SwitchQCVM(NULL); + PR_SwitchQCVM(&cl.qcvm); + n = va("autocvar_%s", var->name); + glob = ED_FindGlobal(n); + if (glob) + { + if (!ED_ParseEpair ((void *)qcvm->globals, glob, var->string)) + Con_Warning("EXT: Unable to configure %s\n", n); + } + PR_SwitchQCVM(NULL); + } + PR_SwitchQCVM(oldqcvm); +} + +void PR_InitExtensions(void) +{ + size_t i, j; + //this only needs to be done once. because we're evil. + //it should help slightly with the 'documentation' above at least. + j = sizeof(qcvm->builtins)/sizeof(qcvm->builtins[0]); + for (i = 1; i < sizeof(extensionbuiltins)/sizeof(extensionbuiltins[0]); i++) + { + if (extensionbuiltins[i].documentednumber) + extensionbuiltins[i].number = extensionbuiltins[i].documentednumber; + else + extensionbuiltins[i].number = --j; } } @@ -4400,54 +5718,45 @@ void PR_EnableExtensions(ddef_t *pr_globaldefs) unsigned int i, j; unsigned int numautocvars = 0; - static builtin_t *stdbuiltins; - static int stdnumbuiltins; - if (!stdbuiltins) - { - stdbuiltins = pr_builtins; - stdnumbuiltins = pr_numbuiltins; - - //this also only needs to be done once. because we're evil. - //it should help slightly with the 'documentation' above at least. - j = sizeof(extbuiltins)/sizeof(extbuiltins[0]); - for (i = 1; i < sizeof(extensionbuiltins)/sizeof(extensionbuiltins[0]); i++) - { - if (extensionbuiltins[i].documentednumber) - extensionbuiltins[i].number = extensionbuiltins[i].documentednumber; - else - extensionbuiltins[i].number = --j; - } - } - pr_builtins = stdbuiltins; - pr_numbuiltins = stdnumbuiltins; - - memset(&pr_extfuncs, 0, sizeof(pr_extfuncs)); - - PR_ShutdownExtensions(); //just in case. - if (!pr_checkextension.value) { Con_DPrintf("not enabling qc extensions\n"); return; } + for (i = qcvm->numbuiltins; i < countof(qcvm->builtins); i++) + qcvm->builtins[i] = PF_Fixme; + qcvm->numbuiltins = i; - PF_EnableExtensionBuiltins(); - extbuiltins[51] = PF_ext_vectoangles; + qcvm->builtins[51] = PF_ext_vectoangles; //swap it with a two-arg version. - pr_extfuncs.parseclientcommand = PR_FindExtFunction("SV_ParseClientCommand"); - pr_extfuncs.endframe = PR_FindExtFunction("EndFrame"); + qcvm->extfuncs.SV_ParseClientCommand = PR_FindExtFunction("SV_ParseClientCommand"); + qcvm->extfuncs.EndFrame = PR_FindExtFunction("EndFrame"); + + qcvm->extfuncs.CSQC_Init = PR_FindExtFunction("CSQC_Init"); + qcvm->extfuncs.CSQC_DrawHud = PR_FindExtFunction("CSQC_DrawHud"); + qcvm->extfuncs.CSQC_DrawScores = PR_FindExtFunction("CSQC_DrawScores"); + qcvm->extfuncs.CSQC_InputEvent = PR_FindExtFunction("CSQC_InputEvent"); + qcvm->extfuncs.CSQC_ConsoleCommand = PR_FindExtFunction("CSQC_ConsoleCommand"); + qcvm->extfuncs.CSQC_Parse_Event = PR_FindExtFunction("CSQC_Parse_Event"); + qcvm->extfuncs.CSQC_Parse_Damage = PR_FindExtFunction("CSQC_Parse_Damage"); + qcvm->extglobals.cltime = PR_FindExtGlobal(ev_float, "cltime"); + qcvm->extglobals.maxclients = PR_FindExtGlobal(ev_float, "maxclients"); + qcvm->extglobals.intermission = PR_FindExtGlobal(ev_float, "intermission"); + qcvm->extglobals.intermission_time = PR_FindExtGlobal(ev_float, "intermission_time"); + qcvm->extglobals.player_localnum = PR_FindExtGlobal(ev_float, "player_localnum"); + qcvm->extglobals.player_localentnum = PR_FindExtGlobal(ev_float, "player_localentnum"); //any #0 functions are remapped to their builtins here, so we don't have to tweak the VM in an obscure potentially-breaking way. - for (i = 0; i < (unsigned int)progs->numfunctions; i++) + for (i = 0; i < (unsigned int)qcvm->progs->numfunctions; i++) { - if (pr_functions[i].first_statement == 0 && pr_functions[i].s_name && !pr_functions[i].parm_start && !pr_functions[i].locals) + if (qcvm->functions[i].first_statement == 0 && qcvm->functions[i].s_name && !qcvm->functions[i].parm_start && !qcvm->functions[i].locals) { - const char *name = PR_GetString(pr_functions[i].s_name); + const char *name = PR_GetString(qcvm->functions[i].s_name); for (j = 0; j < sizeof(extensionbuiltins)/sizeof(extensionbuiltins[0]); j++) { if (!strcmp(extensionbuiltins[j].name, name)) { //okay, map it - pr_functions[i].first_statement = -extensionbuiltins[j].number; + qcvm->functions[i].first_statement = -extensionbuiltins[j].number; break; } } @@ -4455,18 +5764,18 @@ void PR_EnableExtensions(ddef_t *pr_globaldefs) } //autocvars - for (i = 0; i < (unsigned int)progs->numglobaldefs; i++) + for (i = 0; i < (unsigned int)qcvm->progs->numglobaldefs; i++) { - const char *n = PR_GetString(pr_globaldefs[i].s_name); + const char *n = PR_GetString(qcvm->globaldefs[i].s_name); if (!strncmp(n, "autocvar_", 9)) { //really crappy approach - cvar_t *var = Cvar_Create(n + 9, PR_UglyValueString (pr_globaldefs[i].type, (eval_t*)(pr_globals + pr_globaldefs[i].ofs))); + cvar_t *var = Cvar_Create(n + 9, PR_UglyValueString (qcvm->globaldefs[i].type, (eval_t*)(qcvm->globals + qcvm->globaldefs[i].ofs))); numautocvars++; if (!var) continue; //name conflicts with a command? - if (!ED_ParseEpair ((void *)pr_globals, &pr_globaldefs[i], var->string)) + if (!ED_ParseEpair ((void *)qcvm->globals, &pr_globaldefs[i], var->string)) Con_Warning("EXT: Unable to configure %s\n", n); var->flags |= CVAR_AUTOCVAR; } @@ -4479,8 +5788,9 @@ void PR_DumpPlatform_f(void) { char name[MAX_OSPATH]; FILE *f; - const char *outname = "qsextensions"; + const char *outname = NULL; unsigned int i, j; + int targs = 0; for (i = 1; i < (unsigned)Cmd_Argc(); ) { const char *arg = Cmd_Argv(i++); @@ -4491,12 +5801,20 @@ void PR_DumpPlatform_f(void) else outname = Cmd_Argv(i++); } + else if (!q_strcasecmp(arg, "-Tcs")) + targs |= 2; + else if (!q_strcasecmp(arg, "-Tss")) + targs |= 1; else { Con_Printf("%s: Unknown argument\n", Cmd_Argv(0)); return; } } + if (!outname) + outname = ((targs==2)?"qscsextensions":"qsextensions"); + if (!targs) + targs = 3; if (strstr(outname, "..")) return; @@ -4518,10 +5836,16 @@ void PR_DumpPlatform_f(void) ,Cmd_Argv(0), Cmd_Args()?Cmd_Args():"with no args"); fprintf(f, - "\n\n//QuakeSpasm only supports ssqc, so including this file in some other situation is a user error\n" - "#if defined(QUAKEWORLD) || defined(CSQC) || defined(MENU)\n" + "\n\n//This file only supports csqc, so including this file in some other situation is a user error\n" + "#if defined(QUAKEWORLD) || defined(MENU)\n" "#error Mixed up module defs\n" "#endif\n" + "#ifndef CSQC\n" + "#define CSQC\n" + "#endif\n" + "#ifndef CSQC_SIMPLE\n" //quakespasm's csqc implementation is simplified, and can do huds+menus, but that's about it. + "#define CSQC_SIMPLE\n" + "#endif\n" ); fprintf(f, "\n\n//List of advertised extensions\n"); @@ -4531,6 +5855,55 @@ void PR_DumpPlatform_f(void) fprintf(f, "\n\n//Explicitly flag this stuff as probably-not-referenced, meaning fteqcc will shut up about it and silently strip what it can.\n"); fprintf(f, "#pragma noref 1\n"); + if (targs & 2) + { //I really hope that fteqcc's unused variable logic is up to scratch + fprintf(f, "entity self,other,world;\n"); + fprintf(f, "float time,frametime,force_retouch;\n"); + fprintf(f, "string mapname;\n"); + fprintf(f, "float deathmatch,coop,teamplay,serverflags,total_secrets,total_monsters,found_secrets,killed_monsters,parm1, parm2, parm3, parm4, parm5, parm6, parm7, parm8, parm9, parm10, parm11, parm12, parm13, parm14, parm15, parm16;\n"); + fprintf(f, "vector v_forward, v_up, v_right;\n"); + fprintf(f, "float trace_allsolid,trace_startsolid,trace_fraction;\n"); + fprintf(f, "vector trace_endpos,trace_plane_normal;\n"); + fprintf(f, "float trace_plane_dist;\n"); + fprintf(f, "entity trace_ent;\n"); + fprintf(f, "float trace_inopen,trace_inwater;\n"); + fprintf(f, "entity msg_entity;\n"); + fprintf(f, "void() main,StartFrame,PlayerPreThink,PlayerPostThink,ClientKill,ClientConnect,PutClientInServer,ClientDisconnect,SetNewParms,SetChangeParms;\n"); + fprintf(f, "void end_sys_globals;\n"); + fprintf(f, ".float modelindex;\n"); + fprintf(f, ".vector absmin, absmax;\n"); + fprintf(f, ".float ltime,movetype,solid;\n"); + fprintf(f, ".vector origin,oldorigin,velocity,angles,avelocity,punchangle;\n"); + fprintf(f, ".string classname,model;\n"); + fprintf(f, ".float frame,skin,effects;\n"); + fprintf(f, ".vector mins, maxs,size;\n"); + fprintf(f, ".void() touch,use,think,blocked;\n"); + fprintf(f, ".float nextthink;\n"); + fprintf(f, ".entity groundentity;\n"); + fprintf(f, ".float health,frags,weapon;\n"); + fprintf(f, ".string weaponmodel;\n"); + fprintf(f, ".float weaponframe,currentammo,ammo_shells,ammo_nails,ammo_rockets,ammo_cells,items,takedamage;\n"); + fprintf(f, ".entity chain;\n"); + fprintf(f, ".float deadflag;\n"); + fprintf(f, ".vector view_ofs;\n"); + fprintf(f, ".float button0,button1,button2,impulse,fixangle;\n"); + fprintf(f, ".vector v_angle;\n"); + fprintf(f, ".float idealpitch;\n"); + fprintf(f, ".string netname;\n"); + fprintf(f, ".entity enemy;\n"); + fprintf(f, ".float flags,colormap,team,max_health,teleport_time,armortype,armorvalue,waterlevel,watertype,ideal_yaw,yaw_speed;\n"); + fprintf(f, ".entity aiment,goalentity;\n"); + fprintf(f, ".float spawnflags;\n"); + fprintf(f, ".string target,targetname;\n"); + fprintf(f, ".float dmg_take,dmg_save;\n"); + fprintf(f, ".entity dmg_inflictor,owner;\n"); + fprintf(f, ".vector movedir;\n"); + fprintf(f, ".string message;\n"); + fprintf(f, ".float sounds;\n"); + fprintf(f, ".string noise, noise1, noise2, noise3;\n"); + fprintf(f, "void end_sys_fields;\n"); + } + fprintf(f, "\n\n//Some custom types (that might be redefined as accessors by fteextensions.qc, although we don't define any methods here)\n"); fprintf(f, "#ifdef _ACCESSORS\n"); fprintf(f, "accessor strbuf:float;\n"); @@ -4546,64 +5919,169 @@ void PR_DumpPlatform_f(void) fprintf(f, "#define filestream float\n"); fprintf(f, "#endif\n"); - //extra fields - fprintf(f, "\n\n//Supported Extension fields\n"); - fprintf(f, ".float gravity;\n"); //used by hipnotic - fprintf(f, "//.float items2; /*if defined, overrides serverflags for displaying runes on the hud*/\n"); //used by both mission packs. *REPLACES* serverflags if defined, so lets try not to define it. - fprintf(f, ".float traileffectnum; /*can also be set with 'traileffect' from a map editor*/\n"); - fprintf(f, ".float emiteffectnum; /*can also be set with 'traileffect' from a map editor*/\n"); - fprintf(f, ".vector movement; /*describes which forward/right/up keys the player is holidng*/\n"); - fprintf(f, ".entity viewmodelforclient; /*attaches this entity to the specified player's view. invisible to other players*/\n"); - fprintf(f, ".float scale; /*rescales the etntiy*/\n"); - fprintf(f, ".float alpha; /*entity opacity*/\n"); //entity alpha. woot. - fprintf(f, ".vector colormod; /*tints the entity's colours*/\n"); - fprintf(f, ".entity tag_entity;\n"); - fprintf(f, ".float tag_index;\n"); - fprintf(f, ".float button3;\n"); - fprintf(f, ".float button4;\n"); - fprintf(f, ".float button5;\n"); - fprintf(f, ".float button6;\n"); - fprintf(f, ".float button7;\n"); - fprintf(f, ".float button8;\n"); - fprintf(f, ".float viewzoom; /*rescales the user's fov*/\n"); - fprintf(f, ".float modelflags; /*provides additional modelflags to use (effects&EF_NOMODELFLAGS to replace the original model's)*/\n"); - //extra constants - fprintf(f, "\n\n//Supported Extension Constants\n"); - fprintf(f, "const float MOVETYPE_FOLLOW = "STRINGIFY(MOVETYPE_EXT_FOLLOW)";\n"); - fprintf(f, "const float SOLID_CORPSE = "STRINGIFY(SOLID_EXT_CORPSE)";\n"); + if (targs & 1) + { + fprintf(f, "void(string cmd) SV_ParseClientCommand;\n"); + fprintf(f, "void() EndFrame;\n"); + } + + if (targs & 2) + { + fprintf(f, "void(float apilevel, string enginename, float engineversion) CSQC_Init;\n"); + fprintf(f, "float(string cmdstr) CSQC_ConsoleCommand;\n"); + fprintf(f, "void(vector virtmin, vector virtsize, float showscores) CSQC_DrawHud;\n"); + fprintf(f, "void(vector virtmin, vector virtsize, float showscores) CSQC_DrawScores;\n"); + fprintf(f, "float(float evtype, float scanx, float chary, float devid) CSQC_InputEvent;\n"); + fprintf(f, "void() CSQC_Parse_Event;\n"); + fprintf(f, "float(float save, float take, vector dir) CSQC_Parse_Damage;\n"); + } + + if (targs & 2) + { + fprintf(f, "float cltime; /* increases regardless of pause state or game speed */\n"); + fprintf(f, "float maxclients; /* maximum number of players possible on this server */\n"); + fprintf(f, "float intermission; /* in intermission */\n"); + fprintf(f, "float intermission_time; /* when the intermission started */\n"); + fprintf(f, "float player_localnum; /* the player slot that is believed to be assigned to us*/\n"); + fprintf(f, "float player_localentnum; /* the entity number that the view is attached to */\n"); + } + + fprintf(f, "const float FALSE = 0;\n"); + fprintf(f, "const float TRUE = 1;\n"); + + if (targs & 2) + { + fprintf(f, "const float IE_KEYDOWN = %i;\n", CSIE_KEYDOWN); + fprintf(f, "const float IE_KEYUP = %i;\n", CSIE_KEYUP); + fprintf(f, "const float IE_MOUSEDELTA = %i;\n", CSIE_MOUSEDELTA); + fprintf(f, "const float IE_MOUSEABS = %i;\n", CSIE_MOUSEABS); + fprintf(f, "const float IE_JOYAXIS = %i;\n", CSIE_JOYAXIS); + + fprintf(f, "const float STAT_HEALTH = 0; /* Player's health. */\n"); +// fprintf(f, "const float STAT_FRAGS = 1; /* unused */\n"); + fprintf(f, "const float STAT_WEAPONMODELI = 2; /* This is the modelindex of the current viewmodel (renamed from the original name 'STAT_WEAPON' due to confusions). */\n"); + fprintf(f, "const float STAT_AMMO = 3; /* player.currentammo */\n"); + fprintf(f, "const float STAT_ARMOR = 4;\n"); + fprintf(f, "const float STAT_WEAPONFRAME = 5;\n"); + fprintf(f, "const float STAT_SHELLS = 6;\n"); + fprintf(f, "const float STAT_NAILS = 7;\n"); + fprintf(f, "const float STAT_ROCKETS = 8;\n"); + fprintf(f, "const float STAT_CELLS = 9;\n"); + fprintf(f, "const float STAT_ACTIVEWEAPON = 10; /* player.weapon */\n"); + fprintf(f, "const float STAT_TOTALSECRETS = 11;\n"); + fprintf(f, "const float STAT_TOTALMONSTERS = 12;\n"); + fprintf(f, "const float STAT_FOUNDSECRETS = 13;\n"); + fprintf(f, "const float STAT_KILLEDMONSTERS = 14;\n"); + fprintf(f, "const float STAT_ITEMS = 15; /* self.items | (self.items2<<23). In order to decode this stat properly, you need to use getstatbits(STAT_ITEMS,0,23) to read self.items, and getstatbits(STAT_ITEMS,23,11) to read self.items2 or getstatbits(STAT_ITEMS,28,4) to read the visible part of serverflags, whichever is applicable. */\n"); + fprintf(f, "const float STAT_VIEWHEIGHT = 16; /* player.view_ofs_z */\n"); +// fprintf(f, "const float STAT_TIME = 17;\n"); +// fprintf(f, "//const float STAT_MATCHSTARTTIME = 18;\n"); +// fprintf(f, "const float STAT_UNUSED = 19;\n"); + fprintf(f, "const float STAT_VIEW2 = 20; /* This stat contains the number of the entity in the server's .view2 field. */\n"); + fprintf(f, "const float STAT_VIEWZOOM = 21; /* Scales fov and sensitiity. Part of DP_VIEWZOOM. */\n"); +// fprintf(f, "const float STAT_UNUSED = 22;\n"); +// fprintf(f, "const float STAT_UNUSED = 23;\n"); +// fprintf(f, "const float STAT_UNUSED = 24;\n"); + fprintf(f, "const float STAT_IDEALPITCH = 25;\n"); + fprintf(f, "const float STAT_PUNCHANGLE_X = 26;\n"); + fprintf(f, "const float STAT_PUNCHANGLE_Y = 27;\n"); + fprintf(f, "const float STAT_PUNCHANGLE_Y = 28;\n"); +// fprintf(f, "const float STAT_PUNCHVECTOR_X = 29;\n"); +// fprintf(f, "const float STAT_PUNCHVECTOR_Y = 30;\n"); +// fprintf(f, "const float STAT_PUNCHVECTOR_Z = 31;\n"); + } + fprintf(f, "const float STAT_USER = 32; /* Custom user stats start here (lower values are reserved for engine use). */\n"); + + if (targs & 1) + { + //extra fields + fprintf(f, "\n\n//Supported Extension fields\n"); + fprintf(f, ".float gravity;\n"); //used by hipnotic + fprintf(f, "//.float items2; /*if defined, overrides serverflags for displaying runes on the hud*/\n"); //used by both mission packs. *REPLACES* serverflags if defined, so lets try not to define it. + fprintf(f, ".float traileffectnum; /*can also be set with 'traileffect' from a map editor*/\n"); + fprintf(f, ".float emiteffectnum; /*can also be set with 'traileffect' from a map editor*/\n"); + fprintf(f, ".vector movement; /*describes which forward/right/up keys the player is holidng*/\n"); + fprintf(f, ".entity viewmodelforclient; /*attaches this entity to the specified player's view. invisible to other players*/\n"); + fprintf(f, ".float scale; /*rescales the etntiy*/\n"); + fprintf(f, ".float alpha; /*entity opacity*/\n"); //entity alpha. woot. + fprintf(f, ".vector colormod; /*tints the entity's colours*/\n"); + fprintf(f, ".entity tag_entity;\n"); + fprintf(f, ".float tag_index;\n"); + fprintf(f, ".float button3;\n"); + fprintf(f, ".float button4;\n"); + fprintf(f, ".float button5;\n"); + fprintf(f, ".float button6;\n"); + fprintf(f, ".float button7;\n"); + fprintf(f, ".float button8;\n"); + fprintf(f, ".float viewzoom; /*rescales the user's fov*/\n"); + fprintf(f, ".float modelflags; /*provides additional modelflags to use (effects&EF_NOMODELFLAGS to replace the original model's)*/\n"); + + //extra constants + fprintf(f, "\n\n//Supported Extension Constants\n"); + fprintf(f, "const float MOVETYPE_FOLLOW = "STRINGIFY(MOVETYPE_EXT_FOLLOW)";\n"); + fprintf(f, "const float SOLID_CORPSE = "STRINGIFY(SOLID_EXT_CORPSE)";\n"); + + fprintf(f, "const float CLIENTTYPE_DISCONNECT = "STRINGIFY(0)";\n"); + fprintf(f, "const float CLIENTTYPE_REAL = "STRINGIFY(1)";\n"); + fprintf(f, "const float CLIENTTYPE_BOT = "STRINGIFY(2)";\n"); + fprintf(f, "const float CLIENTTYPE_NOTCLIENT = "STRINGIFY(3)";\n"); + + fprintf(f, "const float EF_NOSHADOW = %#x;\n", EF_NOSHADOW); + fprintf(f, "const float EF_NOMODELFLAGS = %#x; /*the standard modelflags from the model are ignored*/\n", EF_NOMODELFLAGS); + + fprintf(f, "const float MF_ROCKET = %#x;\n", EF_ROCKET); + fprintf(f, "const float MF_GRENADE = %#x;\n", EF_GRENADE); + fprintf(f, "const float MF_GIB = %#x;\n", EF_GIB); + fprintf(f, "const float MF_ROTATE = %#x;\n", EF_ROTATE); + fprintf(f, "const float MF_TRACER = %#x;\n", EF_TRACER); + fprintf(f, "const float MF_ZOMGIB = %#x;\n", EF_ZOMGIB); + fprintf(f, "const float MF_TRACER2 = %#x;\n", EF_TRACER2); + fprintf(f, "const float MF_TRACER3 = %#x;\n", EF_TRACER3); + + fprintf(f, "const float MSG_MULTICAST = %i;\n", 4); + fprintf(f, "const float MULTICAST_ALL = %i;\n", MULTICAST_ALL_U); + // fprintf(f, "const float MULTICAST_PHS = %i;\n", MULTICAST_PHS_U); + fprintf(f, "const float MULTICAST_PVS = %i;\n", MULTICAST_PVS_U); + fprintf(f, "const float MULTICAST_ONE = %i;\n", MULTICAST_ONE_U); + fprintf(f, "const float MULTICAST_ALL_R = %i;\n", MULTICAST_ALL_R); + // fprintf(f, "const float MULTICAST_PHS_R = %i;\n", MULTICAST_PHS_R); + fprintf(f, "const float MULTICAST_PVS_R = %i;\n", MULTICAST_PVS_R); + fprintf(f, "const float MULTICAST_ONE_R = %i;\n", MULTICAST_ONE_R); + fprintf(f, "const float MULTICAST_INIT = %i;\n", MULTICAST_INIT); + } fprintf(f, "const float FILE_READ = "STRINGIFY(0)";\n"); fprintf(f, "const float FILE_APPEND = "STRINGIFY(1)";\n"); fprintf(f, "const float FILE_WRITE = "STRINGIFY(2)";\n"); - fprintf(f, "const float CLIENTTYPE_DISCONNECT = "STRINGIFY(0)";\n"); - fprintf(f, "const float CLIENTTYPE_REAL = "STRINGIFY(1)";\n"); - fprintf(f, "const float CLIENTTYPE_BOT = "STRINGIFY(2)";\n"); - fprintf(f, "const float CLIENTTYPE_NOTCLIENT = "STRINGIFY(3)";\n"); - - fprintf(f, "const float EF_NOSHADOW = %#x;\n", EF_NOSHADOW); - fprintf(f, "const float EF_NOMODELFLAGS = %#x; /*the standard modelflags from the model are ignored*/\n", EF_NOMODELFLAGS); - - fprintf(f, "const float MF_ROCKET = %#x;\n", EF_ROCKET); - fprintf(f, "const float MF_GRENADE = %#x;\n", EF_GRENADE); - fprintf(f, "const float MF_GIB = %#x;\n", EF_GIB); - fprintf(f, "const float MF_ROTATE = %#x;\n", EF_ROTATE); - fprintf(f, "const float MF_TRACER = %#x;\n", EF_TRACER); - fprintf(f, "const float MF_ZOMGIB = %#x;\n", EF_ZOMGIB); - fprintf(f, "const float MF_TRACER2 = %#x;\n", EF_TRACER2); - fprintf(f, "const float MF_TRACER3 = %#x;\n", EF_TRACER3); - - fprintf(f, "const float MSG_MULTICAST = "STRINGIFY(4)";\n"); - fprintf(f, "const float MULTICAST_ALL = "STRINGIFY(MULTICAST_ALL_U)";\n"); -// fprintf(f, "const float MULTICAST_PHS = "STRINGIFY(MULTICAST_PHS_U)";\n"); - fprintf(f, "const float MULTICAST_PVS = "STRINGIFY(MULTICAST_PVS_U)";\n"); - fprintf(f, "const float MULTICAST_ONE = "STRINGIFY(MULTICAST_ONE_U)";\n"); - fprintf(f, "const float MULTICAST_ALL_R = "STRINGIFY(MULTICAST_ALL_R)";\n"); -// fprintf(f, "const float MULTICAST_PHS_R = "STRINGIFY(MULTICAST_PHS_R)";\n"); - fprintf(f, "const float MULTICAST_PVS_R = "STRINGIFY(MULTICAST_PVS_R)";\n"); - fprintf(f, "const float MULTICAST_ONE_R = "STRINGIFY(MULTICAST_ONE_R)";\n"); - fprintf(f, "const float MULTICAST_INIT = "STRINGIFY(MULTICAST_INIT)";\n"); + if (targs & 2) + { + fprintf(f, "\n\n//Vanilla Builtin list (reduced, so as to avoid conflicts)\n"); + fprintf(f, "void(vector) makevectors = #1;\n"); + fprintf(f, "void(entity,vector) setorigin = #2;\n"); + fprintf(f, "void(entity,string) setmodel = #3;\n"); + fprintf(f, "void(entity,vector,vector) setsize = #4;\n"); + fprintf(f, "float() random = #7;\n"); + //sound = #8 + fprintf(f, "vector(vector) normalize = #9;\n"); + fprintf(f, "void(string e) error = #10;\n"); + fprintf(f, "void(string n) objerror = #11;\n"); + fprintf(f, "float(vector) vlen = #12;\n"); + fprintf(f, "entity() spawn = #14;\n"); + fprintf(f, "void(entity e) remove = #15;\n"); + fprintf(f, "void(string,...) dprint = #25;\n"); + fprintf(f, "string(float) ftos = #26;\n"); + fprintf(f, "string(vector) vtos = #27;\n"); + fprintf(f, "float(float n) rint = #36;\n"); + fprintf(f, "float(float n) floor = #37;\n"); + fprintf(f, "float(float n) ceil = #38;\n"); + fprintf(f, "float(float n) fabs = #43;\n"); + fprintf(f, "float(string) cvar = #45;\n"); + fprintf(f, "void(string,...) localcmd = #46;\n"); + fprintf(f, "entity(entity) nextent = #47;\n"); + fprintf(f, "void(string var, string val) cvar_set = #72;\n"); + } for (j = 0; j < 2; j++) { @@ -4613,6 +6091,13 @@ void PR_DumpPlatform_f(void) fprintf(f, "\n\n//Builtin list\n"); for (i = 0; i < sizeof(extensionbuiltins)/sizeof(extensionbuiltins[0]); i++) { + if ((targs & 2) && extensionbuiltins[i].csqcfunc) + ; + else if ((targs & 1) && extensionbuiltins[i].ssqcfunc) + ; + else + continue; + if (j != (extensionbuiltins[i].desc?!strncmp(extensionbuiltins[i].desc, "stub.", 5):0)) continue; fprintf(f, "%s %s = #%i;", extensionbuiltins[i].typestr, extensionbuiltins[i].name, extensionbuiltins[i].documentednumber); @@ -4643,6 +6128,18 @@ void PR_DumpPlatform_f(void) fprintf(f, "*/\n"); } + if (targs & 2) + { + for (i = 0; i < MAX_KEYS; i++) + { + const char *k = Key_KeynumToString(i); + if (!k[0] || !k[1] || k[0] == '<') + continue; //skip simple keynames that can be swapped with 'k', and keys. + fprintf(f, "const float K_%s = %i;\n", k, Key_NativeToQC(i)); + } + } + + fprintf(f, "\n\n//Reset this back to normal.\n"); fprintf(f, "#pragma noref 0\n"); fclose(f); diff --git a/Quake/progs.h b/Quake/progs.h index 61977587..4202e4f4 100644 --- a/Quake/progs.h +++ b/Quake/progs.h @@ -59,25 +59,21 @@ typedef struct edict_s //============================================================================ -extern dprograms_t *progs; -extern dfunction_t *pr_functions; -extern dstatement_t *pr_statements; -extern globalvars_t *pr_global_struct; -extern float *pr_globals; /* same as pr_global_struct */ -extern ddef_t *pr_fielddefs; //yay reflection. - -extern int pr_edict_size; /* in bytes */ - +typedef void (*builtin_t) (void); +typedef struct qcvm_s qcvm_t; void PR_Init (void); void PR_ExecuteProgram (func_t fnum); -void PR_LoadProgs (void); +void PR_ClearProgs(qcvm_t *vm); +qboolean PR_LoadProgs (const char *filename, qboolean fatal, builtin_t *builtins, size_t numbuiltins); //from pr_ext.c +void PR_InitExtensions(void); void PR_EnableExtensions(ddef_t *pr_globaldefs); //adds in the extra builtins etc void PR_AutoCvarChanged(cvar_t *var); //updates the autocvar_ globals when their cvar is changed void PR_ShutdownExtensions(void); //nooooes! +func_t PR_FindExtFunction(const char *entryname); void PR_DumpPlatform_f(void); //console command: writes out a qsextensions.qc file //special hacks... int PF_SV_ForceParticlePrecache(const char *s); @@ -118,18 +114,20 @@ void ED_LoadFromFile (const char *data); edict_t *EDICT_NUM(int n); int NUM_FOR_EDICT(edict_t *e); -#define NEXT_EDICT(e) ((edict_t *)( (byte *)e + pr_edict_size)) +#define NEXT_EDICT(e) ((edict_t *)( (byte *)e + qcvm->edict_size)) -#define EDICT_TO_PROG(e) ((byte *)e - (byte *)sv.edicts) -#define PROG_TO_EDICT(e) ((edict_t *)((byte *)sv.edicts + e)) +#define EDICT_TO_PROG(e) ((byte *)e - (byte *)qcvm->edicts) +#define PROG_TO_EDICT(e) ((edict_t *)((byte *)qcvm->edicts + e)) -#define G_FLOAT(o) (pr_globals[o]) -#define G_INT(o) (*(int *)&pr_globals[o]) -#define G_EDICT(o) ((edict_t *)((byte *)sv.edicts+ *(int *)&pr_globals[o])) +#define G_FLOAT(o) (qcvm->globals[o]) +#define G_INT(o) (*(int *)&qcvm->globals[o]) +#define G_EDICT(o) ((edict_t *)((byte *)qcvm->edicts+ *(int *)&qcvm->globals[o])) #define G_EDICTNUM(o) NUM_FOR_EDICT(G_EDICT(o)) -#define G_VECTOR(o) (&pr_globals[o]) -#define G_STRING(o) (PR_GetString(*(string_t *)&pr_globals[o])) -#define G_FUNCTION(o) (*(func_t *)&pr_globals[o]) +#define G_VECTOR(o) (&qcvm->globals[o]) +#define G_STRING(o) (PR_GetString(*(string_t *)&qcvm->globals[o])) +#define G_FUNCTION(o) (*(func_t *)&qcvm->globals[o]) + +#define G_VECTORSET(r,x,y,z) do{G_FLOAT((r)+0) = x; G_FLOAT((r)+1) = y;G_FLOAT((r)+2) = z;}while(0) #define E_FLOAT(e,o) (((float*)&e->v)[o]) #define E_INT(e,o) (*(int *)&((float*)&e->v)[o]) @@ -138,18 +136,6 @@ int NUM_FOR_EDICT(edict_t *e); extern int type_size[8]; -typedef void (*builtin_t) (void); -extern builtin_t *pr_builtins; -extern int pr_numbuiltins; - -extern int pr_argc; - -extern qboolean pr_trace; -extern dfunction_t *pr_xfunction; -extern int pr_xstatement; - -extern unsigned short pr_crc; - FUNC_NORETURN void PR_RunError (const char *error, ...) FUNC_PRINTF(1,2); #ifdef __WATCOMC__ #pragma aux PR_RunError aborts; @@ -165,19 +151,49 @@ int ED_FindFieldOffset (const char *name); //from pr_cmds, no longer static so that pr_ext can use them. sizebuf_t *WriteDest (void); char *PR_GetTempString (void); +int PR_MakeTempString (const char *val); char *PF_VarString (int first); #define STRINGTEMP_BUFFERS 16 #define STRINGTEMP_LENGTH 1024 void PF_Fixme(void); //the 'unimplemented' builtin. woot. -extern struct pr_extfuncs_s +struct pr_extfuncs_s { //various global qc entry points that might be called by the engine, if set. - func_t endframe; - func_t parseclientcommand; -} pr_extfuncs; + func_t EndFrame; + func_t SV_ParseClientCommand; + + //csqc-specific entry points + func_t CSQC_Init; + func_t CSQC_DrawHud; //for the simple hud-only csqc interface. + func_t CSQC_DrawScores; //(optional) for the simple hud-only csqc interface. + func_t CSQC_InputEvent; + func_t CSQC_ConsoleCommand; + func_t CSQC_Parse_Event; + func_t CSQC_Parse_Damage; + //todo... +// func_t CSQC_Parse_CenterPrint; +// func_t CSQC_Parse_Print; + +// func_t CSQC_Parse_TempEntity; //evil... This is the bane of all protocol compatibility. Die. +// func_t CSQC_Parse_StuffCmd; //not in simple. Too easy to make cheats by ignoring server messages. +}; extern cvar_t pr_checkextension; //if 0, extensions are disabled (unless they'd be fatal, but they're still spammy) -extern struct pr_extfields_s +struct pr_extglobals_s +{ + //csqc-specific globals... + float *cltime; + float *maxclients; + float *intermission; + float *intermission_time; + float *player_localnum; + float *player_localentnum; + + //float *clientcommandframe; //we don't have prediction. + //float *servercommandframe; //we don't have prediction. +}; + +struct pr_extfields_s { //various fields that might be wanted by the engine. -1 == invalid //I should probably use preprocessor magic for this list or something int items2; //float @@ -200,7 +216,110 @@ extern struct pr_extfields_s int viewzoom; //float int modelflags; //float, the upper 8 bits of .effects //REMEMBER TO ADD THESE TO qsextensions.qc AND pr_edict.c -} pr_extfields; +}; + +typedef struct +{ + int s; + dfunction_t *f; +} prstack_t; + + +typedef struct areanode_s +{ + int axis; // -1 = leaf node + float dist; + struct areanode_s *children[2]; + link_t trigger_edicts; + link_t solid_edicts; +} areanode_t; +#define AREA_DEPTH 4 +#define AREA_NODES 32 + +#define CSIE_KEYDOWN 0 +#define CSIE_KEYUP 1 +#define CSIE_MOUSEDELTA 2 +#define CSIE_MOUSEABS 3 +//#define CSIE_ACCELEROMETER 4 +//#define CSIE_FOCUS 5 +#define CSIE_JOYAXIS 6 +//#define CSIE_GYROSCOPE 7 + +struct qcvm_s +{ + dprograms_t *progs; + dfunction_t *functions; + dstatement_t *statements; + float *globals; /* same as pr_global_struct */ + ddef_t *fielddefs; //yay reflection. + + int edict_size; /* in bytes */ + + builtin_t builtins[1024]; + int numbuiltins; + + int argc; + + qboolean trace; + dfunction_t *xfunction; + int xstatement; + + unsigned short crc; + + struct pr_extglobals_s extglobals; + struct pr_extfuncs_s extfuncs; + struct pr_extfields_s extfields; + + //was static inside pr_edict + char *strings; + int stringssize; + const char **knownstrings; + int maxknownstrings; + int numknownstrings; + int freeknownstrings; + ddef_t *globaldefs; + + unsigned char *knownzone; + size_t knownzonesize; + + //originally defined in pr_exec, but moved into the switchable qcvm struct +#define MAX_STACK_DEPTH 64 /* was 32 */ + prstack_t stack[MAX_STACK_DEPTH]; + int depth; + +#define LOCALSTACK_SIZE 2048 + int localstack[LOCALSTACK_SIZE]; + int localstack_used; + + //originally part of the sv_state_t struct + //FIXME: put worldmodel in here too. + double time; + int num_edicts; + int reserved_edicts; + int max_edicts; + edict_t *edicts; // can NOT be array indexed, because edict_t is variable sized, but can be used to reference the world ent + struct qmodel_s *worldmodel; + struct qmodel_s *(*GetModel)(int modelindex); //returns the model for the given index, or null. + + //originally from world.c + areanode_t areanodes[AREA_NODES]; + int numareanodes; +}; +extern globalvars_t *pr_global_struct; + +#if 0 +extern qcvm_t ssqcvm; +#define qcvm (&ssqcvm) +#define PR_SwitchQCVM(n) +#else +extern qcvm_t *qcvm; +void PR_SwitchQCVM(qcvm_t *nvm); +#endif + +extern builtin_t pr_ssqcbuiltins[]; +extern int pr_ssqcnumbuiltins; +extern builtin_t pr_csqcbuiltins[]; +extern int pr_csqcnumbuiltins; #endif /* _QUAKE_PROGS_H */ diff --git a/Quake/protocol.h b/Quake/protocol.h index c8461360..33a61655 100644 --- a/Quake/protocol.h +++ b/Quake/protocol.h @@ -51,6 +51,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // PROTOCOL_FTE_PEXT(1) flags //mostly uninteresting, mostly superseeded by PEXT2_REPLACEMENTDELTAS... +#define PEXT_CSQC 0x40000000 //csqc additions // PROTOCOL_FTE_PEXT2 flags #define PEXT2_PRYDONCURSOR 0x00000001 //a mouse cursor exposed to ssqc @@ -313,6 +314,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define svcfte_spawnbaseline2 66 #define svcfte_updatestatstring 78 #define svcfte_updatestatfloat 79 +#define svcfte_cgamepacket 83 #define svcfte_voicechat 84 #define svcfte_setangledelta 85 #define svcfte_updateentities 86 @@ -328,6 +330,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define clc_stringcmd 4 // [string] message #define clcdp_ackframe 50 // [long] frame sequence. reused by fte replacement deltas #define clcdp_ackdownloaddata 51 +#define clcfte_qcrequest 81 #define clcfte_voicechat 83 /*spike*/ // diff --git a/Quake/quakedef.h b/Quake/quakedef.h index 75f8ca7b..15b8a906 100644 --- a/Quake/quakedef.h +++ b/Quake/quakedef.h @@ -110,33 +110,38 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // stats are integers communicated to the client by the server // -#define MAX_CL_STATS 32 -#define STAT_HEALTH 0 -#define STAT_FRAGS 1 -#define STAT_WEAPON 2 -#define STAT_AMMO 3 -#define STAT_ARMOR 4 +#define MAX_CL_STATS 256 +#define STAT_HEALTH 0 +//#define STAT_FRAGS 1 +#define STAT_WEAPON 2 +#define STAT_AMMO 3 +#define STAT_ARMOR 4 #define STAT_WEAPONFRAME 5 -#define STAT_SHELLS 6 -#define STAT_NAILS 7 +#define STAT_SHELLS 6 +#define STAT_NAILS 7 #define STAT_ROCKETS 8 -#define STAT_CELLS 9 +#define STAT_CELLS 9 #define STAT_ACTIVEWEAPON 10 #define STAT_TOTALSECRETS 11 #define STAT_TOTALMONSTERS 12 #define STAT_SECRETS 13 // bumped on client side by svc_foundsecret #define STAT_MONSTERS 14 // bumped by svc_killedmonster - #define STAT_ITEMS 15 //replaces clc_clientdata info #define STAT_VIEWHEIGHT 16 //replaces clc_clientdata info //#define STAT_TIME 17 //zquake, redundant for nq. //#define STAT_MATCHSTARTTIME 18 -//#define STAT_VIEW2 20 +//#define STAT_VIEW2 20 #define STAT_VIEWZOOM 21 // DP +//#define STAT_UNUSED3 22 +//#define STAT_UNUSED2 23 +//#define STAT_UNUSED1 24 #define STAT_IDEALPITCH 25 //nq-emu #define STAT_PUNCHANGLE_X 26 //nq-emu #define STAT_PUNCHANGLE_Y 27 //nq-emu #define STAT_PUNCHANGLE_Z 28 //nq-emu +#define STAT_PUNCHVECTOR_X 29 +#define STAT_PUNCHVECTOR_Y 30 +#define STAT_PUNCHVECTOR_Z 31 // stock defines // diff --git a/Quake/r_part_fte.c b/Quake/r_part_fte.c index 7dc03059..fc946586 100644 --- a/Quake/r_part_fte.c +++ b/Quake/r_part_fte.c @@ -1691,7 +1691,10 @@ skipread: ptype->t2 = atof(Cmd_Argv(4))/tscale; ptype->randsmax = atoi(Cmd_Argv(6)); - ptype->texsstride = atof(Cmd_Argv(7)); + if (Cmd_Argc()>7) + ptype->texsstride = atof(Cmd_Argv(7));/*FIXME: divide-by-tscale missing */ + else + ptype->texsstride = 1/tscale; if (ptype->randsmax < 1 || ptype->texsstride == 0) ptype->randsmax = 1; diff --git a/Quake/sbar.c b/Quake/sbar.c index c7d101f9..ae589068 100644 --- a/Quake/sbar.c +++ b/Quake/sbar.c @@ -993,6 +993,49 @@ void Sbar_Draw (void) if (scr_con_current == vid.height) return; // console is full screen + if (cl.qcvm.extfuncs.CSQC_DrawHud) + { + qboolean deathmatchoverlay = false; + float s = CLAMP (1.0, scr_sbarscale.value, (float)glwidth / 320.0); + sb_updates++; + GL_SetCanvas (CANVAS_CSQC); //johnfitz + glEnable (GL_BLEND); //in the finest tradition of glquake, we litter gl state calls all over the place. yay state trackers. + glDisable (GL_ALPHA_TEST); //in the finest tradition of glquake, we litter gl state calls all over the place. yay state trackers. + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + PR_SwitchQCVM(&cl.qcvm); + if (qcvm->extglobals.cltime) + *qcvm->extglobals.cltime = realtime; + if (qcvm->extglobals.player_localentnum) + *qcvm->extglobals.player_localentnum = cl.viewentity; + pr_global_struct->time = cl.time; + Sbar_SortFrags (); + G_VECTORSET(OFS_PARM0, vid.width/s, vid.height/s, 0); + G_FLOAT(OFS_PARM1) = sb_showscores; + PR_ExecuteProgram(cl.qcvm.extfuncs.CSQC_DrawHud); + if (cl.qcvm.extfuncs.CSQC_DrawScores) + { + G_VECTORSET(OFS_PARM0, vid.width/s, vid.height/s, 0); + G_FLOAT(OFS_PARM1) = sb_showscores; + if (key_dest != key_menu) + PR_ExecuteProgram(cl.qcvm.extfuncs.CSQC_DrawScores); + } + else + deathmatchoverlay = (sb_showscores || cl.stats[STAT_HEALTH] <= 0); + PR_SwitchQCVM(NULL); + glDisable (GL_BLEND); + glEnable (GL_ALPHA_TEST); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); //back to ignoring vertex colours. + glDisable(GL_SCISSOR_TEST); + glColor3f (1,1,1); + + if (deathmatchoverlay && cl.gametype == GAME_DEATHMATCH) + { + GL_SetCanvas (CANVAS_SBAR); + Sbar_DeathmatchOverlay (); + } + return; + } + if (cl.intermission) return; //johnfitz -- never draw sbar during intermission @@ -1347,6 +1390,36 @@ void Sbar_IntermissionOverlay (void) int dig; int num; + if (cl.qcvm.extfuncs.CSQC_DrawScores) + { + float s = CLAMP (1.0, scr_sbarscale.value, (float)glwidth / 320.0); + GL_SetCanvas (CANVAS_CSQC); + glEnable (GL_BLEND); + glDisable (GL_ALPHA_TEST); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); + PR_SwitchQCVM(&cl.qcvm); + if (qcvm->extglobals.cltime) + *qcvm->extglobals.cltime = realtime; + if (qcvm->extglobals.player_localentnum) + *qcvm->extglobals.player_localentnum = cl.viewentity; + if (qcvm->extglobals.intermission) + *qcvm->extglobals.intermission = cl.intermission; + if (qcvm->extglobals.intermission_time) + *qcvm->extglobals.intermission_time = cl.completed_time; + pr_global_struct->time = cl.time; + Sbar_SortFrags (); + G_VECTORSET(OFS_PARM0, vid.width/s, vid.height/s, 0); + G_FLOAT(OFS_PARM1) = sb_showscores; + PR_ExecuteProgram(cl.qcvm.extfuncs.CSQC_DrawScores); + PR_SwitchQCVM(NULL); + glDisable (GL_BLEND); + glEnable (GL_ALPHA_TEST); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + glDisable(GL_SCISSOR_TEST); + glColor3f (1,1,1); + return; + } + if (cl.gametype == GAME_DEATHMATCH) { Sbar_DeathmatchOverlay (); diff --git a/Quake/screen.h b/Quake/screen.h index 73c888c8..1038bec4 100644 --- a/Quake/screen.h +++ b/Quake/screen.h @@ -68,6 +68,7 @@ typedef enum { CANVAS_BOTTOMLEFT, CANVAS_BOTTOMRIGHT, CANVAS_TOPRIGHT, + CANVAS_CSQC, CANVAS_INVALID = -1 } canvastype; extern cvar_t scr_menuscale; diff --git a/Quake/server.h b/Quake/server.h index a89b6576..47a3d2d4 100644 --- a/Quake/server.h +++ b/Quake/server.h @@ -45,23 +45,17 @@ typedef struct qboolean paused; qboolean loadgame; // handle connections specially - double time; - int lastcheck; // used by PF_checkclient double lastchecktime; + qcvm_t qcvm; // Spike: entire qcvm state + char name[64]; // map name char modelname[64]; // maps/.bsp, for model_precache[0] - struct qmodel_s *worldmodel; const char *model_precache[MAX_MODELS]; // NULL terminated struct qmodel_s *models[MAX_MODELS]; const char *sound_precache[MAX_SOUNDS]; // NULL terminated const char *lightstyles[MAX_LIGHTSTYLES]; - int num_edicts; - int max_edicts; - edict_t *edicts; // can NOT be array indexed, because - // edict_t is variable sized, but can - // be used to reference the world ent server_state_t state; // some actions are only valid during load sizebuf_t datagram; @@ -94,6 +88,15 @@ typedef struct } *ambientsounds; int num_ambients; int max_ambients; + + struct svcustomstat_s + { + int idx; + int type; + int fld; + eval_t *ptr; + } customstats[MAX_CL_STATS*2]; //strings or numeric... + size_t numcustomstats; } server_t; diff --git a/Quake/sv_main.c b/Quake/sv_main.c index d27e4a8e..5674447d 100644 --- a/Quake/sv_main.c +++ b/Quake/sv_main.c @@ -36,10 +36,11 @@ unsigned int sv_protocol_pext2; //spike void SV_CalcStats(client_t *client, int *statsi, float *statsf) { + size_t i; edict_t *ent = client->edict; //FIXME: string stats! int items; - eval_t *val = GetEdictFieldValue(ent, pr_extfields.items2); + eval_t *val = GetEdictFieldValue(ent, qcvm->extfields.items2); if (val) items = (int)ent->v.items | ((int)val->_float << 23); else @@ -67,7 +68,7 @@ void SV_CalcStats(client_t *client, int *statsi, float *statsf) // statsi[STAT_TIME] = sv.time*1000; //in ms, this was a hack to work around vanilla QW clients not having any concept of serverside time. // statsi[STAT_MATCHSTARTTIME] = 0*1000; //in ms, set by the mod to when the current match actually starts (so pregame stuff doesn't interfere with game clocks). // stats[STAT_VIEW2] = NUM_FOR_EDICT(PROG_TO_EDICT(ent->v.view2)); - if ((val = GetEdictFieldValue(ent, pr_extfields.viewzoom)) && val->_float) + if ((val = GetEdictFieldValue(ent, qcvm->extfields.viewzoom)) && val->_float) { statsf[STAT_VIEWZOOM] = val->_float*255; if (statsf[STAT_VIEWZOOM] < 1) @@ -108,6 +109,38 @@ void SV_CalcStats(client_t *client, int *statsi, float *statsf) // statsf[STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION] = sv_gravity.value; } */ + + for (i = 0; i < sv.numcustomstats; i++) + { + eval_t *eval = sv.customstats[i].ptr; + if (!eval) + eval = GetEdictFieldValue(ent, sv.customstats[i].fld); + + switch(sv.customstats[i].type) + { + case ev_ext_integer: + statsi[sv.customstats[i].idx] = eval->_int; + break; + case ev_entity: + statsi[sv.customstats[i].idx] = NUM_FOR_EDICT(PROG_TO_EDICT(eval->edict)); + break; + case ev_float: + statsf[sv.customstats[i].idx] = eval->_float; + break; + case ev_vector: + statsf[sv.customstats[i].idx+0] = eval->vector[0]; + statsf[sv.customstats[i].idx+1] = eval->vector[1]; + statsf[sv.customstats[i].idx+2] = eval->vector[2]; + break; + case ev_string: //not supported in this build... send with svcfte_updatestatstring on change, which is annoying. + case ev_void: //nothing... + case ev_field: //panic! everyone panic! + case ev_function: //doesn't make much sense + case ev_pointer: //doesn't make sense + default: + break; + } + } } @@ -241,7 +274,18 @@ static unsigned int MSGFTE_DeltaCalcBits(entity_state_t *from, entity_state_t *t return bits; } -#define MSG_WriteEntity MSG_WriteShort +//#undef MSG_WriteEntity +void MSG_WriteEntity (sizebuf_t *sb, int c, unsigned int pext2) +{ + //high short, low byte + if (c > 0x7fff && (pext2 & PEXT2_REPLACEMENTDELTAS)) + { + MSG_WriteShort(sb, 0x8000|(c>>8)); + MSG_WriteByte(sb, c&0xff); + } + else + MSG_WriteShort(sb, c); +} static void MSGFTE_WriteEntityUpdate(unsigned int bits, entity_state_t *state, sizebuf_t *msg, unsigned int pext2, unsigned int protocolflags) { unsigned int predbits = 0; @@ -486,7 +530,7 @@ static void MSGFTE_WriteEntityUpdate(unsigned int bits, entity_state_t *state, s } */ if (bits & UF_TAGINFO) { - MSG_WriteEntity(msg, state->tagentity); + MSG_WriteEntity(msg, state->tagentity, pext2); MSG_WriteByte(msg, state->tagindex); } /* if (bits & UF_LIGHT) @@ -593,7 +637,7 @@ static void SVFTE_SetupFrames(client_t *client) for (fr = 0; fr < client->numframes; fr++) client->frames[fr].sequence = client->lastacksequence; - client->numpendingentities = sv.num_edicts; + client->numpendingentities = qcvm->num_edicts; client->pendingentities_bits = calloc(client->numpendingentities, sizeof(*client->pendingentities_bits)); client->pendingentities_bits[0] = UF_REMOVE; @@ -636,7 +680,7 @@ void SVFTE_Ack(client_t *client, int sequence) if (frame->sequence >= 0) { frame->sequence = -1; - host_client->ping_times[host_client->num_pings%NUM_PING_TIMES] = sv.time - frame->timestamp; + host_client->ping_times[host_client->num_pings%NUM_PING_TIMES] = qcvm->time - frame->timestamp; host_client->num_pings++; } } @@ -706,9 +750,9 @@ static void SVFTE_CalcEntityDeltas(client_t *client) { struct entity_num_state_s *olds, *news, *oldstop, *newstop; - if ((int)client->numpendingentities < sv.num_edicts) + if ((int)client->numpendingentities < qcvm->num_edicts) { - int newmax = sv.num_edicts+64; + int newmax = qcvm->num_edicts+64; client->pendingentities_bits = realloc(client->pendingentities_bits, sizeof(*client->pendingentities_bits) * newmax); memset(client->pendingentities_bits+client->numpendingentities, 0, sizeof(*client->pendingentities_bits)*(newmax-client->numpendingentities)); client->numpendingentities = newmax; @@ -780,7 +824,7 @@ static void SVFTE_WriteEntitiesToClient(client_t *client, sizebuf_t *msg, size_t size_t rollbacksize; //I'm too lazy to figure out sizes (especially if someone updates this for bone states or whatever) struct deltaframe_s *frame = &client->frames[sequence&(client->numframes-1)]; frame->sequence = sequence; //so we know that it wasn't stale later. - frame->timestamp = sv.time; + frame->timestamp = qcvm->time; msg->maxsize = overflowsize; @@ -792,7 +836,7 @@ static void SVFTE_WriteEntitiesToClient(client_t *client, sizebuf_t *msg, size_t frame->numents = 0; if (client->protocol_pext2 & PEXT2_PREDINFO) MSG_WriteShort(msg, (client->lastmovemessage&0xffff)); - MSG_WriteFloat(msg, sv.time); //should be the time the last physics frame was run. + MSG_WriteFloat(msg, qcvm->time); //should be the time the last physics frame was run. for (entnum = client->snapshotresume; entnum < client->numpendingentities; entnum++) { bits = client->pendingentities_bits[entnum]; @@ -892,15 +936,15 @@ void SV_BuildEntityState(edict_t *ent, entity_state_t *state) state->frame = ent->v.frame; state->colormap = ent->v.colormap; state->skin = ent->v.skin; - if ((val = GetEdictFieldValue(ent, pr_extfields.scale)) && val->_float) + if ((val = GetEdictFieldValue(ent, qcvm->extfields.scale)) && val->_float) state->scale = val->_float*16; else state->scale = 16; - if ((val = GetEdictFieldValue(ent, pr_extfields.alpha))) + if ((val = GetEdictFieldValue(ent, qcvm->extfields.alpha))) state->alpha = ENTALPHA_ENCODE(val->_float); else state->alpha = ent->alpha; - if ((val = GetEdictFieldValue(ent, pr_extfields.colormod)) && (val->vector[0]||val->vector[1]||val->vector[2])) + if ((val = GetEdictFieldValue(ent, qcvm->extfields.colormod)) && (val->vector[0]||val->vector[1]||val->vector[2])) { state->colormod[0] = val->vector[0]*32; state->colormod[1] = val->vector[1]*32; @@ -908,18 +952,18 @@ void SV_BuildEntityState(edict_t *ent, entity_state_t *state) } else state->colormod[0] = state->colormod[1] = state->colormod[2] = 32; - state->traileffectnum = GetEdictFieldValue(ent, pr_extfields.traileffectnum)->_float; - state->emiteffectnum = GetEdictFieldValue(ent, pr_extfields.emiteffectnum)->_float; - if ((val = GetEdictFieldValue(ent, pr_extfields.tag_entity)) && val->edict) + state->traileffectnum = GetEdictFieldValue(ent, qcvm->extfields.traileffectnum)->_float; + state->emiteffectnum = GetEdictFieldValue(ent, qcvm->extfields.emiteffectnum)->_float; + if ((val = GetEdictFieldValue(ent, qcvm->extfields.tag_entity)) && val->edict) state->tagentity = NUM_FOR_EDICT(PROG_TO_EDICT(val->edict)); else state->tagentity = 0; - if ((val = GetEdictFieldValue(ent, pr_extfields.tag_index))) + if ((val = GetEdictFieldValue(ent, qcvm->extfields.tag_index))) state->tagindex = val->_float; else state->tagindex = 0; state->effects = ent->v.effects; - if ((val = GetEdictFieldValue(ent, pr_extfields.modelflags))) + if ((val = GetEdictFieldValue(ent, qcvm->extfields.modelflags))) state->effects |= ((unsigned int)val->_float)<<24; if (!ent->v.movetype || ent->v.movetype == MOVETYPE_STEP) state->eflags |= EFLAGS_STEP; @@ -948,24 +992,24 @@ static void SVFTE_BuildSnapshotForClient (client_t *client) // find the client's PVS VectorAdd (clent->v.origin, clent->v.view_ofs, org); - pvs = SV_FatPVS (org, sv.worldmodel); + pvs = SV_FatPVS (org, qcvm->worldmodel); - if (maxentities > (unsigned int)sv.num_edicts) - maxentities = (unsigned int)sv.num_edicts; + if (maxentities > (unsigned int)qcvm->num_edicts) + maxentities = (unsigned int)qcvm->num_edicts; // send over all entities (excpet the client) that touch the pvs - ent = NEXT_EDICT(sv.edicts); + ent = NEXT_EDICT(qcvm->edicts); for (e=1 ; e_float; + emiteffect = GetEdictFieldValue(ent, qcvm->extfields.emiteffectnum)->_float; if (ent != clent) // clent is ALLWAYS sent { // ignore ents without visible models if ((!ent->v.modelindex || !PR_GetString(ent->v.model)[0]) && !emiteffect) continue; - val = GetEdictFieldValue(ent, pr_extfields.viewmodelforclient); + val = GetEdictFieldValue(ent, qcvm->extfields.viewmodelforclient); if (val && val->edict == proged) eflags |= EFLAGS_VIEWMODEL; else if (val && val->edict) @@ -974,7 +1018,7 @@ static void SVFTE_BuildSnapshotForClient (client_t *client) { //attached entities should use the pvs of the parent rather than the child (because the child will typically be bugging out around '0 0 0', so won't be useful) parent = ent; - while ((val = GetEdictFieldValue(parent, pr_extfields.tag_entity)) && val->edict) + while ((val = GetEdictFieldValue(parent, qcvm->extfields.tag_entity)) && val->edict) parent = PROG_TO_EDICT(val->edict); if (parent->num_leafs) { @@ -1057,7 +1101,7 @@ void MSG_WriteStaticOrBaseLine(sizebuf_t *buf, int idx, entity_state_t *state, u if (idx>=0) { MSG_WriteByte (buf, bits?svcdp_spawnbaseline2:svc_spawnbaseline); - MSG_WriteEntity (buf, idx); + MSG_WriteEntity (buf, idx, protocol_pext2); } else MSG_WriteByte (buf, bits?svcdp_spawnstatic2:svc_spawnstatic); @@ -1076,7 +1120,7 @@ void MSG_WriteStaticOrBaseLine(sizebuf_t *buf, int idx, entity_state_t *state, u if (idx>=0) { MSG_WriteByte (buf, bits?svc_spawnbaseline2:svc_spawnbaseline); - MSG_WriteEntity (buf, idx); + MSG_WriteEntity (buf, idx, protocol_pext2); } else MSG_WriteByte (buf, bits?svc_spawnstatic2:svc_spawnstatic); @@ -1198,6 +1242,7 @@ void SV_Init (void) extern cvar_t sv_nostep; extern cvar_t sv_freezenonclients; extern cvar_t sv_gameplayfix_spawnbeforethinks; + extern cvar_t sv_gameplayfix_setmodelrealbox; //spike: 1 to replicate a quakespasm bug, 0 for actual vanilla compat. extern cvar_t sv_friction; extern cvar_t sv_edgefriction; extern cvar_t sv_stopspeed; @@ -1212,9 +1257,6 @@ void SV_Init (void) extern cvar_t net_masters[]; //spike extern cvar_t rcon_password; //spike, proquake-compatible rcon - - sv.edicts = NULL; // ericw -- sv.edicts switched to use malloc() - Cvar_RegisterVariable (&sv_maxvelocity); Cvar_RegisterVariable (&sv_gravity); Cvar_RegisterVariable (&sv_friction); @@ -1230,6 +1272,7 @@ void SV_Init (void) Cvar_RegisterVariable (&sv_nostep); Cvar_RegisterVariable (&sv_freezenonclients); Cvar_RegisterVariable (&sv_gameplayfix_spawnbeforethinks); + Cvar_RegisterVariable (&sv_gameplayfix_setmodelrealbox); Cvar_RegisterVariable (&pr_checkextension); Cvar_RegisterVariable (&sv_altnoclip); //johnfitz @@ -1493,7 +1536,7 @@ void SV_SendServerinfo (client_t *client) { client->limit_unreliable = DATAGRAM_MTU;//some safe ethernet limit. these clients should accept pretty much anything, but any routers will not. client->limit_reliable = MAX_DATAGRAM; //quite large, ip allows 16 bits - client->limit_entities = sv.max_edicts; //we don't really know, 8k is probably a save guess but could be 32k, 65k, or even more... + client->limit_entities = qcvm->max_edicts; //we don't really know, 8k is probably a save guess but could be 32k, 65k, or even more... client->limit_models = MAX_MODELS; //not really sure, client's problem until >14bits client->limit_sounds = MAX_SOUNDS; //not really sure, client's problem until >14bits @@ -1530,7 +1573,7 @@ void SV_SendServerinfo (client_t *client) retry: MSG_WriteByte (&client->message, svc_print); // sprintf (message, "%c\nFITZQUAKE %1.2f SERVER (%i CRC)\n", 2, FITZQUAKE_VERSION, pr_crc); //johnfitz -- include fitzquake version - sprintf (message, "%c\n"ENGINE_NAME_AND_VER" Server (%i CRC)\n", 2, pr_crc); //spike -- quakespasm has moved on, and has its own server capabilities now. Advertising = good, right? + sprintf (message, "%c\n"ENGINE_NAME_AND_VER" Server (%i CRC)\n", 2, qcvm->crc); //spike -- quakespasm has moved on, and has its own server capabilities now. Advertising = good, right? MSG_WriteString (&client->message,message); // lack of serverinfo means any csqc info might as well be sent the lame dp way @@ -1579,7 +1622,7 @@ retry: else MSG_WriteByte (&client->message, GAME_COOP); - MSG_WriteString (&client->message, PR_GetString(sv.edicts->v.message)); + MSG_WriteString (&client->message, PR_GetString(qcvm->edicts->v.message)); //johnfitz -- only send the first 256 model and sound precaches if protocol is 15 for (i=1,s = sv.model_precache+1 ; *s && i < client->limit_models; s++,i++) @@ -1593,8 +1636,8 @@ retry: // send music MSG_WriteByte (&client->message, svc_cdtrack); - MSG_WriteByte (&client->message, sv.edicts->v.sounds); - MSG_WriteByte (&client->message, sv.edicts->v.sounds); + MSG_WriteByte (&client->message, qcvm->edicts->v.sounds); + MSG_WriteByte (&client->message, qcvm->edicts->v.sounds); // set view MSG_WriteByte (&client->message, svc_setview); @@ -1630,7 +1673,7 @@ retry: } //protocol 15 is too limited. let people know that they'll get a crappy experience. - if (client->limit_entities <= (unsigned)sv.num_edicts) + if (client->limit_entities <= (unsigned)qcvm->num_edicts) { Con_Warning("Protocol limitation (entities) for %s\n", NET_QSocketGetTrueAddressString(client->netconnection)); MSG_WriteByte (&client->message, svc_print); @@ -1933,7 +1976,7 @@ SV_WriteEntitiesToClient void SV_WriteEntitiesToClient (client_t *client, sizebuf_t *msg) { edict_t *clent = client->edict; - unsigned int e, i, maxedict=sv.num_edicts; + unsigned int e, i, maxedict=qcvm->num_edicts; int bits; byte *pvs; vec3_t org; @@ -1951,10 +1994,10 @@ void SV_WriteEntitiesToClient (client_t *client, sizebuf_t *msg) // find the client's PVS VectorAdd (clent->v.origin, clent->v.view_ofs, org); - pvs = SV_FatPVS (org, sv.worldmodel); + pvs = SV_FatPVS (org, qcvm->worldmodel); // send over all entities (excpet the client) that touch the pvs - ent = NEXT_EDICT(sv.edicts); + ent = NEXT_EDICT(qcvm->edicts); for (e=1 ; eextfields.alpha); if (val) ent->alpha = ENTALPHA_ENCODE(val->_float); @@ -2151,7 +2194,7 @@ void SV_WriteEntitiesToClient (client_t *client, sizebuf_t *msg) if (bits & U_MODEL2) MSG_WriteByte(msg, (int)ent->v.modelindex >> 8); if (bits & U_LERPFINISH) - MSG_WriteByte(msg, (byte)(Q_rint((ent->v.nextthink-sv.time)*255))); + MSG_WriteByte(msg, (byte)(Q_rint((ent->v.nextthink-qcvm->time)*255))); //johnfitz } } @@ -2176,8 +2219,8 @@ void SV_CleanupEnts (void) int e; edict_t *ent; - ent = NEXT_EDICT(sv.edicts); - for (e=1 ; eedicts); + for (e=1 ; enum_edicts ; e++, ent = NEXT_EDICT(ent)) { ent->v.effects = (int)ent->v.effects & ~EF_MUZZLEFLASH; } @@ -2253,7 +2296,7 @@ void SV_WriteClientdataToMessage (client_t *client, sizebuf_t *msg) // stuff the sigil bits into the high bits of items for sbar, or else // mix in items2 - val = GetEdictFieldValue(ent, pr_extfields.items2); + val = GetEdictFieldValue(ent, qcvm->extfields.items2); if (val) items = (int)ent->v.items | ((int)val->_float << 23); @@ -2442,7 +2485,7 @@ qboolean SV_SendClientDatagram (client_t *client) else { MSG_WriteByte (&msg, svc_time); - MSG_WriteFloat (&msg, sv.time); + MSG_WriteFloat (&msg, qcvm->time); if (client->protocol_pext2 & PEXT2_PREDINFO) MSG_WriteShort(&msg, (client->lastmovemessage&0xffff)); @@ -2626,7 +2669,7 @@ int SV_SendPrespawnBaselines(int idx) while (1) { - if (idx >= sv.num_edicts) + if (idx >= qcvm->num_edicts) return -1; svent = EDICT_NUM(idx); @@ -2801,7 +2844,7 @@ void SV_CreateBaseline (void) int entnum; eval_t *val; - for (entnum = 0; entnum < sv.num_edicts; entnum++) + for (entnum = 0; entnum < qcvm->num_edicts; entnum++) { // get the current server version svent = EDICT_NUM(entnum); @@ -2827,7 +2870,7 @@ void SV_CreateBaseline (void) { svent->baseline.colormap = 0; svent->baseline.modelindex = SV_ModelIndex(PR_GetString(svent->v.model)); - val = GetEdictFieldValue(svent, pr_extfields.alpha); + val = GetEdictFieldValue(svent, qcvm->extfields.alpha); if (val) svent->baseline.alpha = ENTALPHA_ENCODE(val->_float); else @@ -2923,7 +2966,9 @@ void SV_SpawnServer (const char *server) // if (sv.active) { + PR_SwitchQCVM(NULL); SV_SendReconnect (); + PR_SwitchQCVM(&sv.qcvm); } // @@ -2958,12 +3003,12 @@ void SV_SpawnServer (const char *server) else sv.protocolflags = 0; // load progs to get entity field count - PR_LoadProgs (); + PR_LoadProgs ("progs.dat", true, pr_ssqcbuiltins, pr_ssqcnumbuiltins); // allocate server memory /* Host_ClearMemory() called above already cleared the whole sv structure */ - sv.max_edicts = CLAMP (MIN_EDICTS,(int)max_edicts.value,MAX_EDICTS); //johnfitz -- max_edicts cvar - sv.edicts = (edict_t *) malloc (sv.max_edicts*pr_edict_size); // ericw -- sv.edicts switched to use malloc() + qcvm->max_edicts = CLAMP (MIN_EDICTS,(int)max_edicts.value,MAX_EDICTS); //johnfitz -- max_edicts cvar + qcvm->edicts = (edict_t *) malloc (qcvm->max_edicts*qcvm->edict_size); // ericw -- sv.edicts switched to use malloc() sv.datagram.maxsize = sizeof(sv.datagram_buf); sv.datagram.cursize = 0; @@ -2982,8 +3027,8 @@ void SV_SpawnServer (const char *server) sv.signon.data = sv.signon_buf; // leave slots at start for clients only - sv.num_edicts = svs.maxclients+1; - memset(sv.edicts, 0, sv.num_edicts*pr_edict_size); // ericw -- sv.edicts switched to use malloc() + qcvm->num_edicts = qcvm->reserved_edicts = svs.maxclients+1; + memset(qcvm->edicts, 0, qcvm->num_edicts*qcvm->edict_size); // ericw -- sv.edicts switched to use malloc() for (i=0 ; itime = 1.0; q_strlcpy (sv.name, server, sizeof(sv.name)); q_snprintf (sv.modelname, sizeof(sv.modelname), "maps/%s.bsp", server); - sv.worldmodel = Mod_ForName (sv.modelname, false); - if (!sv.worldmodel || sv.worldmodel->type != mod_brush) + qcvm->worldmodel = Mod_ForName (sv.modelname, false); + if (!qcvm->worldmodel || qcvm->worldmodel->type != mod_brush) { Con_Printf ("Couldn't spawn server %s\n", sv.modelname); sv.active = false; return; } - sv.models[1] = sv.worldmodel; + sv.models[1] = qcvm->worldmodel; // // clear world interaction links @@ -3014,13 +3059,13 @@ void SV_SpawnServer (const char *server) sv.sound_precache[0] = dummy; sv.model_precache[0] = dummy; sv.model_precache[1] = sv.modelname; - if (sv.worldmodel->numsubmodels > MAX_MODELS) + if (qcvm->worldmodel->numsubmodels > MAX_MODELS) { Con_Printf ("too many inline models %s\n", sv.modelname); sv.active = false; return; } - for (i=1 ; inumsubmodels ; i++) + for (i=1 ; iworldmodel->numsubmodels ; i++) { sv.model_precache[1+i] = localmodels[i]; sv.models[i+1] = Mod_ForName (localmodels[i], false); @@ -3030,9 +3075,9 @@ void SV_SpawnServer (const char *server) // load the rest of the entities // ent = EDICT_NUM(0); - memset (&ent->v, 0, progs->entityfields * 4); + memset (&ent->v, 0, qcvm->progs->entityfields * 4); ent->free = false; - ent->v.model = PR_SetEngineString(sv.worldmodel->name); + ent->v.model = PR_SetEngineString(qcvm->worldmodel->name); ent->v.modelindex = 1; // world model ent->v.solid = SOLID_BSP; ent->v.movetype = MOVETYPE_PUSH; @@ -3047,7 +3092,7 @@ void SV_SpawnServer (const char *server) // serverflags are for cross level information (sigils) pr_global_struct->serverflags = svs.serverflags; - ED_LoadFromFile (sv.worldmodel->entities); + ED_LoadFromFile (qcvm->worldmodel->entities); sv.active = true; @@ -3069,8 +3114,11 @@ void SV_SpawnServer (const char *server) // send serverinfo to all connected clients for (i=0,host_client = svs.clients ; iknowntoqc = false; if (host_client->active) SV_SendServerinfo (host_client); + } Con_DPrintf ("Server spawned.\n"); } diff --git a/Quake/sv_move.c b/Quake/sv_move.c index fd190848..f9d1b0f0 100644 --- a/Quake/sv_move.c +++ b/Quake/sv_move.c @@ -129,7 +129,7 @@ qboolean SV_movestep (edict_t *ent, vec3_t move, qboolean relink) { VectorAdd (ent->v.origin, move, neworg); enemy = PROG_TO_EDICT(ent->v.enemy); - if (i == 0 && enemy != sv.edicts) + if (i == 0 && enemy != qcvm->edicts) { dz = ent->v.origin[2] - PROG_TO_EDICT(ent->v.enemy)->v.origin[2]; if (dz > 40) @@ -150,7 +150,7 @@ qboolean SV_movestep (edict_t *ent, vec3_t move, qboolean relink) return true; } - if (enemy == sv.edicts) + if (enemy == qcvm->edicts) break; } @@ -408,7 +408,7 @@ void SV_MoveToGoal (void) } // if the next step hits the enemy, return immediately - if ( PROG_TO_EDICT(ent->v.enemy) != sv.edicts && SV_CloseEnough (ent, goal, dist) ) + if ( PROG_TO_EDICT(ent->v.enemy) != qcvm->edicts && SV_CloseEnough (ent, goal, dist) ) return; // bump around... diff --git a/Quake/sv_phys.c b/Quake/sv_phys.c index 8c4fc5e6..293596e1 100644 --- a/Quake/sv_phys.c +++ b/Quake/sv_phys.c @@ -65,8 +65,8 @@ void SV_CheckAllEnts (void) edict_t *check; // see if any solid entities are inside the final position - check = NEXT_EDICT(sv.edicts); - for (e=1 ; eedicts); + for (e=1 ; enum_edicts ; e++, check = NEXT_EDICT(check)) { if (check->free) continue; @@ -128,11 +128,11 @@ qboolean SV_RunThink (edict_t *ent) int i; //johnfitz thinktime = ent->v.nextthink; - if (thinktime <= 0 || thinktime > sv.time + host_frametime) + if (thinktime <= 0 || thinktime > qcvm->time + host_frametime) return true; - if (thinktime < sv.time) - thinktime = sv.time; // don't let things stay in the past. + if (thinktime < qcvm->time) + thinktime = qcvm->time; // don't let things stay in the past. // it is possible to start that way // by a trigger with a local time. @@ -141,7 +141,7 @@ qboolean SV_RunThink (edict_t *ent) ent->v.nextthink = 0; pr_global_struct->time = thinktime; pr_global_struct->self = EDICT_TO_PROG(ent); - pr_global_struct->other = EDICT_TO_PROG(sv.edicts); + pr_global_struct->other = EDICT_TO_PROG(qcvm->edicts); PR_ExecuteProgram (ent->v.think); //johnfitz -- PROTOCOL_FITZQUAKE @@ -173,7 +173,7 @@ void SV_Impact (edict_t *e1, edict_t *e2) old_self = pr_global_struct->self; old_other = pr_global_struct->other; - pr_global_struct->time = sv.time; + pr_global_struct->time = qcvm->time; if (e1->v.touch && e1->v.solid != SOLID_NOT) { pr_global_struct->self = EDICT_TO_PROG(e1); @@ -389,7 +389,7 @@ void SV_AddGravity (edict_t *ent) float ent_gravity; eval_t *val; - val = GetEdictFieldValue(ent, pr_extfields.gravity); + val = GetEdictFieldValue(ent, qcvm->extfields.gravity); if (val && val->_float) ent_gravity = val->_float; else @@ -479,14 +479,14 @@ void SV_PushMove (edict_t *pusher, float movetime) //johnfitz -- dynamically allocate mark = Hunk_LowMark (); - moved_edict = (edict_t **) Hunk_Alloc (sv.num_edicts*sizeof(edict_t *)); - moved_from = (vec3_t *) Hunk_Alloc (sv.num_edicts*sizeof(vec3_t)); + moved_edict = (edict_t **) Hunk_Alloc (qcvm->num_edicts*sizeof(edict_t *)); + moved_from = (vec3_t *) Hunk_Alloc (qcvm->num_edicts*sizeof(vec3_t)); //johnfitz // see if any solid entities are inside the final position num_moved = 0; - check = NEXT_EDICT(sv.edicts); - for (e=1 ; eedicts); + for (e=1 ; enum_edicts ; e++, check = NEXT_EDICT(check)) { if (check->free) continue; @@ -612,9 +612,9 @@ void SV_Physics_Pusher (edict_t *ent) if (thinktime > oldltime && thinktime <= ent->v.ltime) { ent->v.nextthink = 0; - pr_global_struct->time = sv.time; + pr_global_struct->time = qcvm->time; pr_global_struct->self = EDICT_TO_PROG(ent); - pr_global_struct->other = EDICT_TO_PROG(sv.edicts); + pr_global_struct->other = EDICT_TO_PROG(qcvm->edicts); PR_ExecuteProgram (ent->v.think); if (ent->free) return; @@ -925,7 +925,7 @@ void SV_Physics_Client (edict_t *ent, int num) // // call standard client pre-think // - pr_global_struct->time = sv.time; + pr_global_struct->time = qcvm->time; pr_global_struct->self = EDICT_TO_PROG(ent); PR_ExecuteProgram (pr_global_struct->PlayerPreThink); @@ -979,7 +979,7 @@ void SV_Physics_Client (edict_t *ent, int num) // SV_LinkEdict (ent, true); - pr_global_struct->time = sv.time; + pr_global_struct->time = qcvm->time; pr_global_struct->self = EDICT_TO_PROG(ent); PR_ExecuteProgram (pr_global_struct->PlayerPostThink); } @@ -1241,9 +1241,9 @@ void SV_Physics (void) edict_t *ent; // let the progs know that a new frame has started - pr_global_struct->self = EDICT_TO_PROG(sv.edicts); - pr_global_struct->other = EDICT_TO_PROG(sv.edicts); - pr_global_struct->time = sv.time; + pr_global_struct->self = EDICT_TO_PROG(qcvm->edicts); + pr_global_struct->other = EDICT_TO_PROG(qcvm->edicts); + pr_global_struct->time = qcvm->time; PR_ExecuteProgram (pr_global_struct->StartFrame); //SV_CheckAllEnts (); @@ -1251,12 +1251,12 @@ void SV_Physics (void) // // treat each object in turn // - ent = sv.edicts; + ent = qcvm->edicts; if (sv_freezenonclients.value) entity_cap = svs.maxclients + 1; // Only run physics on clients and the world else - entity_cap = sv.num_edicts; + entity_cap = qcvm->num_edicts; //for (i=0 ; iforce_retouch--; - if (pr_extfuncs.endframe) + if (qcvm->extfuncs.EndFrame) { - pr_global_struct->self = EDICT_TO_PROG(sv.edicts); - pr_global_struct->other = EDICT_TO_PROG(sv.edicts); - pr_global_struct->time = sv.time; - PR_ExecuteProgram (pr_extfuncs.endframe); + pr_global_struct->self = EDICT_TO_PROG(qcvm->edicts); + pr_global_struct->other = EDICT_TO_PROG(qcvm->edicts); + pr_global_struct->time = qcvm->time; + PR_ExecuteProgram (qcvm->extfuncs.EndFrame); } if (!sv_freezenonclients.value) - sv.time += host_frametime; + qcvm->time += host_frametime; } diff --git a/Quake/sv_user.c b/Quake/sv_user.c index 3bd8fc63..8ea6d48f 100644 --- a/Quake/sv_user.c +++ b/Quake/sv_user.c @@ -284,7 +284,7 @@ void SV_WaterMove (void) void SV_WaterJump (void) { - if (sv.time > sv_player->v.teleport_time + if (qcvm->time > sv_player->v.teleport_time || !sv_player->v.waterlevel) { sv_player->v.flags = (int)sv_player->v.flags & ~FL_WATERJUMP; @@ -335,7 +335,7 @@ void SV_AirMove (void) smove = cmd.sidemove; // hack to not let you back into teleporter - if (sv.time < sv_player->v.teleport_time && fmove < 0) + if (qcvm->time < sv_player->v.teleport_time && fmove < 0) fmove = 0; for (i=0 ; i<3 ; i++) @@ -486,7 +486,7 @@ void SV_ReadClientMove (usercmd_t *move) if (!(host_client->protocol_pext2 & PEXT2_PREDINFO)) { host_client->ping_times[host_client->num_pings%NUM_PING_TIMES] - = sv.time - timestamp; + = qcvm->time - timestamp; host_client->num_pings++; } //otherwise time is still useful for determining the input frame's time value @@ -500,23 +500,23 @@ void SV_ReadClientMove (usercmd_t *move) host_client->edict->v.button0 = (buttonbits & 1)>>0; //button1 was meant to be 'use', but got reused by too many mods to get implemented now host_client->edict->v.button2 = (buttonbits & 2)>>1; - if ((val = GetEdictFieldValue(host_client->edict, pr_extfields.button3))) + if ((val = GetEdictFieldValue(host_client->edict, qcvm->extfields.button3))) val->_float = (buttonbits & 4)>>2; - if ((val = GetEdictFieldValue(host_client->edict, pr_extfields.button4))) + if ((val = GetEdictFieldValue(host_client->edict, qcvm->extfields.button4))) val->_float = (buttonbits & 8)>>3; - if ((val = GetEdictFieldValue(host_client->edict, pr_extfields.button5))) + if ((val = GetEdictFieldValue(host_client->edict, qcvm->extfields.button5))) val->_float = (buttonbits & 0x10)>>4; - if ((val = GetEdictFieldValue(host_client->edict, pr_extfields.button6))) + if ((val = GetEdictFieldValue(host_client->edict, qcvm->extfields.button6))) val->_float = (buttonbits & 0x20)>>5; - if ((val = GetEdictFieldValue(host_client->edict, pr_extfields.button7))) + if ((val = GetEdictFieldValue(host_client->edict, qcvm->extfields.button7))) val->_float = (buttonbits & 0x40)>>6; - if ((val = GetEdictFieldValue(host_client->edict, pr_extfields.button8))) + if ((val = GetEdictFieldValue(host_client->edict, qcvm->extfields.button8))) val->_float = (buttonbits & 0x80)>>7; if (newimpulse) host_client->edict->v.impulse = newimpulse; - eval = GetEdictFieldValue(host_client->edict, pr_extfields.movement); + eval = GetEdictFieldValue(host_client->edict, qcvm->extfields.movement); if (eval) { eval->vector[0] = move->forwardmove; @@ -527,6 +527,98 @@ void SV_ReadClientMove (usercmd_t *move) //FIXME: attempt to apply physics command now, if the mod has custom physics+csqc-prediction } +void SV_ReadQCRequest(void) +{ + int e; + char args[8]; + const char *rname, *fname; + func_t f; + int i; + client_t *cl = host_client; + + for (i = 0; ; ) + { + byte ev = MSG_ReadByte(); + /*if (ev >= 200 && ev < 200+MAX_SPLITS) + { + ev -= 200; + while (ev-- && cl) + cl = cl->controlled; + continue; + }*/ + if (i >= sizeof(args)-1) + { + if (ev != ev_void) + { + msg_badread = true; + return; + } + goto done; + } + switch(ev) + { + default: + args[i] = '?'; + G_INT(OFS_PARM0+i*3) = MSG_ReadLong(); + break; + case ev_void: + goto done; + case ev_float: + args[i] = 'f'; + G_FLOAT(OFS_PARM0+i*3) = MSG_ReadFloat(); + break; + case ev_vector: + args[i] = 'v'; + G_FLOAT(OFS_PARM0+i*3+0) = MSG_ReadFloat(); + G_FLOAT(OFS_PARM0+i*3+1) = MSG_ReadFloat(); + G_FLOAT(OFS_PARM0+i*3+2) = MSG_ReadFloat(); + break; + case ev_ext_integer: + args[i] = 'i'; + G_INT(OFS_PARM0+i*3) = MSG_ReadLong(); + break; + case ev_string: + args[i] = 's'; + G_INT(OFS_PARM0+i*3) = PR_MakeTempString(MSG_ReadString()); + break; + case ev_entity: + args[i] = 'e'; + e = MSG_ReadEntity(host_client->protocol_pext2); + if (e < 0 || e >= qcvm->num_edicts) + e = 0; + G_INT(OFS_PARM0+i*3) = EDICT_TO_PROG(EDICT_NUM(e)); + break; + } + i++; + } + +done: + args[i] = 0; + rname = MSG_ReadString(); + if (i) + fname = va("CSEv_%s_%s", rname, args); + else + fname = va("CSEv_%s", rname); + f = PR_FindExtFunction(fname); + /*if (!f) + { + if (i) + rname = va("Cmd_%s_%s", rname, args); + else + rname = va("Cmd_%s", rname); + f = PR_FindExtFunction(rname); + }*/ + if (!cl) + ; //bad seat! not going to warn as they might have been removed recently + else if (f) + { + pr_global_struct->self = EDICT_TO_PROG(cl->edict); + PR_ExecuteProgram(f); + } + else + SV_ClientPrintf("qcrequest \"%s\" not supported\n", fname); +} + /* =================== SV_ReadClientMessage @@ -569,14 +661,14 @@ qboolean SV_ReadClientMessage (void) case clc_stringcmd: s = MSG_ReadString (); - if (q_strncasecmp(s, "spawn", 5) && q_strncasecmp(s, "begin", 5) && q_strncasecmp(s, "prespawn", 8) && pr_extfuncs.parseclientcommand) + if (q_strncasecmp(s, "spawn", 5) && q_strncasecmp(s, "begin", 5) && q_strncasecmp(s, "prespawn", 8) && qcvm->extfuncs.SV_ParseClientCommand) { //the spawn/begin/prespawn are because of numerous mods that disobey the rules. //at a minimum, we must be able to join the server, so that we can see any sprints/bprints (because dprint sucks, yes there's proper ways to deal with this, but moders don't always know them). client_t *ohc = host_client; G_INT(OFS_PARM0) = PR_SetEngineString(s); - pr_global_struct->time = sv.time; + pr_global_struct->time = qcvm->time; pr_global_struct->self = EDICT_TO_PROG(host_client->edict); - PR_ExecuteProgram(pr_extfuncs.parseclientcommand); + PR_ExecuteProgram(qcvm->extfuncs.SV_ParseClientCommand); host_client = ohc; } else @@ -601,6 +693,10 @@ qboolean SV_ReadClientMessage (void) Host_DownloadAck(host_client); break; + case clcfte_qcrequest: + SV_ReadQCRequest(); + break; + case clcfte_voicechat: SV_VoiceReadPacket(host_client); break; @@ -664,7 +760,7 @@ void SV_RunClients (void) //botclients can't receive packets. don't even try. //not sure where to put this code, but here seems sane enough. //fill in the user's desired stuff according to a few things. - eval_t *ev = GetEdictFieldValue(host_client->edict, pr_extfields.movement); + eval_t *ev = GetEdictFieldValue(host_client->edict, qcvm->extfields.movement); if (ev) //.movement normally works the other way around. oh well. { host_client->cmd.forwardmove = ev->vector[0]; diff --git a/Quake/sys_sdl_unix.c b/Quake/sys_sdl_unix.c index 100912ad..fb83a93e 100644 --- a/Quake/sys_sdl_unix.c +++ b/Quake/sys_sdl_unix.c @@ -391,6 +391,7 @@ void Sys_Error (const char *error, ...) fputs (errortxt1, stderr); Con_Redirect(NULL); + PR_SwitchQCVM(NULL); Host_Shutdown (); fputs (errortxt2, stderr); fputs (text, stderr); diff --git a/Quake/sys_sdl_win.c b/Quake/sys_sdl_win.c index a0c4882a..fc347bb3 100644 --- a/Quake/sys_sdl_win.c +++ b/Quake/sys_sdl_win.c @@ -312,6 +312,8 @@ void Sys_Error (const char *error, ...) q_vsnprintf (text, sizeof(text), error, argptr); va_end (argptr); + PR_SwitchQCVM(NULL); + Con_Redirect(NULL); if (isDedicated) @@ -377,7 +379,11 @@ void Sys_Quit (void) double Sys_DoubleTime (void) { +#if 1 + return SDL_GetPerformanceCounter() / (long double)SDL_GetPerformanceFrequency(); +#else return SDL_GetTicks() / 1000.0; +#endif } const char *Sys_ConsoleInput (void) diff --git a/Quake/vid.h b/Quake/vid.h index 46ae8d65..ddee02fd 100644 --- a/Quake/vid.h +++ b/Quake/vid.h @@ -88,6 +88,7 @@ void *VID_GetWindow (void); qboolean VID_HasMouseOrInputFocus (void); qboolean VID_IsMinimized (void); void VID_Lock (void); +void VID_SetWindowCaption(const char *newcaption); #endif /* __VID_DEFS_H */ diff --git a/Quake/view.c b/Quake/view.c index 9d70ad90..c9ac6f54 100644 --- a/Quake/view.c +++ b/Quake/view.c @@ -279,6 +279,21 @@ void V_ParseDamage (void) for (i=0 ; i<3 ; i++) from[i] = MSG_ReadCoord (cl.protocolflags); + if (cl.qcvm.extfuncs.CSQC_Parse_Damage) + { + qboolean inhibit; + PR_SwitchQCVM(&cl.qcvm); + pr_global_struct->time = cl.time; + G_FLOAT(OFS_PARM0) = armor; + G_FLOAT(OFS_PARM1) = blood; + G_VECTORSET(OFS_PARM2, from[0], from[1], from[2]); + PR_ExecuteProgram(cl.qcvm.extfuncs.CSQC_Parse_Damage); + inhibit = G_FLOAT(OFS_RETURN); + PR_SwitchQCVM(NULL); + if (inhibit) + return; + } + count = blood*0.5 + armor*0.5; if (count < 10) count = 10; diff --git a/Quake/world.c b/Quake/world.c index bd5de451..ddae555e 100644 --- a/Quake/world.c +++ b/Quake/world.c @@ -183,21 +183,6 @@ ENTITY AREA CHECKING =============================================================================== */ -typedef struct areanode_s -{ - int axis; // -1 = leaf node - float dist; - struct areanode_s *children[2]; - link_t trigger_edicts; - link_t solid_edicts; -} areanode_t; - -#define AREA_DEPTH 4 -#define AREA_NODES 32 - -static areanode_t sv_areanodes[AREA_NODES]; -static int sv_numareanodes; - /* =============== SV_CreateAreaNode @@ -210,8 +195,8 @@ areanode_t *SV_CreateAreaNode (int depth, vec3_t mins, vec3_t maxs) vec3_t size; vec3_t mins1, maxs1, mins2, maxs2; - anode = &sv_areanodes[sv_numareanodes]; - sv_numareanodes++; + anode = &qcvm->areanodes[qcvm->numareanodes]; + qcvm->numareanodes++; ClearLink (&anode->trigger_edicts); ClearLink (&anode->solid_edicts); @@ -253,9 +238,9 @@ void SV_ClearWorld (void) { SV_InitBoxHull (); - memset (sv_areanodes, 0, sizeof(sv_areanodes)); - sv_numareanodes = 0; - SV_CreateAreaNode (0, sv.worldmodel->mins, sv.worldmodel->maxs); + memset (qcvm->areanodes, 0, sizeof(qcvm->areanodes)); + qcvm->numareanodes = 0; + SV_CreateAreaNode (0, qcvm->worldmodel->mins, qcvm->worldmodel->maxs); } @@ -341,10 +326,10 @@ void SV_TouchLinks (edict_t *ent) int mark; mark = Hunk_LowMark (); - list = (edict_t **) Hunk_Alloc (sv.num_edicts*sizeof(edict_t *)); + list = (edict_t **) Hunk_Alloc (qcvm->num_edicts*sizeof(edict_t *)); listcount = 0; - SV_AreaTriggerEdicts (ent, sv_areanodes, list, &listcount, sv.num_edicts); + SV_AreaTriggerEdicts (ent, qcvm->areanodes, list, &listcount, qcvm->num_edicts); for (i = 0; i < listcount; i++) { @@ -367,7 +352,7 @@ void SV_TouchLinks (edict_t *ent) pr_global_struct->self = EDICT_TO_PROG(touch); pr_global_struct->other = EDICT_TO_PROG(ent); - pr_global_struct->time = sv.time; + pr_global_struct->time = qcvm->time; PR_ExecuteProgram (touch->v.touch); pr_global_struct->self = old_self; @@ -403,7 +388,7 @@ void SV_FindTouchedLeafs (edict_t *ent, mnode_t *node) return; leaf = (mleaf_t *)node; - leafnum = leaf - sv.worldmodel->leafs - 1; + leafnum = leaf - qcvm->worldmodel->leafs - 1; ent->leafnums[ent->num_leafs] = leafnum; ent->num_leafs++; @@ -436,7 +421,7 @@ void SV_LinkEdict (edict_t *ent, qboolean touch_triggers) if (ent->area.prev) SV_UnlinkEdict (ent); // unlink from old position - if (ent == sv.edicts) + if (ent == qcvm->edicts) return; // don't add the world if (ent->free) @@ -471,13 +456,13 @@ void SV_LinkEdict (edict_t *ent, qboolean touch_triggers) // link to PVS leafs ent->num_leafs = 0; if (ent->v.modelindex) - SV_FindTouchedLeafs (ent, sv.worldmodel->nodes); + SV_FindTouchedLeafs (ent, qcvm->worldmodel->nodes); if (ent->v.solid == SOLID_NOT) return; // find the first node that the ent's box crosses - node = sv_areanodes; + node = qcvm->areanodes; while (1) { if (node->axis == -1) @@ -556,7 +541,7 @@ int SV_PointContents (vec3_t p) { int cont; - cont = SV_HullPointContents (&sv.worldmodel->hulls[0], 0, p); + cont = SV_HullPointContents (&qcvm->worldmodel->hulls[0], 0, p); if (cont <= CONTENTS_CURRENT_0 && cont >= CONTENTS_CURRENT_DOWN) cont = CONTENTS_WATER; return cont; @@ -564,7 +549,7 @@ int SV_PointContents (vec3_t p) int SV_TruePointContents (vec3_t p) { - return SV_HullPointContents (&sv.worldmodel->hulls[0], 0, p); + return SV_HullPointContents (&qcvm->worldmodel->hulls[0], 0, p); } //=========================================================================== @@ -583,7 +568,7 @@ edict_t *SV_TestEntityPosition (edict_t *ent) trace = SV_Move (ent->v.origin, ent->v.mins, ent->v.maxs, ent->v.origin, 0, ent); if (trace.startsolid) - return sv.edicts; + return qcvm->edicts; return NULL; } @@ -916,7 +901,7 @@ trace_t SV_Move (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int type, e memset ( &clip, 0, sizeof ( moveclip_t ) ); // clip to world - clip.trace = SV_ClipMoveToEntity ( sv.edicts, start, mins, maxs, end ); + clip.trace = SV_ClipMoveToEntity ( qcvm->edicts, start, mins, maxs, end ); clip.start = start; clip.end = end; @@ -943,7 +928,7 @@ trace_t SV_Move (vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int type, e SV_MoveBounds ( start, clip.mins2, clip.maxs2, end, clip.boxmins, clip.boxmaxs ); // clip to entities - SV_ClipToLinks ( sv_areanodes, &clip ); + SV_ClipToLinks ( qcvm->areanodes, &clip ); return clip.trace; } diff --git a/Quakespasm-Spiked.txt b/Quakespasm-Spiked.txt index b26bc953..0e1e87f6 100755 --- a/Quakespasm-Spiked.txt +++ b/Quakespasm-Spiked.txt @@ -36,6 +36,11 @@ QuakeSpasm-Spiked copy of regular QuakeSpasm, you should be able to have both installed in the same game directory without issue - on the condition that they're both win32 or both win64, not a mix. + Note that advanced users can use the -basedir argument in order to install + engines within sub-dirs/where-ever in order to avoid conflicts be they + win32 vs win64, qs vs qss, or even qss vs any other engine - this argument + should exist in ALL quake engines, and thus it need not be just qss that + is given it. However, this will not prevent other sorts of conflicts. ------------- 3. New Toys!