]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - clvm_cmds.c
DP_QC_SPRINTF :P
[xonotic/darkplaces.git] / clvm_cmds.c
index fd0af2073b462a5d7ef553679cbf010790d7f725..67212a3295cabfd0661c5958fee3b71e53a37aec 100644 (file)
@@ -21,6 +21,7 @@
 //4 feature darkplaces csqc: add builtins to clientside qc for gl calls
 
 extern cvar_t v_flipped;
+extern cvar_t r_equalize_entities_fullbright;
 
 sfx_t *S_FindName(const char *name);
 int Sbar_GetSortedPlayerIndex (int index);
@@ -161,6 +162,7 @@ static void VM_CL_sound (void)
        prvm_edict_t            *entity;
        float                           volume;
        float                           attenuation;
+       vec3_t                          org;
 
        VM_SAFEPARMCOUNT(5, VM_CL_sound);
 
@@ -188,7 +190,8 @@ static void VM_CL_sound (void)
                return;
        }
 
-       S_StartSound(32768 + PRVM_NUM_FOR_EDICT(entity), channel, S_FindName(sample), entity->fields.client->origin, volume, attenuation);
+       CL_VM_GetEntitySoundOrigin(MAX_EDICTS + PRVM_NUM_FOR_EDICT(entity), org);
+       S_StartSound(MAX_EDICTS + PRVM_NUM_FOR_EDICT(entity), channel, S_FindName(sample), org, volume, attenuation);
 }
 
 // #483 void(vector origin, string sample, float volume, float attenuation) pointsound
@@ -218,8 +221,8 @@ static void VM_CL_pointsound(void)
                return;
        }
 
-       // Send World Entity as Entity to Play Sound (for CSQC, that is 32768)
-       S_StartSound(32768, 0, S_FindName(sample), org, volume, attenuation);
+       // Send World Entity as Entity to Play Sound (for CSQC, that is MAX_EDICTS)
+       S_StartSound(MAX_EDICTS, 0, S_FindName(sample), org, volume, attenuation);
 }
 
 // #14 entity() spawn
@@ -241,7 +244,7 @@ void CL_VM_SetTraceGlobals(const trace_t *trace, int svent)
 #define CL_HitNetworkBrushModels(move) !((move) == MOVE_WORLDONLY)
 #define CL_HitNetworkPlayers(move)     !((move) == MOVE_WORLDONLY || (move) == MOVE_NOMONSTERS)
 
-// #16 float(vector v1, vector v2, float movetype, entity ignore) traceline
+// #16 void(vector v1, vector v2, float movetype, entity ignore) traceline
 static void VM_CL_traceline (void)
 {
        float   *v1, *v2;
@@ -258,10 +261,10 @@ static void VM_CL_traceline (void)
        move = (int)PRVM_G_FLOAT(OFS_PARM2);
        ent = PRVM_G_EDICT(OFS_PARM3);
 
-       if (IS_NAN(v1[0]) || IS_NAN(v1[1]) || IS_NAN(v1[2]) || IS_NAN(v2[0]) || IS_NAN(v1[2]) || IS_NAN(v2[2]))
+       if (IS_NAN(v1[0]) || IS_NAN(v1[1]) || IS_NAN(v1[2]) || IS_NAN(v2[0]) || IS_NAN(v2[1]) || IS_NAN(v2[2]))
                PRVM_ERROR("%s: NAN errors detected in traceline('%f %f %f', '%f %f %f', %i, entity %i)\n", PRVM_NAME, v1[0], v1[1], v1[2], v2[0], v2[1], v2[2], move, PRVM_EDICT_TO_PROG(ent));
 
-       trace = CL_Move(v1, vec3_origin, vec3_origin, v2, move, ent, CL_GenericHitSuperContentsMask(ent), CL_HitNetworkBrushModels(move), CL_HitNetworkPlayers(move), &svent, true);
+       trace = CL_TraceLine(v1, v2, move, ent, CL_GenericHitSuperContentsMask(ent), CL_HitNetworkBrushModels(move), CL_HitNetworkPlayers(move), &svent, true);
 
        CL_VM_SetTraceGlobals(&trace, svent);
 }
@@ -296,10 +299,10 @@ static void VM_CL_tracebox (void)
        move = (int)PRVM_G_FLOAT(OFS_PARM4);
        ent = PRVM_G_EDICT(OFS_PARM5);
 
-       if (IS_NAN(v1[0]) || IS_NAN(v1[1]) || IS_NAN(v1[2]) || IS_NAN(v2[0]) || IS_NAN(v1[2]) || IS_NAN(v2[2]))
+       if (IS_NAN(v1[0]) || IS_NAN(v1[1]) || IS_NAN(v1[2]) || IS_NAN(v2[0]) || IS_NAN(v2[1]) || IS_NAN(v2[2]))
                PRVM_ERROR("%s: NAN errors detected in tracebox('%f %f %f', '%f %f %f', '%f %f %f', '%f %f %f', %i, entity %i)\n", PRVM_NAME, v1[0], v1[1], v1[2], m1[0], m1[1], m1[2], m2[0], m2[1], m2[2], v2[0], v2[1], v2[2], move, PRVM_EDICT_TO_PROG(ent));
 
-       trace = CL_Move(v1, m1, m2, v2, move, ent, CL_GenericHitSuperContentsMask(ent), CL_HitNetworkBrushModels(move), CL_HitNetworkPlayers(move), &svent, true);
+       trace = CL_TraceBox(v1, m1, m2, v2, move, ent, CL_GenericHitSuperContentsMask(ent), CL_HitNetworkBrushModels(move), CL_HitNetworkPlayers(move), &svent, true);
 
        CL_VM_SetTraceGlobals(&trace, svent);
 }
@@ -334,7 +337,7 @@ trace_t CL_Trace_Toss (prvm_edict_t *tossent, prvm_edict_t *ignore, int *svent)
                VectorMA (tossent->fields.client->angles, 0.05, tossent->fields.client->avelocity, tossent->fields.client->angles);
                VectorScale (tossent->fields.client->velocity, 0.05, move);
                VectorAdd (tossent->fields.client->origin, move, end);
-               trace = CL_Move (tossent->fields.client->origin, tossent->fields.client->mins, tossent->fields.client->maxs, end, MOVE_NORMAL, tossent, CL_GenericHitSuperContentsMask(tossent), true, true, NULL, true);
+               trace = CL_TraceBox(tossent->fields.client->origin, tossent->fields.client->mins, tossent->fields.client->maxs, end, MOVE_NORMAL, tossent, CL_GenericHitSuperContentsMask(tossent), true, true, NULL, true);
                VectorCopy (trace.endpos, tossent->fields.client->origin);
 
                if (trace.fraction < 1)
@@ -393,7 +396,7 @@ void VM_CL_precache_model (void)
                }
        }
        PRVM_G_FLOAT(OFS_RETURN) = 0;
-       m = Mod_ForName(name, false, false, false);
+       m = Mod_ForName(name, false, false, name[0] == '*' ? cl.model_name[1] : NULL);
        if(m && m->loaded)
        {
                for (i = 0;i < MAX_MODELS;i++)
@@ -434,9 +437,17 @@ static void VM_CL_findradius (void)
        vec_t                   radius, radius2;
        vec3_t                  org, eorg, mins, maxs;
        int                             i, numtouchedicts;
-       prvm_edict_t    *touchedicts[MAX_EDICTS];
+       static prvm_edict_t     *touchedicts[MAX_EDICTS];
+       int             chainfield;
+
+       VM_SAFEPARMCOUNTRANGE(2, 3, VM_CL_findradius);
 
-       VM_SAFEPARMCOUNT(2, VM_CL_findradius);
+       if(prog->argc == 3)
+               chainfield = PRVM_G_INT(OFS_PARM2);
+       else
+               chainfield = prog->fieldoffsets.chain;
+       if(chainfield < 0)
+               PRVM_ERROR("VM_findchain: %s doesnt have the specified chain field !", PRVM_NAME);
 
        chain = (prvm_edict_t *)prog->edicts;
 
@@ -478,7 +489,7 @@ static void VM_CL_findradius (void)
                        VectorMAMAM(1, eorg, -0.5f, ent->fields.client->mins, -0.5f, ent->fields.client->maxs, eorg);
                if (DotProduct(eorg, eorg) < radius2)
                {
-                       ent->fields.client->chain = PRVM_EDICT_TO_PROG(chain);
+                       PRVM_EDICTFIELDVALUE(ent, chainfield)->edict = PRVM_EDICT_TO_PROG(chain);
                        chain = ent;
                }
        }
@@ -514,7 +525,7 @@ static void VM_CL_droptofloor (void)
        VectorCopy (ent->fields.client->origin, end);
        end[2] -= 256;
 
-       trace = CL_Move(ent->fields.client->origin, ent->fields.client->mins, ent->fields.client->maxs, end, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, NULL, true);
+       trace = CL_TraceBox(ent->fields.client->origin, ent->fields.client->mins, ent->fields.client->maxs, end, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, NULL, true);
 
        if (trace.fraction != 1)
        {
@@ -543,7 +554,7 @@ static void VM_CL_lightstyle (void)
                VM_Warning("VM_CL_lightstyle >= MAX_LIGHTSTYLES\n");
                return;
        }
-       strlcpy (cl.lightstyle[i].map,  MSG_ReadString(), sizeof (cl.lightstyle[i].map));
+       strlcpy (cl.lightstyle[i].map, c, sizeof (cl.lightstyle[i].map));
        cl.lightstyle[i].map[MAX_STYLESTRING - 1] = 0;
        cl.lightstyle[i].length = (int)strlen(cl.lightstyle[i].map);
 }
@@ -593,7 +604,7 @@ realcheck:
        start[0] = stop[0] = (mins[0] + maxs[0])*0.5;
        start[1] = stop[1] = (mins[1] + maxs[1])*0.5;
        stop[2] = start[2] - 2*sv_stepheight.value;
-       trace = CL_Move (start, vec3_origin, vec3_origin, stop, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, NULL, true);
+       trace = CL_TraceLine(start, stop, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, NULL, true);
 
        if (trace.fraction == 1.0)
                return;
@@ -607,7 +618,7 @@ realcheck:
                        start[0] = stop[0] = x ? maxs[0] : mins[0];
                        start[1] = stop[1] = y ? maxs[1] : mins[1];
 
-                       trace = CL_Move (start, vec3_origin, vec3_origin, stop, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, NULL, true);
+                       trace = CL_TraceLine(start, stop, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, NULL, true);
 
                        if (trace.fraction != 1.0 && trace.endpos[2] > bottom)
                                bottom = trace.endpos[2];
@@ -672,7 +683,7 @@ static void VM_CL_getlight (void)
 
 //============================================================================
 //[515]: SCENE MANAGER builtins
-extern qboolean CSQC_AddRenderEdict (prvm_edict_t *ed);//csprogs.c
+extern qboolean CSQC_AddRenderEdict (prvm_edict_t *ed, int edictnum);//csprogs.c
 
 static void CSQC_R_RecalcView (void)
 {
@@ -718,6 +729,7 @@ extern void CSQC_Predraw (prvm_edict_t *ed);//csprogs.c
 extern void CSQC_Think (prvm_edict_t *ed);//csprogs.c
 void VM_CL_R_AddEntities (void)
 {
+       double t = Sys_DoubleTime();
        int                     i, drawmask;
        prvm_edict_t *ed;
        VM_SAFEPARMCOUNT(1, VM_CL_R_AddEntities);
@@ -740,15 +752,20 @@ void VM_CL_R_AddEntities (void)
                        continue;
                if(!((int)ed->fields.client->drawmask & drawmask))
                        continue;
-               CSQC_AddRenderEdict(ed);
+               CSQC_AddRenderEdict(ed, i);
        }
+
+       // callprofile fixing hack: do not include this time in what is counted for CSQC_UpdateView
+       prog->functions[prog->funcoffsets.CSQC_UpdateView].totaltime -= Sys_DoubleTime() - t;
 }
 
 //#302 void(entity ent) addentity (EXT_CSQC)
 void VM_CL_R_AddEntity (void)
 {
+       double t = Sys_DoubleTime();
        VM_SAFEPARMCOUNT(1, VM_CL_R_AddEntity);
-       CSQC_AddRenderEdict(PRVM_G_EDICT(OFS_PARM0));
+       CSQC_AddRenderEdict(PRVM_G_EDICT(OFS_PARM0), 0);
+       prog->functions[prog->funcoffsets.CSQC_UpdateView].totaltime -= Sys_DoubleTime() - t;
 }
 
 //#303 float(float property, ...) setproperty (EXT_CSQC)
@@ -836,13 +853,13 @@ void VM_CL_R_SetView (void)
                CSQC_R_RecalcView();
                break;
        case VF_DRAWWORLD:
-               cl.csqc_vidvars.drawworld = k;
+               cl.csqc_vidvars.drawworld = k != 0;
                break;
        case VF_DRAWENGINESBAR:
-               cl.csqc_vidvars.drawenginesbar = k;
+               cl.csqc_vidvars.drawenginesbar = k != 0;
                break;
        case VF_DRAWCROSSHAIR:
-               cl.csqc_vidvars.drawcrosshair = k;
+               cl.csqc_vidvars.drawcrosshair = k != 0;
                break;
        case VF_CL_VIEWANGLES:
                VectorCopy(f, cl.viewangles);
@@ -873,6 +890,7 @@ void VM_CL_R_SetView (void)
 //#305 void(vector org, float radius, vector lightcolours[, float style, string cubemapname, float pflags]) adddynamiclight (EXT_CSQC)
 void VM_CL_R_AddDynamicLight (void)
 {
+       double t = Sys_DoubleTime();
        vec_t *org;
        float radius = 300;
        vec_t *col;
@@ -919,6 +937,7 @@ void VM_CL_R_AddDynamicLight (void)
 
        R_RTLight_Update(&r_refdef.scene.templights[r_refdef.scene.numlights], false, &matrix, col, style, cubemapname, castshadow, coronaintensity, coronasizescale, ambientscale, diffusescale, specularscale, LIGHTFLAG_NORMALMODE | LIGHTFLAG_REALTIMEMODE);
        r_refdef.scene.lights[r_refdef.scene.numlights] = &r_refdef.scene.templights[r_refdef.scene.numlights++];
+       prog->functions[prog->funcoffsets.CSQC_UpdateView].totaltime -= Sys_DoubleTime() - t;
 }
 
 //============================================================================
@@ -932,8 +951,11 @@ static void VM_CL_unproject (void)
        VM_SAFEPARMCOUNT(1, VM_CL_unproject);
        f = PRVM_G_VECTOR(OFS_PARM0);
        if(v_flipped.integer)
-               f[0] = r_refdef.view.x + r_refdef.view.width - f[0];
-       VectorSet(temp, f[2], (-1.0 + 2.0 * (f[0] - r_refdef.view.x)) / r_refdef.view.width * f[2] * -r_refdef.view.frustum_x, (-1.0 + 2.0 * (f[1] - r_refdef.view.y))  / r_refdef.view.height * f[2] * -r_refdef.view.frustum_y);
+               f[0] = (2 * r_refdef.view.x + r_refdef.view.width) * (vid_conwidth.integer / (float) vid.width) - f[0];
+       VectorSet(temp,
+               f[2],
+               (-1.0 + 2.0 * (f[0] / (vid_conwidth.integer / (float) vid.width) - r_refdef.view.x) / r_refdef.view.width) * f[2] * -r_refdef.view.frustum_x,
+               (-1.0 + 2.0 * (f[1] / (vid_conheight.integer / (float) vid.height) - r_refdef.view.y) / r_refdef.view.height) * f[2] * -r_refdef.view.frustum_y);
        Matrix4x4_Transform(&r_refdef.view.matrix, temp, PRVM_G_VECTOR(OFS_RETURN));
 }
 
@@ -950,7 +972,10 @@ static void VM_CL_project (void)
        Matrix4x4_Transform(&m, f, v);
        if(v_flipped.integer)
                v[1] = -v[1];
-       VectorSet(PRVM_G_VECTOR(OFS_RETURN), r_refdef.view.x + r_refdef.view.width*0.5*(1.0+v[1]/v[0]/-r_refdef.view.frustum_x), r_refdef.view.y + r_refdef.view.height*0.5*(1.0+v[2]/v[0]/-r_refdef.view.frustum_y), v[0]);
+       VectorSet(PRVM_G_VECTOR(OFS_RETURN),
+               (vid_conwidth.integer / (float) vid.width) * (r_refdef.view.x + r_refdef.view.width*0.5*(1.0+v[1]/v[0]/-r_refdef.view.frustum_x)),
+               (vid_conheight.integer / (float) vid.height) * (r_refdef.view.y + r_refdef.view.height*0.5*(1.0+v[2]/v[0]/-r_refdef.view.frustum_y)),
+               v[0]);
 }
 
 //#330 float(float stnum) getstatf (EXT_CSQC)
@@ -1128,7 +1153,7 @@ static void VM_CL_getkeybind (void)
 static void VM_CL_setcursormode (void)
 {
        VM_SAFEPARMCOUNT(1, VM_CL_setcursormode);
-       cl.csqc_wantsmousemove = PRVM_G_FLOAT(OFS_PARM0);
+       cl.csqc_wantsmousemove = PRVM_G_FLOAT(OFS_PARM0) != 0;
        cl_ignoremousemoves = 2;
 }
 
@@ -1151,6 +1176,7 @@ static void VM_CL_getinputstate (void)
        int i, frame;
        VM_SAFEPARMCOUNT(1, VM_CL_getinputstate);
        frame = (int)PRVM_G_FLOAT(OFS_PARM0);
+       PRVM_G_FLOAT(OFS_RETURN) = false;
        for (i = 0;i < CL_MAX_USERCMDS;i++)
        {
                if (cl.movecmd[i].sequence == frame)
@@ -1171,6 +1197,7 @@ static void VM_CL_getinputstate (void)
                                VectorCopy(cl.playerstandmins, prog->globals.client->pmove_mins);
                                VectorCopy(cl.playerstandmaxs, prog->globals.client->pmove_maxs);
                        }
+                       PRVM_G_FLOAT(OFS_RETURN) = true;
                }
        }
 }
@@ -1219,6 +1246,9 @@ static void VM_CL_getplayerkey (void)
        else
                if(!strcasecmp(c, "pl"))
                        dpsnprintf(t, sizeof(t), "%i", cl.scores[i].qw_packetloss);
+       else
+               if(!strcasecmp(c, "movementloss"))
+                       dpsnprintf(t, sizeof(t), "%i", cl.scores[i].qw_movementloss);
        else
                if(!strcasecmp(c, "entertime"))
                        dpsnprintf(t, sizeof(t), "%f", cl.scores[i].qw_entertime);
@@ -1407,10 +1437,10 @@ static void VM_CL_makestatic (void)
                // copy it to the current state
                memset(staticent, 0, sizeof(*staticent));
                staticent->render.model = CL_GetModelByIndex((int)ent->fields.client->modelindex);
-               staticent->render.frame1 = staticent->render.frame2 = (int)ent->fields.client->frame;
-               staticent->render.framelerp = 0;
+               staticent->render.framegroupblend[0].frame = (int)ent->fields.client->frame;
+               staticent->render.framegroupblend[0].lerp = 1;
                // make torchs play out of sync
-               staticent->render.frame1time = staticent->render.frame2time = lhrandom(-10, -1);
+               staticent->render.framegroupblend[0].start = lhrandom(-10, -1);
                staticent->render.skinnum = (int)ent->fields.client->skin;
                staticent->render.effects = (int)ent->fields.client->effects;
                staticent->render.alpha = 1;
@@ -1418,6 +1448,11 @@ static void VM_CL_makestatic (void)
                staticent->render.scale = 1;
                if ((val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.scale)) && val->_float) staticent->render.scale = val->_float;
                if ((val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.colormod)) && VectorLength2(val->vector)) VectorCopy(val->vector, staticent->render.colormod);
+               if ((val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.glowmod)) && VectorLength2(val->vector)) VectorCopy(val->vector, staticent->render.glowmod);
+               if (!VectorLength2(staticent->render.colormod))
+                       VectorSet(staticent->render.colormod, 1, 1, 1);
+               if (!VectorLength2(staticent->render.glowmod))
+                       VectorSet(staticent->render.glowmod, 1, 1, 1);
 
                renderflags = 0;
                if ((val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.renderflags)) && val->_float) renderflags = (int)val->_float;
@@ -1432,12 +1467,24 @@ static void VM_CL_makestatic (void)
                        Matrix4x4_CreateFromQuakeEntity(&staticent->render.matrix, ent->fields.client->origin[0], ent->fields.client->origin[1], ent->fields.client->origin[2], ent->fields.client->angles[0], ent->fields.client->angles[1], ent->fields.client->angles[2], staticent->render.scale);
 
                // either fullbright or lit
-               if (!(staticent->render.effects & EF_FULLBRIGHT) && !r_fullbright.integer)
-                       staticent->render.flags |= RENDER_LIGHT;
+               if(!r_fullbright.integer)
+               {
+                       if (!(staticent->render.effects & EF_FULLBRIGHT))
+                               staticent->render.flags |= RENDER_LIGHT;
+                       else if(r_equalize_entities_fullbright.integer)
+                               staticent->render.flags |= RENDER_LIGHT | RENDER_EQUALIZE;
+               }
                // turn off shadows from transparent objects
                if (!(staticent->render.effects & (EF_NOSHADOW | EF_ADDITIVE | EF_NODEPTHTEST)) && (staticent->render.alpha >= 1))
                        staticent->render.flags |= RENDER_SHADOW;
-
+               if (staticent->render.effects & EF_NODEPTHTEST)
+                       staticent->render.flags |= RENDER_NODEPTHTEST;
+               if (staticent->render.effects & EF_ADDITIVE)
+                       staticent->render.flags |= RENDER_ADDITIVE;
+               if (staticent->render.effects & EF_DOUBLESIDED)
+                       staticent->render.flags |= RENDER_DOUBLESIDED;
+
+               staticent->render.allowdecals = true;
                CL_UpdateRenderEntity(&staticent->render);
        }
        else
@@ -2172,7 +2219,76 @@ int CL_GetTagIndex (prvm_edict_t *e, const char *tagname)
                return Mod_Alias_GetTagIndexForName(model, (int)e->fields.client->skin, tagname);
        else
                return -1;
-};
+}
+
+int CL_GetExtendedTagInfo (prvm_edict_t *e, int tagindex, int *parentindex, const char **tagname, matrix4x4_t *tag_localmatrix)
+{
+       int r;
+       dp_model_t *model;
+
+       *tagname = NULL;
+       *parentindex = 0;
+       Matrix4x4_CreateIdentity(tag_localmatrix);
+
+       if (tagindex >= 0
+        && (model = CL_GetModelFromEdict(e))
+        && model->animscenes)
+       {
+               r = Mod_Alias_GetExtendedTagInfoForIndex(model, (int)e->fields.client->skin, e->priv.server->frameblend, &e->priv.server->skeleton, tagindex - 1, parentindex, tagname, tag_localmatrix);
+
+               if(!r) // success?
+                       *parentindex += 1;
+
+               return r;
+       }
+
+       return 1;
+}
+
+int CL_GetPitchSign(prvm_edict_t *ent)
+{
+       dp_model_t *model;
+       if ((model = CL_GetModelFromEdict(ent)) && model->type == mod_alias)
+               return -1;
+       return 1;
+}
+
+void CL_GetEntityMatrix (prvm_edict_t *ent, matrix4x4_t *out, qboolean viewmatrix)
+{
+       prvm_eval_t *val;
+       float scale;
+       float pitchsign = 1;
+
+       scale = 1;
+       val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.scale);
+       if (val && val->_float != 0)
+               scale = val->_float;
+
+       // TODO do we need the same weird angle inverting logic here as in the server side case?
+       if(viewmatrix)
+               Matrix4x4_CreateFromQuakeEntity(out, cl.csqc_origin[0], cl.csqc_origin[1], cl.csqc_origin[2], cl.csqc_angles[0], cl.csqc_angles[1], cl.csqc_angles[2], scale * cl_viewmodel_scale.value);
+       else
+       {
+               pitchsign = CL_GetPitchSign(ent);
+               Matrix4x4_CreateFromQuakeEntity(out, ent->fields.client->origin[0], ent->fields.client->origin[1], ent->fields.client->origin[2], pitchsign * ent->fields.client->angles[0], ent->fields.client->angles[1], ent->fields.client->angles[2], scale);
+       }
+}
+
+int CL_GetEntityLocalTagMatrix(prvm_edict_t *ent, int tagindex, matrix4x4_t *out)
+{
+       dp_model_t *model;
+       if (tagindex >= 0
+        && (model = CL_GetModelFromEdict(ent))
+        && model->animscenes)
+       {
+               VM_GenerateFrameGroupBlend(ent->priv.server->framegroupblend, ent);
+               VM_FrameBlendFromFrameGroupBlend(ent->priv.server->frameblend, ent->priv.server->framegroupblend, model);
+               VM_UpdateEdictSkeleton(ent, model, ent->priv.server->frameblend);
+               return Mod_Alias_GetTagMatrix(model, ent->priv.server->frameblend, &ent->priv.server->skeleton, tagindex, out);
+       }
+       *out = identitymatrix;
+       return 0;
+}
 
 // Warnings/errors code:
 // 0 - normal (everything all-right)
@@ -2186,11 +2302,11 @@ extern cvar_t cl_bobcycle;
 extern cvar_t cl_bobup;
 int CL_GetTagMatrix (matrix4x4_t *out, prvm_edict_t *ent, int tagindex)
 {
+       int ret;
        prvm_eval_t *val;
-       int reqframe;
-       matrix4x4_t entitymatrix, tagmatrix;
+       int attachloop;
+       matrix4x4_t entitymatrix, tagmatrix, attachmatrix;
        dp_model_t *model;
-       float scale;
 
        *out = identitymatrix; // warnings and errors return identical matrix
 
@@ -2200,34 +2316,65 @@ int CL_GetTagMatrix (matrix4x4_t *out, prvm_edict_t *ent, int tagindex)
                return 2;
 
        model = CL_GetModelFromEdict(ent);
-
        if(!model)
                return 3;
 
-       if (ent->fields.client->frame >= 0 && ent->fields.client->frame < model->numframes && model->animscenes)
-               reqframe = model->animscenes[(int)ent->fields.client->frame].firstframe;
-       else
-               reqframe = 0; // if model has wrong frame, engine automatically switches to model first frame
-
-       // get initial tag matrix
-       if (tagindex)
+       tagmatrix = identitymatrix;
+       attachloop = 0;
+       for(;;)
        {
-               int ret = Mod_Alias_GetTagMatrix(model, reqframe, tagindex - 1, &tagmatrix);
-               if (ret)
+               if(attachloop >= 256)
+                       return 5;
+               // apply transformation by child's tagindex on parent entity and then
+               // by parent entity itself
+               ret = CL_GetEntityLocalTagMatrix(ent, tagindex - 1, &attachmatrix);
+               if(ret && attachloop == 0)
                        return ret;
+               CL_GetEntityMatrix(ent, &entitymatrix, false);
+               Matrix4x4_Concat(&tagmatrix, &attachmatrix, out);
+               Matrix4x4_Concat(out, &entitymatrix, &tagmatrix);
+               // next iteration we process the parent entity
+               if ((val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.tag_entity)) && val->edict)
+               {
+                       tagindex = (int)PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.tag_index)->_float;
+                       ent = PRVM_EDICT_NUM(val->edict);
+               }
+               else
+                       break;
+               attachloop++;
        }
-       else
-               tagmatrix = identitymatrix;
 
-       // normal or RENDER_VIEWMODEL entity (or main parent entity on attach chain)
-       scale = 1;
-       val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.scale);
-       if (val && val->_float != 0)
-               scale = val->_float;
-       // Alias models have inverse pitch, bmodels can't have tags, so don't check for modeltype...
-       // FIXME: support RF_USEAXIS
-       Matrix4x4_CreateFromQuakeEntity(&entitymatrix, ent->fields.client->origin[0], ent->fields.client->origin[1], ent->fields.client->origin[2], -ent->fields.client->angles[0], ent->fields.client->angles[1], ent->fields.client->angles[2], scale);
-       Matrix4x4_Concat(out, &entitymatrix, &tagmatrix);
+       // RENDER_VIEWMODEL magic
+       if ((val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.renderflags)) && (RF_VIEWMODEL & (int)val->_float))
+       {
+               Matrix4x4_Copy(&tagmatrix, out);
+
+               CL_GetEntityMatrix(prog->edicts, &entitymatrix, true);
+               Matrix4x4_Concat(out, &entitymatrix, &tagmatrix);
+
+               /*
+               // Cl_bob, ported from rendering code
+               if (ent->fields.client->health > 0 && cl_bob.value && cl_bobcycle.value)
+               {
+                       double bob, cycle;
+                       // LordHavoc: this code is *weird*, but not replacable (I think it
+                       // should be done in QC on the server, but oh well, quake is quake)
+                       // LordHavoc: figured out bobup: the time at which the sin is at 180
+                       // degrees (which allows lengthening or squishing the peak or valley)
+                       cycle = cl.time/cl_bobcycle.value;
+                       cycle -= (int)cycle;
+                       if (cycle < cl_bobup.value)
+                               cycle = sin(M_PI * cycle / cl_bobup.value);
+                       else
+                               cycle = sin(M_PI + M_PI * (cycle-cl_bobup.value)/(1.0 - cl_bobup.value));
+                       // bob is proportional to velocity in the xy plane
+                       // (don't count Z, or jumping messes it up)
+                       bob = sqrt(ent->fields.client->velocity[0]*ent->fields.client->velocity[0] + ent->fields.client->velocity[1]*ent->fields.client->velocity[1])*cl_bob.value;
+                       bob = bob*0.3 + bob*0.7*cycle;
+                       Matrix4x4_AdjustOrigin(out, 0, 0, bound(-7, bob, 4));
+               }
+               */
+       }
        return 0;
 }
 
@@ -2236,7 +2383,7 @@ void VM_CL_gettagindex (void)
 {
        prvm_edict_t *ent;
        const char *tag_name;
-       int modelindex, tag_index;
+       int tag_index;
 
        VM_SAFEPARMCOUNT(2, VM_CL_gettagindex);
 
@@ -2244,24 +2391,23 @@ void VM_CL_gettagindex (void)
        tag_name = PRVM_G_STRING(OFS_PARM1);
        if (ent == prog->edicts)
        {
-               VM_Warning("gettagindex: can't affect world entity\n");
+               VM_Warning("VM_CL_gettagindex(entity #%i): can't affect world entity\n", PRVM_NUM_FOR_EDICT(ent));
                return;
        }
        if (ent->priv.server->free)
        {
-               VM_Warning("gettagindex: can't affect free entity\n");
+               VM_Warning("VM_CL_gettagindex(entity #%i): can't affect free entity\n", PRVM_NUM_FOR_EDICT(ent));
                return;
        }
 
-       modelindex = (int)ent->fields.client->modelindex;
        tag_index = 0;
-       if (modelindex >= MAX_MODELS || (modelindex <= -MAX_MODELS /* client models */))
-               Con_DPrintf("gettagindex(entity #%i): null or non-precached model\n", PRVM_NUM_FOR_EDICT(ent));
+       if (!CL_GetModelFromEdict(ent))
+               Con_DPrintf("VM_CL_gettagindex(entity #%i): null or non-precached model\n", PRVM_NUM_FOR_EDICT(ent));
        else
        {
                tag_index = CL_GetTagIndex(ent, tag_name);
                if (tag_index == 0)
-                       Con_DPrintf("gettagindex(entity #%i): tag \"%s\" not found\n", PRVM_NUM_FOR_EDICT(ent), tag_name);
+                       Con_Printf("VM_CL_gettagindex(entity #%i): tag \"%s\" not found\n", PRVM_NUM_FOR_EDICT(ent), tag_name);
        }
        PRVM_G_FLOAT(OFS_RETURN) = tag_index;
 }
@@ -2272,14 +2418,40 @@ void VM_CL_gettaginfo (void)
        prvm_edict_t *e;
        int tagindex;
        matrix4x4_t tag_matrix;
+       matrix4x4_t tag_localmatrix;
+       int parentindex;
+       const char *tagname;
        int returncode;
+       prvm_eval_t *val;
+       vec3_t fo, le, up, trans;
+       const dp_model_t *model;
 
        VM_SAFEPARMCOUNT(2, VM_CL_gettaginfo);
 
        e = PRVM_G_EDICT(OFS_PARM0);
        tagindex = (int)PRVM_G_FLOAT(OFS_PARM1);
        returncode = CL_GetTagMatrix(&tag_matrix, e, tagindex);
-       Matrix4x4_ToVectors(&tag_matrix, prog->globals.client->v_forward, prog->globals.client->v_right, prog->globals.client->v_up, PRVM_G_VECTOR(OFS_RETURN));
+       Matrix4x4_ToVectors(&tag_matrix, prog->globals.client->v_forward, le, prog->globals.client->v_up, PRVM_G_VECTOR(OFS_RETURN));
+       VectorScale(le, -1, prog->globals.client->v_right);
+       model = CL_GetModelFromEdict(e);
+       VM_GenerateFrameGroupBlend(e->priv.server->framegroupblend, e);
+       VM_FrameBlendFromFrameGroupBlend(e->priv.server->frameblend, e->priv.server->framegroupblend, model);
+       VM_UpdateEdictSkeleton(e, model, e->priv.server->frameblend);
+       CL_GetExtendedTagInfo(e, tagindex, &parentindex, &tagname, &tag_localmatrix);
+       Matrix4x4_ToVectors(&tag_localmatrix, fo, le, up, trans);
+
+       if((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.gettaginfo_parent)))
+               val->_float = parentindex;
+       if((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.gettaginfo_name)))
+               val->string = tagname ? PRVM_SetTempString(tagname) : 0;
+       if((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.gettaginfo_offset)))
+               VectorCopy(trans, val->vector);
+       if((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.gettaginfo_forward)))
+               VectorCopy(fo, val->vector);
+       if((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.gettaginfo_right)))
+               VectorScale(le, -1, val->vector);
+       if((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.gettaginfo_up)))
+               VectorCopy(up, val->vector);
 
        switch(returncode)
        {
@@ -2303,6 +2475,448 @@ void VM_CL_gettaginfo (void)
 
 //============================================================================
 
+//====================
+// DP_CSQC_SPAWNPARTICLE
+// a QC hook to engine's CL_NewParticle
+//====================
+
+// particle theme struct
+typedef struct vmparticletheme_s
+{
+       unsigned short typeindex;
+       qboolean initialized;
+       pblend_t blendmode;
+       porientation_t orientation;
+       int color1;
+       int color2;
+       int tex;
+       float size;
+       float sizeincrease;
+       int alpha;
+       int alphafade;
+       float gravity;
+       float bounce;
+       float airfriction;
+       float liquidfriction;
+       float originjitter;
+       float velocityjitter;
+       qboolean qualityreduction;
+       float lifetime;
+       float stretch;
+       int staincolor1;
+       int staincolor2;
+       int staintex;
+       float delayspawn;
+       float delaycollision;
+}vmparticletheme_t;
+
+// particle spawner
+typedef struct vmparticlespawner_s
+{
+       mempool_t                       *pool;
+       qboolean                        initialized;
+       qboolean                        verified;
+       vmparticletheme_t       *themes;
+       int                                     max_themes;
+       // global addresses
+       float *particle_type;
+       float *particle_blendmode; 
+       float *particle_orientation;
+       float *particle_color1;
+       float *particle_color2;
+       float *particle_tex;
+       float *particle_size;
+       float *particle_sizeincrease;
+       float *particle_alpha;
+       float *particle_alphafade;
+       float *particle_time;
+       float *particle_gravity;
+       float *particle_bounce;
+       float *particle_airfriction;
+       float *particle_liquidfriction;
+       float *particle_originjitter;
+       float *particle_velocityjitter;
+       float *particle_qualityreduction;
+       float *particle_stretch;
+       float *particle_staincolor1;
+       float *particle_staincolor2;
+       float *particle_staintex;
+       float *particle_delayspawn;
+       float *particle_delaycollision;
+}vmparticlespawner_t;
+
+vmparticlespawner_t vmpartspawner;
+
+// TODO: automatic max_themes grow
+static void VM_InitParticleSpawner (int maxthemes)
+{
+       prvm_eval_t *val;
+
+       // bound max themes to not be an insane value
+       if (maxthemes < 4)
+               maxthemes = 4;
+       if (maxthemes > 2048)
+               maxthemes = 2048;
+       // allocate and set up structure
+       if (vmpartspawner.initialized) // reallocate
+       {
+               Mem_FreePool(&vmpartspawner.pool);
+               memset(&vmpartspawner, 0, sizeof(vmparticlespawner_t));
+       }
+       vmpartspawner.pool = Mem_AllocPool("VMPARTICLESPAWNER", 0, NULL);
+       vmpartspawner.themes = (vmparticletheme_t *)Mem_Alloc(vmpartspawner.pool, sizeof(vmparticletheme_t)*maxthemes);
+       vmpartspawner.max_themes = maxthemes;
+       vmpartspawner.initialized = true;
+       vmpartspawner.verified = true;
+       // get field addresses for fast querying (we can do 1000 calls of spawnparticle in a frame)
+       #define getglobal(v,s) val = PRVM_GLOBALFIELDVALUE(PRVM_ED_FindGlobalOffset(s)); if (val) { vmpartspawner.v = &val->_float; } else { VM_Warning("VM_InitParticleSpawner: missing global '%s', spawner cannot work\n", s); vmpartspawner.verified = false; }
+       #define getglobalvector(v,s) val = PRVM_GLOBALFIELDVALUE(PRVM_ED_FindGlobalOffset(s)); if (val) { vmpartspawner.v = (float *)val->vector; } else { VM_Warning("VM_InitParticleSpawner: missing global '%s', spawner cannot work\n", s); vmpartspawner.verified = false; }
+       getglobal(particle_type, "particle_type");
+       getglobal(particle_blendmode, "particle_blendmode");
+       getglobal(particle_orientation, "particle_orientation");
+       getglobalvector(particle_color1, "particle_color1");
+       getglobalvector(particle_color2, "particle_color2");
+       getglobal(particle_tex, "particle_tex");
+       getglobal(particle_size, "particle_size");
+       getglobal(particle_sizeincrease, "particle_sizeincrease");
+       getglobal(particle_alpha, "particle_alpha");
+       getglobal(particle_alphafade, "particle_alphafade");
+       getglobal(particle_time, "particle_time");
+       getglobal(particle_gravity, "particle_gravity");
+       getglobal(particle_bounce, "particle_bounce");
+       getglobal(particle_airfriction, "particle_airfriction");
+       getglobal(particle_liquidfriction, "particle_liquidfriction");
+       getglobal(particle_originjitter, "particle_originjitter");
+       getglobal(particle_velocityjitter, "particle_velocityjitter");
+       getglobal(particle_qualityreduction, "particle_qualityreduction");
+       getglobal(particle_stretch, "particle_stretch");
+       getglobalvector(particle_staincolor1, "particle_staincolor1");
+       getglobalvector(particle_staincolor2, "particle_staincolor2");
+       getglobal(particle_staintex, "particle_staintex");
+       getglobal(particle_delayspawn, "particle_delayspawn");
+       getglobal(particle_delaycollision, "particle_delaycollision");
+       #undef getglobal
+       #undef getglobalvector
+}
+
+// reset particle theme to default values
+static void VM_ResetParticleTheme (vmparticletheme_t *theme)
+{
+       theme->initialized = true;
+       theme->typeindex = pt_static;
+       theme->blendmode = PBLEND_ADD;
+       theme->orientation = PARTICLE_BILLBOARD;
+       theme->color1 = 0x808080;
+       theme->color2 = 0xFFFFFF;
+       theme->tex = 63;
+       theme->size = 2;
+       theme->sizeincrease = 0;
+       theme->alpha = 256;
+       theme->alphafade = 512;
+       theme->gravity = 0.0f;
+       theme->bounce = 0.0f;
+       theme->airfriction = 1.0f;
+       theme->liquidfriction = 4.0f;
+       theme->originjitter = 0.0f;
+       theme->velocityjitter = 0.0f;
+       theme->qualityreduction = false;
+       theme->lifetime = 4;
+       theme->stretch = 1;
+       theme->staincolor1 = -1;
+       theme->staincolor2 = -1;
+       theme->staintex = -1;
+       theme->delayspawn = 0.0f;
+       theme->delaycollision = 0.0f;
+}
+
+// particle theme -> QC globals
+void VM_CL_ParticleThemeToGlobals(vmparticletheme_t *theme)
+{
+       *vmpartspawner.particle_type = theme->typeindex;
+       *vmpartspawner.particle_blendmode = theme->blendmode;
+       *vmpartspawner.particle_orientation = theme->orientation;
+       vmpartspawner.particle_color1[0] = (theme->color1 >> 16) & 0xFF; // VorteX: int only can store 0-255, not 0-256 which means 0 - 0,99609375...
+       vmpartspawner.particle_color1[1] = (theme->color1 >> 8) & 0xFF;
+       vmpartspawner.particle_color1[2] = (theme->color1 >> 0) & 0xFF;
+       vmpartspawner.particle_color2[0] = (theme->color2 >> 16) & 0xFF;
+       vmpartspawner.particle_color2[1] = (theme->color2 >> 8) & 0xFF;
+       vmpartspawner.particle_color2[2] = (theme->color2 >> 0) & 0xFF;
+       *vmpartspawner.particle_tex = (float)theme->tex;
+       *vmpartspawner.particle_size = theme->size;
+       *vmpartspawner.particle_sizeincrease = theme->sizeincrease;
+       *vmpartspawner.particle_alpha = (float)theme->alpha/256;
+       *vmpartspawner.particle_alphafade = (float)theme->alphafade/256;
+       *vmpartspawner.particle_time = theme->lifetime;
+       *vmpartspawner.particle_gravity = theme->gravity;
+       *vmpartspawner.particle_bounce = theme->bounce;
+       *vmpartspawner.particle_airfriction = theme->airfriction;
+       *vmpartspawner.particle_liquidfriction = theme->liquidfriction;
+       *vmpartspawner.particle_originjitter = theme->originjitter;
+       *vmpartspawner.particle_velocityjitter = theme->velocityjitter;
+       *vmpartspawner.particle_qualityreduction = theme->qualityreduction;
+       *vmpartspawner.particle_stretch = theme->stretch;
+       vmpartspawner.particle_staincolor1[0] = (theme->staincolor1 >> 16) & 0xFF;
+       vmpartspawner.particle_staincolor1[1] = (theme->staincolor1 >> 8) & 0xFF;
+       vmpartspawner.particle_staincolor1[2] = (theme->staincolor1 >> 0) & 0xFF;
+       vmpartspawner.particle_staincolor2[0] = (theme->staincolor2 >> 16) & 0xFF;
+       vmpartspawner.particle_staincolor2[1] = (theme->staincolor2 >> 8) & 0xFF;
+       vmpartspawner.particle_staincolor2[2] = (theme->staincolor2 >> 0) & 0xFF;
+       *vmpartspawner.particle_staintex = (float)theme->staintex;
+       *vmpartspawner.particle_delayspawn = theme->delayspawn;
+       *vmpartspawner.particle_delaycollision = theme->delaycollision;
+}
+
+// QC globals ->  particle theme
+void VM_CL_ParticleThemeFromGlobals(vmparticletheme_t *theme)
+{
+       theme->typeindex = (unsigned short)*vmpartspawner.particle_type;
+       theme->blendmode = (pblend_t)*vmpartspawner.particle_blendmode;
+       theme->orientation = (porientation_t)*vmpartspawner.particle_orientation;
+       theme->color1 = ((int)vmpartspawner.particle_color1[0] << 16) + ((int)vmpartspawner.particle_color1[1] << 8) + ((int)vmpartspawner.particle_color1[2]);
+       theme->color2 = ((int)vmpartspawner.particle_color2[0] << 16) + ((int)vmpartspawner.particle_color2[1] << 8) + ((int)vmpartspawner.particle_color2[2]);
+       theme->tex = (int)*vmpartspawner.particle_tex;
+       theme->size = *vmpartspawner.particle_size;
+       theme->sizeincrease = *vmpartspawner.particle_sizeincrease;
+       theme->alpha = (int)(*vmpartspawner.particle_alpha*256);
+       theme->alphafade = (int)(*vmpartspawner.particle_alphafade*256);
+       theme->lifetime = *vmpartspawner.particle_time;
+       theme->gravity = *vmpartspawner.particle_gravity;
+       theme->bounce = *vmpartspawner.particle_bounce;
+       theme->airfriction = *vmpartspawner.particle_airfriction;
+       theme->liquidfriction = *vmpartspawner.particle_liquidfriction;
+       theme->originjitter = *vmpartspawner.particle_originjitter;
+       theme->velocityjitter = *vmpartspawner.particle_velocityjitter;
+       theme->qualityreduction = (*vmpartspawner.particle_qualityreduction) ? true : false;
+       theme->stretch = *vmpartspawner.particle_stretch;
+       theme->staincolor1 = vmpartspawner.particle_staincolor1[0]*65536 + vmpartspawner.particle_staincolor1[1]*256 + vmpartspawner.particle_staincolor1[2];
+       theme->staincolor2 = vmpartspawner.particle_staincolor2[0]*65536 + vmpartspawner.particle_staincolor2[1]*256 + vmpartspawner.particle_staincolor2[2];
+       theme->staintex =(int)*vmpartspawner.particle_staintex;
+       theme->delayspawn = *vmpartspawner.particle_delayspawn;
+       theme->delaycollision = *vmpartspawner.particle_delaycollision;
+}
+
+// init particle spawner interface
+// # float(float max_themes) initparticlespawner
+void VM_CL_InitParticleSpawner (void)
+{
+       VM_SAFEPARMCOUNTRANGE(0, 1, VM_CL_InitParticleSpawner);
+       VM_InitParticleSpawner((int)PRVM_G_FLOAT(OFS_PARM0));
+       vmpartspawner.themes[0].initialized = true;
+       VM_ResetParticleTheme(&vmpartspawner.themes[0]);
+       PRVM_G_FLOAT(OFS_RETURN) = (vmpartspawner.verified == true) ? 1 : 0;
+}
+
+// void() resetparticle
+void VM_CL_ResetParticle (void)
+{
+       VM_SAFEPARMCOUNT(0, VM_CL_ResetParticle);
+       if (vmpartspawner.verified == false)
+       {
+               VM_Warning("VM_CL_ResetParticle: particle spawner not initialized\n");
+               return;
+       }
+       VM_CL_ParticleThemeToGlobals(&vmpartspawner.themes[0]);
+}
+
+// void(float themenum) particletheme
+void VM_CL_ParticleTheme (void)
+{
+       int themenum;
+
+       VM_SAFEPARMCOUNT(1, VM_CL_ParticleTheme);
+       if (vmpartspawner.verified == false)
+       {
+               VM_Warning("VM_CL_ParticleTheme: particle spawner not initialized\n");
+               return;
+       }
+       themenum = (int)PRVM_G_FLOAT(OFS_PARM0);
+       if (themenum < 0 || themenum >= vmpartspawner.max_themes)
+       {
+               VM_Warning("VM_CL_ParticleTheme: bad theme number %i\n", themenum);
+               VM_CL_ParticleThemeToGlobals(&vmpartspawner.themes[0]);
+               return;
+       }
+       if (vmpartspawner.themes[themenum].initialized == false)
+       {
+               VM_Warning("VM_CL_ParticleTheme: theme #%i not exists\n", themenum);
+               VM_CL_ParticleThemeToGlobals(&vmpartspawner.themes[0]);
+               return;
+       }
+       // load particle theme into globals
+       VM_CL_ParticleThemeToGlobals(&vmpartspawner.themes[themenum]);
+}
+
+// float() saveparticletheme
+// void(float themenum) updateparticletheme
+void VM_CL_ParticleThemeSave (void)
+{
+       int themenum;
+
+       VM_SAFEPARMCOUNTRANGE(0, 1, VM_CL_ParticleThemeSave);
+       if (vmpartspawner.verified == false)
+       {
+               VM_Warning("VM_CL_ParticleThemeSave: particle spawner not initialized\n");
+               return;
+       }
+       // allocate new theme, save it and return
+       if (prog->argc < 1)
+       {
+               for (themenum = 0; themenum < vmpartspawner.max_themes; themenum++)
+                       if (vmpartspawner.themes[themenum].initialized == false)
+                               break;
+               if (themenum >= vmpartspawner.max_themes)
+               {
+                       if (vmpartspawner.max_themes == 2048)
+                               VM_Warning("VM_CL_ParticleThemeSave: no free theme slots\n");
+                       else
+                               VM_Warning("VM_CL_ParticleThemeSave: no free theme slots, try initparticlespawner() with highter max_themes\n");
+                       PRVM_G_FLOAT(OFS_RETURN) = -1;
+                       return;
+               }
+               vmpartspawner.themes[themenum].initialized = true;
+               VM_CL_ParticleThemeFromGlobals(&vmpartspawner.themes[themenum]);
+               PRVM_G_FLOAT(OFS_RETURN) = themenum;
+               return;
+       }
+       // update existing theme
+       themenum = (int)PRVM_G_FLOAT(OFS_PARM0);
+       if (themenum < 0 || themenum >= vmpartspawner.max_themes)
+       {
+               VM_Warning("VM_CL_ParticleThemeSave: bad theme number %i\n", themenum);
+               return;
+       }
+       vmpartspawner.themes[themenum].initialized = true;
+       VM_CL_ParticleThemeFromGlobals(&vmpartspawner.themes[themenum]);
+}
+
+// void(float themenum) freeparticletheme
+void VM_CL_ParticleThemeFree (void)
+{
+       int themenum;
+
+       VM_SAFEPARMCOUNT(1, VM_CL_ParticleThemeFree);
+       if (vmpartspawner.verified == false)
+       {
+               VM_Warning("VM_CL_ParticleThemeFree: particle spawner not initialized\n");
+               return;
+       }
+       themenum = (int)PRVM_G_FLOAT(OFS_PARM0);
+       // check parms
+       if (themenum <= 0 || themenum >= vmpartspawner.max_themes)
+       {
+               VM_Warning("VM_CL_ParticleThemeFree: bad theme number %i\n", themenum);
+               return;
+       }
+       if (vmpartspawner.themes[themenum].initialized == false)
+       {
+               VM_Warning("VM_CL_ParticleThemeFree: theme #%i already freed\n", themenum);
+               VM_CL_ParticleThemeToGlobals(&vmpartspawner.themes[0]);
+               return;
+       }
+       // free theme
+       VM_ResetParticleTheme(&vmpartspawner.themes[themenum]);
+       vmpartspawner.themes[themenum].initialized = false;
+}
+
+// float(vector org, vector dir, [float theme]) particle
+// returns 0 if failed, 1 if succesful
+void VM_CL_SpawnParticle (void)
+{
+       float *org, *dir;
+       vmparticletheme_t *theme;
+       particle_t *part;
+       int themenum;
+
+       VM_SAFEPARMCOUNTRANGE(2, 3, VM_CL_SpawnParticle2);
+       if (vmpartspawner.verified == false)
+       {
+               VM_Warning("VM_CL_SpawnParticle: particle spawner not initialized\n");
+               PRVM_G_FLOAT(OFS_RETURN) = 0; 
+               return;
+       }
+       org = PRVM_G_VECTOR(OFS_PARM0);
+       dir = PRVM_G_VECTOR(OFS_PARM1);
+       
+       if (prog->argc < 3) // global-set particle
+       {
+               part = CL_NewParticle((unsigned short)*vmpartspawner.particle_type, ((int)vmpartspawner.particle_color1[0] << 16) + ((int)vmpartspawner.particle_color1[1] << 8) + ((int)vmpartspawner.particle_color1[2]), ((int)vmpartspawner.particle_color2[0] << 16) + ((int)vmpartspawner.particle_color2[1] << 8) + ((int)vmpartspawner.particle_color2[2]), (int)*vmpartspawner.particle_tex, *vmpartspawner.particle_size, *vmpartspawner.particle_sizeincrease, (int)(*vmpartspawner.particle_alpha*256), (int)(*vmpartspawner.particle_alphafade*256), *vmpartspawner.particle_gravity, *vmpartspawner.particle_bounce, org[0], org[1], org[2], dir[0], dir[1], dir[2], *vmpartspawner.particle_airfriction, *vmpartspawner.particle_liquidfriction, *vmpartspawner.particle_originjitter, *vmpartspawner.particle_velocityjitter, (*vmpartspawner.particle_qualityreduction) ? true : false, *vmpartspawner.particle_time, *vmpartspawner.particle_stretch, (pblend_t)*vmpartspawner.particle_blendmode, (porientation_t)*vmpartspawner.particle_orientation, ((int)vmpartspawner.particle_staincolor1[0] << 16) + ((int)vmpartspawner.particle_staincolor1[1] << 8) + ((int)vmpartspawner.particle_staincolor1[2]), ((int)vmpartspawner.particle_staincolor2[0] << 16) + ((int)vmpartspawner.particle_staincolor2[1] << 8) + ((int)vmpartspawner.particle_staincolor2[2]), (int)*vmpartspawner.particle_staintex);
+               if (!part)
+               {
+                       PRVM_G_FLOAT(OFS_RETURN) = 0; 
+                       return;
+               }
+               if (*vmpartspawner.particle_delayspawn)
+                       part->delayedspawn = cl.time + *vmpartspawner.particle_delayspawn;
+               if (*vmpartspawner.particle_delaycollision)
+                       part->delayedcollisions = cl.time + *vmpartspawner.particle_delaycollision;
+       }
+       else // quick themed particle
+       {
+               themenum = (int)PRVM_G_FLOAT(OFS_PARM2);
+               if (themenum <= 0 || themenum >= vmpartspawner.max_themes)
+               {
+                       VM_Warning("VM_CL_SpawnParticle: bad theme number %i\n", themenum);
+                       PRVM_G_FLOAT(OFS_RETURN) = 0; 
+                       return;
+               }
+               theme = &vmpartspawner.themes[themenum];
+               part = CL_NewParticle(theme->typeindex, theme->color1, theme->color2, theme->tex, theme->size, theme->sizeincrease, theme->alpha, theme->alphafade, theme->gravity, theme->bounce, org[0], org[1], org[2], dir[0], dir[1], dir[2], theme->airfriction, theme->liquidfriction, theme->originjitter, theme->velocityjitter, theme->qualityreduction, theme->lifetime, theme->stretch, theme->blendmode, theme->orientation, theme->staincolor1, theme->staincolor2, theme->staintex);
+               if (!part)
+               {
+                       PRVM_G_FLOAT(OFS_RETURN) = 0; 
+                       return;
+               }
+               if (theme->delayspawn)
+                       part->delayedspawn = cl.time + theme->delayspawn;
+               if (theme->delaycollision)
+                       part->delayedcollisions = cl.time + theme->delaycollision;
+       }
+       PRVM_G_FLOAT(OFS_RETURN) = 1; 
+}
+
+// float(vector org, vector dir, float spawndelay, float collisiondelay, [float theme]) delayedparticle
+// returns 0 if failed, 1 if success
+void VM_CL_SpawnParticleDelayed (void)
+{
+       float *org, *dir;
+       vmparticletheme_t *theme;
+       particle_t *part;
+       int themenum;
+
+       VM_SAFEPARMCOUNTRANGE(4, 5, VM_CL_SpawnParticle2);
+       if (vmpartspawner.verified == false)
+       {
+               VM_Warning("VM_CL_SpawnParticle: particle spawner not initialized\n");
+               PRVM_G_FLOAT(OFS_RETURN) = 0; 
+               return;
+       }
+       org = PRVM_G_VECTOR(OFS_PARM0);
+       dir = PRVM_G_VECTOR(OFS_PARM1);
+       if (prog->argc < 5) // global-set particle
+               part = CL_NewParticle((unsigned short)*vmpartspawner.particle_type, ((int)vmpartspawner.particle_color1[0] << 16) + ((int)vmpartspawner.particle_color1[1] << 8) + ((int)vmpartspawner.particle_color1[2]), ((int)vmpartspawner.particle_color2[0] << 16) + ((int)vmpartspawner.particle_color2[1] << 8) + ((int)vmpartspawner.particle_color2[2]), (int)*vmpartspawner.particle_tex, *vmpartspawner.particle_size, *vmpartspawner.particle_sizeincrease, (int)(*vmpartspawner.particle_alpha*256), (int)(*vmpartspawner.particle_alphafade*256), *vmpartspawner.particle_gravity, *vmpartspawner.particle_bounce, org[0], org[1], org[2], dir[0], dir[1], dir[2], *vmpartspawner.particle_airfriction, *vmpartspawner.particle_liquidfriction, *vmpartspawner.particle_originjitter, *vmpartspawner.particle_velocityjitter, (*vmpartspawner.particle_qualityreduction) ? true : false, *vmpartspawner.particle_time, *vmpartspawner.particle_stretch, (pblend_t)*vmpartspawner.particle_blendmode, (porientation_t)*vmpartspawner.particle_orientation, ((int)vmpartspawner.particle_staincolor1[0] << 16) + ((int)vmpartspawner.particle_staincolor1[1] << 8) + ((int)vmpartspawner.particle_staincolor1[2]), ((int)vmpartspawner.particle_staincolor2[0] << 16) + ((int)vmpartspawner.particle_staincolor2[1] << 8) + ((int)vmpartspawner.particle_staincolor2[2]), (int)*vmpartspawner.particle_staintex);
+       else // themed particle
+       {
+               themenum = (int)PRVM_G_FLOAT(OFS_PARM4);
+               if (themenum <= 0 || themenum >= vmpartspawner.max_themes)
+               {
+                       VM_Warning("VM_CL_SpawnParticle: bad theme number %i\n", themenum);
+                       PRVM_G_FLOAT(OFS_RETURN) = 0;  
+                       return;
+               }
+               theme = &vmpartspawner.themes[themenum];
+               part = CL_NewParticle(theme->typeindex, theme->color1, theme->color2, theme->tex, theme->size, theme->sizeincrease, theme->alpha, theme->alphafade, theme->gravity, theme->bounce, org[0], org[1], org[2], dir[0], dir[1], dir[2], theme->airfriction, theme->liquidfriction, theme->originjitter, theme->velocityjitter, theme->qualityreduction, theme->lifetime, theme->stretch, theme->blendmode, theme->orientation, theme->staincolor1, theme->staincolor2, theme->staintex);
+       }
+       if (!part) 
+       { 
+               PRVM_G_FLOAT(OFS_RETURN) = 0; 
+               return; 
+       }
+       part->delayedspawn = cl.time + PRVM_G_FLOAT(OFS_PARM2);
+       part->delayedcollisions = cl.time + PRVM_G_FLOAT(OFS_PARM3);
+       PRVM_G_FLOAT(OFS_RETURN) = 0;
+}
+
+//
 //====================
 //QC POLYGON functions
 //====================
@@ -2351,8 +2965,10 @@ vmpolygons_t vmpolygons[PRVM_MAXPROGS];
 // --blub
 void VM_CL_R_RenderScene (void)
 {
+       double t = Sys_DoubleTime();
        vmpolygons_t* polys = vmpolygons + PRVM_GetProgNr();
        VM_SAFEPARMCOUNT(0, VM_CL_R_RenderScene);
+
        // we need to update any RENDER_VIEWMODEL entities at this point because
        // csqc supplies its own view matrix
        CL_UpdateViewEntities();
@@ -2361,6 +2977,9 @@ void VM_CL_R_RenderScene (void)
 
        polys->num_vertices = polys->num_triangles = 0;
        polys->progstarttime = prog->starttime;
+
+       // callprofile fixing hack: do not include this time in what is counted for CSQC_UpdateView
+       prog->functions[prog->funcoffsets.CSQC_UpdateView].totaltime -= Sys_DoubleTime() - t;
 }
 
 static void VM_ResizePolygons(vmpolygons_t *polys)
@@ -2415,12 +3034,11 @@ static void VM_DrawPolygonCallback (const entity_render_t *ent, const rtlight_t
        if(polys->progstarttime != prog->starttime) // from other progs? won't draw these (this can cause crashes!)
                return;
        R_Mesh_ResetTextureState();
-       R_Mesh_Matrix(&identitymatrix);
+       R_EntityMatrix(&identitymatrix);
        GL_CullFace(GL_NONE);
        R_Mesh_VertexPointer(polys->data_vertex3f, 0, 0);
        R_Mesh_ColorPointer(polys->data_color4f, 0, 0);
        R_Mesh_TexCoordPointer(0, 2, polys->data_texcoord2f, 0, 0);
-       R_SetupGenericShader(true);
 
        for (surfacelistindex = 0;surfacelistindex < numsurfaces;)
        {
@@ -2439,7 +3057,7 @@ static void VM_DrawPolygonCallback (const entity_render_t *ent, const rtlight_t
                        GL_BlendFunc(GL_ONE_MINUS_DST_COLOR,GL_ONE);
                else
                        GL_BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-               R_Mesh_TexBind(0, R_GetTexture(tex));
+               R_SetupShader_Generic(tex, NULL, GL_MODULATE, 1);
                numtriangles = 0;
                for (;surfacelistindex < numsurfaces;surfacelistindex++)
                {
@@ -2461,7 +3079,8 @@ void VMPolygons_Store(vmpolygons_t *polys)
                mesh.texture = polys->begin_texture;
                mesh.num_vertices = polys->begin_vertices;
                mesh.num_triangles = polys->begin_vertices-2;
-               mesh.data_element3s = polygonelements;
+               mesh.data_element3i = polygonelement3i;
+               mesh.data_element3s = polygonelement3s;
                mesh.data_vertex3f = polys->begin_vertex[0];
                mesh.data_color4f = polys->begin_color[0];
                mesh.data_texcoord2f = polys->begin_texcoord[0];
@@ -2730,7 +3349,7 @@ realcheck:
        start[0] = stop[0] = (mins[0] + maxs[0])*0.5;
        start[1] = stop[1] = (mins[1] + maxs[1])*0.5;
        stop[2] = start[2] - 2*sv_stepheight.value;
-       trace = CL_Move (start, vec3_origin, vec3_origin, stop, MOVE_NOMONSTERS, ent, CL_GenericHitSuperContentsMask(ent), true, false, NULL, true);
+       trace = CL_TraceLine(start, stop, MOVE_NOMONSTERS, ent, CL_GenericHitSuperContentsMask(ent), true, false, NULL, true);
 
        if (trace.fraction == 1.0)
                return false;
@@ -2743,7 +3362,7 @@ realcheck:
                        start[0] = stop[0] = x ? maxs[0] : mins[0];
                        start[1] = stop[1] = y ? maxs[1] : mins[1];
 
-                       trace = CL_Move (start, vec3_origin, vec3_origin, stop, MOVE_NOMONSTERS, ent, CL_GenericHitSuperContentsMask(ent), true, false, NULL, true);
+                       trace = CL_TraceLine(start, stop, MOVE_NOMONSTERS, ent, CL_GenericHitSuperContentsMask(ent), true, false, NULL, true);
 
                        if (trace.fraction != 1.0 && trace.endpos[2] > bottom)
                                bottom = trace.endpos[2];
@@ -2792,7 +3411,7 @@ qboolean CL_movestep (prvm_edict_t *ent, vec3_t move, qboolean relink, qboolean
                                if (dz < 30)
                                        neworg[2] += 8;
                        }
-                       trace = CL_Move (ent->fields.client->origin, ent->fields.client->mins, ent->fields.client->maxs, neworg, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, &svent, true);
+                       trace = CL_TraceBox(ent->fields.client->origin, ent->fields.client->mins, ent->fields.client->maxs, neworg, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, &svent, true);
                        if (settrace)
                                CL_VM_SetTraceGlobals(&trace, svent);
 
@@ -2820,14 +3439,14 @@ qboolean CL_movestep (prvm_edict_t *ent, vec3_t move, qboolean relink, qboolean
        VectorCopy (neworg, end);
        end[2] -= sv_stepheight.value*2;
 
-       trace = CL_Move (neworg, ent->fields.client->mins, ent->fields.client->maxs, end, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, &svent, true);
+       trace = CL_TraceBox(neworg, ent->fields.client->mins, ent->fields.client->maxs, end, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, &svent, true);
        if (settrace)
                CL_VM_SetTraceGlobals(&trace, svent);
 
        if (trace.startsolid)
        {
                neworg[2] -= sv_stepheight.value;
-               trace = CL_Move (neworg, ent->fields.client->mins, ent->fields.client->maxs, end, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, &svent, true);
+               trace = CL_TraceBox(neworg, ent->fields.client->mins, ent->fields.client->maxs, end, MOVE_NORMAL, ent, CL_GenericHitSuperContentsMask(ent), true, true, &svent, true);
                if (settrace)
                        CL_VM_SetTraceGlobals(&trace, svent);
                if (trace.startsolid)
@@ -2948,6 +3567,384 @@ void VM_CL_serverkey(void)
        PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(string);
 }
 
+/*
+=================
+VM_CL_checkpvs
+
+Checks if an entity is in a point's PVS.
+Should be fast but can be inexact.
+
+float checkpvs(vector viewpos, entity viewee) = #240;
+=================
+*/
+static void VM_CL_checkpvs (void)
+{
+       vec3_t viewpos;
+       prvm_edict_t *viewee;
+       vec3_t mi, ma;
+#if 1
+       unsigned char *pvs;
+#else
+       int fatpvsbytes;
+       unsigned char fatpvs[MAX_MAP_LEAFS/8];
+#endif
+
+       VM_SAFEPARMCOUNT(2, VM_SV_checkpvs);
+       VectorCopy(PRVM_G_VECTOR(OFS_PARM0), viewpos);
+       viewee = PRVM_G_EDICT(OFS_PARM1);
+
+       if(viewee->priv.required->free)
+       {
+               VM_Warning("checkpvs: can not check free entity\n");
+               PRVM_G_FLOAT(OFS_RETURN) = 4;
+               return;
+       }
+
+       VectorAdd(viewee->fields.server->origin, viewee->fields.server->mins, mi);
+       VectorAdd(viewee->fields.server->origin, viewee->fields.server->maxs, ma);
+
+#if 1
+       if(!sv.worldmodel->brush.GetPVS || !sv.worldmodel->brush.BoxTouchingPVS)
+       {
+               // no PVS support on this worldmodel... darn
+               PRVM_G_FLOAT(OFS_RETURN) = 3;
+               return;
+       }
+       pvs = sv.worldmodel->brush.GetPVS(sv.worldmodel, viewpos);
+       if(!pvs)
+       {
+               // viewpos isn't in any PVS... darn
+               PRVM_G_FLOAT(OFS_RETURN) = 2;
+               return;
+       }
+       PRVM_G_FLOAT(OFS_RETURN) = sv.worldmodel->brush.BoxTouchingPVS(sv.worldmodel, pvs, mi, ma);
+#else
+       // using fat PVS like FTEQW does (slow)
+       if(!sv.worldmodel->brush.FatPVS || !sv.worldmodel->brush.BoxTouchingPVS)
+       {
+               // no PVS support on this worldmodel... darn
+               PRVM_G_FLOAT(OFS_RETURN) = 3;
+               return;
+       }
+       fatpvsbytes = sv.worldmodel->brush.FatPVS(sv.worldmodel, viewpos, 8, fatpvs, sizeof(fatpvs), false);
+       if(!fatpvsbytes)
+       {
+               // viewpos isn't in any PVS... darn
+               PRVM_G_FLOAT(OFS_RETURN) = 2;
+               return;
+       }
+       PRVM_G_FLOAT(OFS_RETURN) = sv.worldmodel->brush.BoxTouchingPVS(sv.worldmodel, fatpvs, mi, ma);
+#endif
+}
+
+// #263 float(float modlindex) skel_create = #263; // (FTE_CSQC_SKELETONOBJECTS) create a skeleton (be sure to assign this value into .skeletonindex for use), returns skeleton index (1 or higher) on success, returns 0 on failure  (for example if the modelindex is not skeletal), it is recommended that you create a new skeleton if you change modelindex.
+static void VM_CL_skel_create(void)
+{
+       int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0);
+       dp_model_t *model = CL_GetModelByIndex(modelindex);
+       skeleton_t *skeleton;
+       int i;
+       PRVM_G_FLOAT(OFS_RETURN) = 0;
+       if (!model || !model->num_bones)
+               return;
+       for (i = 0;i < MAX_EDICTS;i++)
+               if (!prog->skeletons[i])
+                       break;
+       if (i == MAX_EDICTS)
+               return;
+       prog->skeletons[i] = skeleton = Mem_Alloc(cls.levelmempool, sizeof(skeleton_t) + model->num_bones * sizeof(matrix4x4_t));
+       skeleton->model = model;
+       skeleton->relativetransforms = (matrix4x4_t *)(skeleton+1);
+       // initialize to identity matrices
+       for (i = 0;i < skeleton->model->num_bones;i++)
+               skeleton->relativetransforms[i] = identitymatrix;
+       PRVM_G_FLOAT(OFS_RETURN) = i + 1;
+}
+
+// #264 float(float skel, entity ent, float modlindex, float retainfrac, float firstbone, float lastbone) skel_build = #264; // (FTE_CSQC_SKELETONOBJECTS) blend in a percentage of standard animation, 0 replaces entirely, 1 does nothing, 0.5 blends half, etc, and this only alters the bones in the specified range for which out of bounds values like 0,100000 are safe (uses .frame, .frame2, .frame3, .frame4, .lerpfrac, .lerpfrac3, .lerpfrac4, .frame1time, .frame2time, .frame3time, .frame4time), returns skel on success, 0 on failure
+static void VM_CL_skel_build(void)
+{
+       int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1;
+       skeleton_t *skeleton;
+       prvm_edict_t *ed = PRVM_G_EDICT(OFS_PARM1);
+       int modelindex = (int)PRVM_G_FLOAT(OFS_PARM2);
+       float retainfrac = PRVM_G_FLOAT(OFS_PARM3);
+       int firstbone = PRVM_G_FLOAT(OFS_PARM4);
+       int lastbone = PRVM_G_FLOAT(OFS_PARM5);
+       dp_model_t *model = CL_GetModelByIndex(modelindex);
+       float blendfrac;
+       int numblends;
+       int bonenum;
+       int blendindex;
+       framegroupblend_t framegroupblend[MAX_FRAMEGROUPBLENDS];
+       frameblend_t frameblend[MAX_FRAMEBLENDS];
+       matrix4x4_t blendedmatrix;
+       matrix4x4_t matrix;
+       PRVM_G_FLOAT(OFS_RETURN) = 0;
+       if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex]))
+               return;
+       firstbone = max(0, firstbone);
+       lastbone = min(lastbone, model->num_bones - 1);
+       lastbone = min(lastbone, skeleton->model->num_bones - 1);
+       VM_GenerateFrameGroupBlend(framegroupblend, ed);
+       VM_FrameBlendFromFrameGroupBlend(frameblend, framegroupblend, model);
+       blendfrac = 1.0f - retainfrac;
+       for (numblends = 0;numblends < MAX_FRAMEBLENDS && frameblend[numblends].lerp;numblends++)
+               frameblend[numblends].lerp *= blendfrac;
+       for (bonenum = firstbone;bonenum <= lastbone;bonenum++)
+       {
+               memset(&blendedmatrix, 0, sizeof(blendedmatrix));
+               Matrix4x4_Accumulate(&blendedmatrix, &skeleton->relativetransforms[bonenum], retainfrac);
+               for (blendindex = 0;blendindex < numblends;blendindex++)
+               {
+                       Matrix4x4_FromArray12FloatD3D(&matrix, model->data_poses + 12 * (frameblend[blendindex].subframe * model->num_bones + bonenum));
+                       Matrix4x4_Accumulate(&blendedmatrix, &matrix, frameblend[blendindex].lerp);
+               }
+               skeleton->relativetransforms[bonenum] = blendedmatrix;
+       }
+       PRVM_G_FLOAT(OFS_RETURN) = skeletonindex;
+}
+
+// #265 float(float skel) skel_get_numbones = #265; // (FTE_CSQC_SKELETONOBJECTS) returns how many bones exist in the created skeleton
+static void VM_CL_skel_get_numbones(void)
+{
+       int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1;
+       skeleton_t *skeleton;
+       PRVM_G_FLOAT(OFS_RETURN) = 0;
+       if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex]))
+               return;
+       PRVM_G_FLOAT(OFS_RETURN) = skeleton->model->num_bones;
+}
+
+// #266 string(float skel, float bonenum) skel_get_bonename = #266; // (FTE_CSQC_SKELETONOBJECTS) returns name of bone (as a tempstring)
+static void VM_CL_skel_get_bonename(void)
+{
+       int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1;
+       int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1;
+       skeleton_t *skeleton;
+       PRVM_G_INT(OFS_RETURN) = 0;
+       if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex]))
+               return;
+       if (bonenum < 0 || bonenum >= skeleton->model->num_bones)
+               return;
+       PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(skeleton->model->data_bones[bonenum].name);
+}
+
+// #267 float(float skel, float bonenum) skel_get_boneparent = #267; // (FTE_CSQC_SKELETONOBJECTS) returns parent num for supplied bonenum, 0 if bonenum has no parent or bone does not exist (returned value is always less than bonenum, you can loop on this)
+static void VM_CL_skel_get_boneparent(void)
+{
+       int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1;
+       int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1;
+       skeleton_t *skeleton;
+       PRVM_G_FLOAT(OFS_RETURN) = 0;
+       if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex]))
+               return;
+       if (bonenum < 0 || bonenum >= skeleton->model->num_bones)
+               return;
+       PRVM_G_FLOAT(OFS_RETURN) = skeleton->model->data_bones[bonenum].parent + 1;
+}
+
+// #268 float(float skel, string tagname) skel_find_bone = #268; // (FTE_CSQC_SKELETONOBJECTS) get number of bone with specified name, 0 on failure, tagindex (bonenum+1) on success, same as using gettagindex on the modelindex
+static void VM_CL_skel_find_bone(void)
+{
+       int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1;
+       const char *tagname = PRVM_G_STRING(OFS_PARM1);
+       skeleton_t *skeleton;
+       PRVM_G_FLOAT(OFS_RETURN) = 0;
+       if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex]))
+               return;
+       PRVM_G_FLOAT(OFS_RETURN) = Mod_Alias_GetTagIndexForName(skeleton->model, 0, tagname) + 1;
+}
+
+// #269 vector(float skel, float bonenum) skel_get_bonerel = #269; // (FTE_CSQC_SKELETONOBJECTS) get matrix of bone in skeleton relative to its parent - sets v_forward, v_right, v_up, returns origin (relative to parent bone)
+static void VM_CL_skel_get_bonerel(void)
+{
+       int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1;
+       int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1;
+       skeleton_t *skeleton;
+       matrix4x4_t matrix;
+       vec3_t forward, left, up, origin;
+       VectorClear(PRVM_G_VECTOR(OFS_RETURN));
+       VectorClear(prog->globals.client->v_forward);
+       VectorClear(prog->globals.client->v_right);
+       VectorClear(prog->globals.client->v_up);
+       if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex]))
+               return;
+       if (bonenum < 0 || bonenum >= skeleton->model->num_bones)
+               return;
+       matrix = skeleton->relativetransforms[bonenum];
+       Matrix4x4_ToVectors(&matrix, forward, left, up, origin);
+       VectorCopy(forward, prog->globals.client->v_forward);
+       VectorNegate(left, prog->globals.client->v_right);
+       VectorCopy(up, prog->globals.client->v_up);
+       VectorCopy(origin, PRVM_G_VECTOR(OFS_RETURN));
+}
+
+// #270 vector(float skel, float bonenum) skel_get_boneabs = #270; // (FTE_CSQC_SKELETONOBJECTS) get matrix of bone in skeleton in model space - sets v_forward, v_right, v_up, returns origin (relative to entity)
+static void VM_CL_skel_get_boneabs(void)
+{
+       int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1;
+       int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1;
+       skeleton_t *skeleton;
+       matrix4x4_t matrix;
+       matrix4x4_t temp;
+       vec3_t forward, left, up, origin;
+       VectorClear(PRVM_G_VECTOR(OFS_RETURN));
+       VectorClear(prog->globals.client->v_forward);
+       VectorClear(prog->globals.client->v_right);
+       VectorClear(prog->globals.client->v_up);
+       if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex]))
+               return;
+       if (bonenum < 0 || bonenum >= skeleton->model->num_bones)
+               return;
+       matrix = skeleton->relativetransforms[bonenum];
+       // convert to absolute
+       while ((bonenum = skeleton->model->data_bones[bonenum].parent) >= 0)
+       {
+               temp = matrix;
+               Matrix4x4_Concat(&matrix, &skeleton->relativetransforms[bonenum], &temp);
+       }
+       Matrix4x4_ToVectors(&matrix, forward, left, up, origin);
+       VectorCopy(forward, prog->globals.client->v_forward);
+       VectorNegate(left, prog->globals.client->v_right);
+       VectorCopy(up, prog->globals.client->v_up);
+       VectorCopy(origin, PRVM_G_VECTOR(OFS_RETURN));
+}
+
+// #271 void(float skel, float bonenum, vector org) skel_set_bone = #271; // (FTE_CSQC_SKELETONOBJECTS) set matrix of bone relative to its parent, reads v_forward, v_right, v_up, takes origin as parameter (relative to parent bone)
+static void VM_CL_skel_set_bone(void)
+{
+       int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1;
+       int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1;
+       vec3_t forward, left, up, origin;
+       skeleton_t *skeleton;
+       matrix4x4_t matrix;
+       if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex]))
+               return;
+       if (bonenum < 0 || bonenum >= skeleton->model->num_bones)
+               return;
+       VectorCopy(prog->globals.client->v_forward, forward);
+       VectorNegate(prog->globals.client->v_right, left);
+       VectorCopy(prog->globals.client->v_up, up);
+       VectorCopy(PRVM_G_VECTOR(OFS_PARM2), origin);
+       Matrix4x4_FromVectors(&matrix, forward, left, up, origin);
+       skeleton->relativetransforms[bonenum] = matrix;
+}
+
+// #272 void(float skel, float bonenum, vector org) skel_mul_bone = #272; // (FTE_CSQC_SKELETONOBJECTS) transform bone matrix (relative to its parent) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bone)
+static void VM_CL_skel_mul_bone(void)
+{
+       int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1;
+       int bonenum = (int)PRVM_G_FLOAT(OFS_PARM1) - 1;
+       vec3_t forward, left, up, origin;
+       skeleton_t *skeleton;
+       matrix4x4_t matrix;
+       matrix4x4_t temp;
+       if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex]))
+               return;
+       if (bonenum < 0 || bonenum >= skeleton->model->num_bones)
+               return;
+       VectorCopy(PRVM_G_VECTOR(OFS_PARM2), origin);
+       VectorCopy(prog->globals.client->v_forward, forward);
+       VectorNegate(prog->globals.client->v_right, left);
+       VectorCopy(prog->globals.client->v_up, up);
+       Matrix4x4_FromVectors(&matrix, forward, left, up, origin);
+       temp = skeleton->relativetransforms[bonenum];
+       Matrix4x4_Concat(&skeleton->relativetransforms[bonenum], &matrix, &temp);
+}
+
+// #273 void(float skel, float startbone, float endbone, vector org) skel_mul_bones = #273; // (FTE_CSQC_SKELETONOBJECTS) transform bone matrices (relative to their parents) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bones)
+static void VM_CL_skel_mul_bones(void)
+{
+       int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1;
+       int firstbone = PRVM_G_FLOAT(OFS_PARM1) - 1;
+       int lastbone = PRVM_G_FLOAT(OFS_PARM2) - 1;
+       int bonenum;
+       vec3_t forward, left, up, origin;
+       skeleton_t *skeleton;
+       matrix4x4_t matrix;
+       matrix4x4_t temp;
+       if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex]))
+               return;
+       VectorCopy(PRVM_G_VECTOR(OFS_PARM3), origin);
+       VectorCopy(prog->globals.client->v_forward, forward);
+       VectorNegate(prog->globals.client->v_right, left);
+       VectorCopy(prog->globals.client->v_up, up);
+       Matrix4x4_FromVectors(&matrix, forward, left, up, origin);
+       firstbone = max(0, firstbone);
+       lastbone = min(lastbone, skeleton->model->num_bones - 1);
+       for (bonenum = firstbone;bonenum <= lastbone;bonenum++)
+       {
+               temp = skeleton->relativetransforms[bonenum];
+               Matrix4x4_Concat(&skeleton->relativetransforms[bonenum], &matrix, &temp);
+       }
+}
+
+// #274 void(float skeldst, float skelsrc, float startbone, float endbone) skel_copybones = #274; // (FTE_CSQC_SKELETONOBJECTS) copy bone matrices (relative to their parents) from one skeleton to another, useful for copying a skeleton to a corpse
+static void VM_CL_skel_copybones(void)
+{
+       int skeletonindexdst = (int)PRVM_G_FLOAT(OFS_PARM0) - 1;
+       int skeletonindexsrc = (int)PRVM_G_FLOAT(OFS_PARM1) - 1;
+       int firstbone = PRVM_G_FLOAT(OFS_PARM2) - 1;
+       int lastbone = PRVM_G_FLOAT(OFS_PARM3) - 1;
+       int bonenum;
+       skeleton_t *skeletondst;
+       skeleton_t *skeletonsrc;
+       if (skeletonindexdst < 0 || skeletonindexdst >= MAX_EDICTS || !(skeletondst = prog->skeletons[skeletonindexdst]))
+               return;
+       if (skeletonindexsrc < 0 || skeletonindexsrc >= MAX_EDICTS || !(skeletonsrc = prog->skeletons[skeletonindexsrc]))
+               return;
+       firstbone = max(0, firstbone);
+       lastbone = min(lastbone, skeletondst->model->num_bones - 1);
+       lastbone = min(lastbone, skeletonsrc->model->num_bones - 1);
+       for (bonenum = firstbone;bonenum <= lastbone;bonenum++)
+               skeletondst->relativetransforms[bonenum] = skeletonsrc->relativetransforms[bonenum];
+}
+
+// #275 void(float skel) skel_delete = #275; // (FTE_CSQC_SKELETONOBJECTS) deletes skeleton at the beginning of the next frame (you can add the entity, delete the skeleton, renderscene, and it will still work)
+static void VM_CL_skel_delete(void)
+{
+       int skeletonindex = (int)PRVM_G_FLOAT(OFS_PARM0) - 1;
+       skeleton_t *skeleton;
+       if (skeletonindex < 0 || skeletonindex >= MAX_EDICTS || !(skeleton = prog->skeletons[skeletonindex]))
+               return;
+       Mem_Free(skeleton);
+       prog->skeletons[skeletonindex] = NULL;
+}
+
+// #276 float(float modlindex, string framename) frameforname = #276; // (FTE_CSQC_SKELETONOBJECTS) finds number of a specified frame in the animation, returns -1 if no match found
+static void VM_CL_frameforname(void)
+{
+       int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0);
+       dp_model_t *model = CL_GetModelByIndex(modelindex);
+       const char *name = PRVM_G_STRING(OFS_PARM1);
+       int i;
+       PRVM_G_FLOAT(OFS_RETURN) = -1;
+       if (!model || !model->animscenes)
+               return;
+       for (i = 0;i < model->numframes;i++)
+       {
+               if (!strcasecmp(model->animscenes[i].name, name))
+               {
+                       PRVM_G_FLOAT(OFS_RETURN) = i;
+                       break;
+               }
+       }
+}
+
+// #277 float(float modlindex, float framenum) frameduration = #277; // (FTE_CSQC_SKELETONOBJECTS) returns the intended play time (in seconds) of the specified framegroup, if it does not exist the result is 0, if it is a single frame it may be a small value around 0.1 or 0.
+static void VM_CL_frameduration(void)
+{
+       int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0);
+       dp_model_t *model = CL_GetModelByIndex(modelindex);
+       int framenum = (int)PRVM_G_FLOAT(OFS_PARM1);
+       PRVM_G_FLOAT(OFS_RETURN) = 0;
+       if (!model || !model->animscenes || framenum < 0 || framenum >= model->numframes)
+               return;
+       if (model->animscenes[framenum].framerate)
+               PRVM_G_FLOAT(OFS_RETURN) = model->animscenes[framenum].framecount / model->animscenes[framenum].framerate;
+}
+
 //============================================================================
 
 // To create a almost working builtin file from this replace:
@@ -2974,7 +3971,7 @@ VM_vlen,                                          // #12 float(vector v) vlen (QUAKE)
 VM_vectoyaw,                                   // #13 float(vector v) vectoyaw (QUAKE)
 VM_CL_spawn,                                   // #14 entity() spawn (QUAKE)
 VM_remove,                                             // #15 void(entity e) remove (QUAKE)
-VM_CL_traceline,                               // #16 float(vector v1, vector v2, float tryents, entity ignoreentity) traceline (QUAKE)
+VM_CL_traceline,                               // #16 void(vector v1, vector v2, float tryents, entity ignoreentity) traceline (QUAKE)
 NULL,                                                  // #17 entity() checkclient (QUAKE)
 VM_find,                                               // #18 entity(entity start, .string fld, string match) find (QUAKE)
 VM_precache_sound,                             // #19 void(string s) precache_sound (QUAKE)
@@ -3200,7 +4197,7 @@ NULL,                                                     // #236
 NULL,                                                  // #237
 NULL,                                                  // #238
 NULL,                                                  // #239
-NULL,                                                  // #240
+VM_CL_checkpvs,                                        // #240
 NULL,                                                  // #241
 NULL,                                                  // #242
 NULL,                                                  // #243
@@ -3223,21 +4220,21 @@ NULL,                                                   // #259
 NULL,                                                  // #260
 NULL,                                                  // #261
 NULL,                                                  // #262
-NULL,                                                  // #263
-NULL,                                                  // #264
-NULL,                                                  // #265
-NULL,                                                  // #266
-NULL,                                                  // #267
-NULL,                                                  // #268
-NULL,                                                  // #269
-NULL,                                                  // #270
-NULL,                                                  // #271
-NULL,                                                  // #272
-NULL,                                                  // #273
-NULL,                                                  // #274
-NULL,                                                  // #275
-NULL,                                                  // #276
-NULL,                                                  // #277
+VM_CL_skel_create,                             // #263 float(float modlindex) skel_create = #263; // (FTE_CSQC_SKELETONOBJECTS) create a skeleton (be sure to assign this value into .skeletonindex for use), returns skeleton index (1 or higher) on success, returns 0 on failure  (for example if the modelindex is not skeletal), it is recommended that you create a new skeleton if you change modelindex.
+VM_CL_skel_build,                              // #264 float(float skel, entity ent, float modlindex, float retainfrac, float firstbone, float lastbone) skel_build = #264; // (FTE_CSQC_SKELETONOBJECTS) blend in a percentage of standard animation, 0 replaces entirely, 1 does nothing, 0.5 blends half, etc, and this only alters the bones in the specified range for which out of bounds values like 0,100000 are safe (uses .frame, .frame2, .frame3, .frame4, .lerpfrac, .lerpfrac3, .lerpfrac4, .frame1time, .frame2time, .frame3time, .frame4time), returns skel on success, 0 on failure
+VM_CL_skel_get_numbones,               // #265 float(float skel) skel_get_numbones = #265; // (FTE_CSQC_SKELETONOBJECTS) returns how many bones exist in the created skeleton
+VM_CL_skel_get_bonename,               // #266 string(float skel, float bonenum) skel_get_bonename = #266; // (FTE_CSQC_SKELETONOBJECTS) returns name of bone (as a tempstring)
+VM_CL_skel_get_boneparent,             // #267 float(float skel, float bonenum) skel_get_boneparent = #267; // (FTE_CSQC_SKELETONOBJECTS) returns parent num for supplied bonenum, -1 if bonenum has no parent or bone does not exist (returned value is always less than bonenum, you can loop on this)
+VM_CL_skel_find_bone,                  // #268 float(float skel, string tagname) skel_find_bone = #268; // (FTE_CSQC_SKELETONOBJECTS) get number of bone with specified name, 0 on failure, tagindex (bonenum+1) on success, same as using gettagindex on the modelindex
+VM_CL_skel_get_bonerel,                        // #269 vector(float skel, float bonenum) skel_get_bonerel = #269; // (FTE_CSQC_SKELETONOBJECTS) get matrix of bone in skeleton relative to its parent - sets v_forward, v_right, v_up, returns origin (relative to parent bone)
+VM_CL_skel_get_boneabs,                        // #270 vector(float skel, float bonenum) skel_get_boneabs = #270; // (FTE_CSQC_SKELETONOBJECTS) get matrix of bone in skeleton in model space - sets v_forward, v_right, v_up, returns origin (relative to entity)
+VM_CL_skel_set_bone,                   // #271 void(float skel, float bonenum, vector org) skel_set_bone = #271; // (FTE_CSQC_SKELETONOBJECTS) set matrix of bone relative to its parent, reads v_forward, v_right, v_up, takes origin as parameter (relative to parent bone)
+VM_CL_skel_mul_bone,                   // #272 void(float skel, float bonenum, vector org) skel_mul_bone = #272; // (FTE_CSQC_SKELETONOBJECTS) transform bone matrix (relative to its parent) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bone)
+VM_CL_skel_mul_bones,                  // #273 void(float skel, float startbone, float endbone, vector org) skel_mul_bones = #273; // (FTE_CSQC_SKELETONOBJECTS) transform bone matrices (relative to their parents) by the supplied matrix in v_forward, v_right, v_up, takes origin as parameter (relative to parent bones)
+VM_CL_skel_copybones,                  // #274 void(float skeldst, float skelsrc, float startbone, float endbone) skel_copybones = #274; // (FTE_CSQC_SKELETONOBJECTS) copy bone matrices (relative to their parents) from one skeleton to another, useful for copying a skeleton to a corpse
+VM_CL_skel_delete,                             // #275 void(float skel) skel_delete = #275; // (FTE_CSQC_SKELETONOBJECTS) deletes skeleton at the beginning of the next frame (you can add the entity, delete the skeleton, renderscene, and it will still work)
+VM_CL_frameforname,                            // #276 float(float modlindex, string framename) frameforname = #276; // (FTE_CSQC_SKELETONOBJECTS) finds number of a specified frame in the animation, returns -1 if no match found
+VM_CL_frameduration,                   // #277 float(float modlindex, float framenum) frameduration = #277; // (FTE_CSQC_SKELETONOBJECTS) returns the intended play time (in seconds) of the specified framegroup, if it does not exist the result is 0, if it is a single frame it may be a small value around 0.1 or 0.
 NULL,                                                  // #278
 NULL,                                                  // #279
 NULL,                                                  // #280
@@ -3290,7 +4287,7 @@ VM_drawresetcliparea,                     // #325 void(void) drawresetcliparea
 VM_drawcolorcodedstring,               // #326 float drawcolorcodedstring(vector position, string text, vector scale, vector rgb, float alpha, float flag) (EXT_CSQC)
 VM_stringwidth,                 // #327 // FIXME is this okay?
 VM_drawsubpic,                                 // #328 // FIXME is this okay?
-NULL,                                                  // #329
+VM_drawrotpic,                                 // #329 // FIXME is this okay?
 VM_CL_getstatf,                                        // #330 float(float stnum) getstatf (EXT_CSQC)
 VM_CL_getstati,                                        // #331 float(float stnum) getstati (EXT_CSQC)
 VM_CL_getstats,                                        // #332 string(float firststnum) getstats (EXT_CSQC)
@@ -3481,9 +4478,116 @@ VM_argv_start_index,                                    // #515 float(float idx) argv_start_index = #515; (DP_Q
 VM_argv_end_index,                                             // #516 float(float idx) argv_end_index = #516; (DP_QC_TOKENIZE_CONSOLE)
 VM_buf_cvarlist,                                               // #517 void(float buf, string prefix, string antiprefix) buf_cvarlist = #517; (DP_QC_STRINGBUFFERS_CVARLIST)
 VM_cvar_description,                                   // #518 float(string name) cvar_description = #518; (DP_QC_CVAR_DESCRIPTION)
-NULL,                                                  // #519
+VM_gettime,                                            // #519 float(float timer) gettime = #519; (DP_QC_GETTIME)
 VM_keynumtostring,                             // #520 string keynumtostring(float keynum)
-VM_findkeysforcommand,         // #521 string findkeysforcommand(string command)
+VM_findkeysforcommand,                 // #521 string findkeysforcommand(string command)
+VM_CL_InitParticleSpawner,             // #522 void(float max_themes) initparticlespawner (DP_CSQC_SPAWNPARTICLE)
+VM_CL_ResetParticle,                   // #523 void() resetparticle (DP_CSQC_SPAWNPARTICLE)
+VM_CL_ParticleTheme,                   // #524 void(float theme) particletheme (DP_CSQC_SPAWNPARTICLE)
+VM_CL_ParticleThemeSave,               // #525 void() particlethemesave, void(float theme) particlethemeupdate (DP_CSQC_SPAWNPARTICLE)
+VM_CL_ParticleThemeFree,               // #526 void() particlethemefree (DP_CSQC_SPAWNPARTICLE)
+VM_CL_SpawnParticle,                   // #527 float(vector org, vector vel, [float theme]) particle (DP_CSQC_SPAWNPARTICLE)
+VM_CL_SpawnParticleDelayed,            // #528 float(vector org, vector vel, float delay, float collisiondelay, [float theme]) delayedparticle (DP_CSQC_SPAWNPARTICLE)
+VM_loadfromdata,                               // #529
+VM_loadfromfile,                               // #530
+NULL,                                                  // #531
+VM_log,                                                        // #532
+NULL,                                                  // #533
+NULL,                                                  // #534
+NULL,                                                  // #535
+NULL,                                                  // #536
+NULL,                                                  // #537
+NULL,                                                  // #538
+NULL,                                                  // #539
+NULL,                                                  // #540
+NULL,                                                  // #541
+NULL,                                                  // #542
+NULL,                                                  // #543
+NULL,                                                  // #544
+NULL,                                                  // #545
+NULL,                                                  // #546
+NULL,                                                  // #547
+NULL,                                                  // #548
+NULL,                                                  // #549
+NULL,                                                  // #550
+NULL,                                                  // #551
+NULL,                                                  // #552
+NULL,                                                  // #553
+NULL,                                                  // #554
+NULL,                                                  // #555
+NULL,                                                  // #556
+NULL,                                                  // #557
+NULL,                                                  // #558
+NULL,                                                  // #559
+NULL,                                                  // #560
+NULL,                                                  // #561
+NULL,                                                  // #562
+NULL,                                                  // #563
+NULL,                                                  // #564
+NULL,                                                  // #565
+NULL,                                                  // #566
+NULL,                                                  // #567
+NULL,                                                  // #568
+NULL,                                                  // #569
+NULL,                                                  // #570
+NULL,                                                  // #571
+NULL,                                                  // #572
+NULL,                                                  // #573
+NULL,                                                  // #574
+NULL,                                                  // #575
+NULL,                                                  // #576
+NULL,                                                  // #577
+NULL,                                                  // #578
+NULL,                                                  // #579
+NULL,                                                  // #580
+NULL,                                                  // #581
+NULL,                                                  // #582
+NULL,                                                  // #583
+NULL,                                                  // #584
+NULL,                                                  // #585
+NULL,                                                  // #586
+NULL,                                                  // #587
+NULL,                                                  // #588
+NULL,                                                  // #589
+NULL,                                                  // #590
+NULL,                                                  // #591
+NULL,                                                  // #592
+NULL,                                                  // #593
+NULL,                                                  // #594
+NULL,                                                  // #595
+NULL,                                                  // #596
+NULL,                                                  // #597
+NULL,                                                  // #598
+NULL,                                                  // #599
+NULL,                                                  // #600
+NULL,                                                  // #601
+NULL,                                                  // #602
+NULL,                                                  // #603
+NULL,                                                  // #604
+VM_callfunction,                               // #605
+VM_writetofile,                                        // #606
+VM_isfunction,                                 // #607
+NULL,                                                  // #608
+NULL,                                                  // #609
+NULL,                                                  // #610
+NULL,                                                  // #611
+NULL,                                                  // #612
+VM_parseentitydata,                            // #613
+NULL,                                                  // #614
+NULL,                                                  // #615
+NULL,                                                  // #616
+NULL,                                                  // #617
+NULL,                                                  // #618
+NULL,                                                  // #619
+NULL,                                                  // #620
+NULL,                                                  // #621
+NULL,                                                  // #622
+NULL,                                                  // #623
+VM_CL_getextresponse,                  // #624 string getextresponse(void)
+NULL,                                                  // #625
+NULL,                                                  // #626
+VM_sprintf,                     // #627 string sprintf(string format, ...)
+NULL,                                                  // #628
 };
 
 const int vm_cl_numbuiltins = sizeof(vm_cl_builtins) / sizeof(prvm_builtin_t);
@@ -3508,6 +4612,7 @@ void VM_CL_Cmd_Init(void)
 
 void VM_CL_Cmd_Reset(void)
 {
+       World_End(&cl.world);
        VM_Cmd_Reset();
        VM_Polygons_Reset();
 }