]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - clvm_cmds.c
extresponse: make svqc receive only those on the server socket, and csqc/menuqc only...
[xonotic/darkplaces.git] / clvm_cmds.c
index 8cb4f3813b40da96ff5ea6077bcc2aef93f17649..89bea834150ca5f2cf7e8d056e8863722a239931 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);
@@ -188,7 +189,7 @@ 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);
+       S_StartSound(MAX_EDICTS + PRVM_NUM_FOR_EDICT(entity), channel, S_FindName(sample), entity->fields.client->origin, volume, attenuation);
 }
 
 // #483 void(vector origin, string sample, float volume, float attenuation) pointsound
@@ -218,8 +219,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 +242,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 +259,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 +297,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 +335,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 +394,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++)
@@ -435,8 +436,16 @@ static void VM_CL_findradius (void)
        vec3_t                  org, eorg, mins, maxs;
        int                             i, numtouchedicts;
        prvm_edict_t    *touchedicts[MAX_EDICTS];
+       int             chainfield;
 
-       VM_SAFEPARMCOUNT(2, VM_CL_findradius);
+       VM_SAFEPARMCOUNTRANGE(2, 3, 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 +487,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 +523,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 +552,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 +602,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 +616,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 +681,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 +727,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 +750,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 +851,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 +888,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 +935,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 +949,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 +970,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 +1151,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 +1174,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 +1195,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;
                }
        }
 }
@@ -1407,10 +1432,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 +1443,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 +1462,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 +2214,93 @@ 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;
+       int frame;
+
+       *tagname = NULL;
+       *parentindex = 0;
+       Matrix4x4_CreateIdentity(tag_localmatrix);
+
+       if (tagindex >= 0
+        && (model = CL_GetModelFromEdict(e))
+        && model->animscenes)
+       {
+               frame = (int)e->fields.client->frame;
+               if (frame < 0 || frame >= model->numframes)
+                       frame = 0;
+
+               r = Mod_Alias_GetExtendedTagInfoForIndex(model, (int)e->fields.client->skin, model->animscenes[frame].firstframe, 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)
+{
+       int frame;
+       int ret;
+       dp_model_t *model;
+       entity_render_t cheatentity;
+       if (tagindex >= 0
+        && (model = CL_GetModelFromEdict(ent))
+        && model->animscenes)
+       {
+               // if model has wrong frame, engine automatically switches to model first frame
+               frame = (int)ent->fields.client->frame;
+               if (frame < 0 || frame >= model->numframes)
+                       frame = 0;
+               // now we'll do some CHEATING
+               memset(&cheatentity, 0, sizeof(cheatentity));
+               cheatentity.model = model;
+               CL_LoadFrameGroupBlend(ent, &cheatentity);
+               R_LerpAnimation(&cheatentity);
+               ret = CL_BlendTagMatrix(&cheatentity, tagindex, out);
+               if(ret)
+                       *out = identitymatrix;
+               return ret;
+       }
+       *out = identitymatrix;
+       return 0;
+}
 
 // Warnings/errors code:
 // 0 - normal (everything all-right)
@@ -2186,12 +2314,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, attachloop;
+       int attachloop;
        matrix4x4_t entitymatrix, tagmatrix, attachmatrix;
-       prvm_edict_t *attachent;
        dp_model_t *model;
-       float scale;
 
        *out = identitymatrix; // warnings and errors return identical matrix
 
@@ -2201,80 +2328,40 @@ 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;
-       }
-       else
-               tagmatrix = identitymatrix;
-
-       if ((val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.tag_entity)) && val->edict)
-       { // DP_GFX_QUAKE3MODELTAGS, scan all chain and stop on unattached entity
-               attachloop = 0;
-               do
+               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)
                {
-                       attachent = PRVM_EDICT_NUM(val->edict); // to this it entity our entity is attached
-                       val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.tag_index);
-
-                       model = CL_GetModelFromEdict(attachent);
-
-                       if (model && val->_float >= 1 && model->animscenes && attachent->fields.client->frame >= 0 && attachent->fields.client->frame < model->numframes)
-                               Mod_Alias_GetTagMatrix(model, model->animscenes[(int)attachent->fields.client->frame].firstframe, (int)val->_float - 1, &attachmatrix);
-                       else
-                               attachmatrix = identitymatrix;
-
-                       // apply transformation by child entity matrix
-                       scale = 1;
-                       val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.scale);
-                       if (val && val->_float != 0)
-                               scale = val->_float;
-                       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);
-                       Matrix4x4_Copy(&tagmatrix, out);
-
-                       // finally transformate by matrix of tag on parent entity
-                       Matrix4x4_Concat(out, &attachmatrix, &tagmatrix);
-                       Matrix4x4_Copy(&tagmatrix, out);
-
-                       ent = attachent;
-                       attachloop += 1;
-                       if (attachloop > 255) // prevent runaway looping
-                               return 5;
+                       tagindex = (int)PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.tag_index)->_float;
+                       ent = PRVM_EDICT_NUM(val->edict);
                }
-               while ((val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.tag_entity)) && val->edict);
+               else
+                       break;
+               attachloop++;
        }
 
-       // 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...
-       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))
-       {// RENDER_VIEWMODEL magic
+       {
                Matrix4x4_Copy(&tagmatrix, out);
 
-               scale = 1;
-               val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.scale);
-               if (val && val->_float != 0)
-                       scale = val->_float;
-
-               Matrix4x4_CreateFromQuakeEntity(&entitymatrix, 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_GetEntityMatrix(prog->edicts, &entitymatrix, true);
                Matrix4x4_Concat(out, &entitymatrix, &tagmatrix);
 
                /*
@@ -2344,14 +2431,35 @@ 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;
 
        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);
+       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)
        {
@@ -2375,6 +2483,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
 //====================
@@ -2423,8 +2973,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();
@@ -2433,6 +2985,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)
@@ -2533,7 +3088,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];
@@ -2802,7 +3358,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;
@@ -2815,7 +3371,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];
@@ -2864,7 +3420,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);
 
@@ -2892,14 +3448,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)
@@ -3020,6 +3576,75 @@ 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
+}
 //============================================================================
 
 // To create a almost working builtin file from this replace:
@@ -3046,7 +3671,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)
@@ -3272,7 +3897,7 @@ NULL,                                                     // #236
 NULL,                                                  // #237
 NULL,                                                  // #238
 NULL,                                                  // #239
-NULL,                                                  // #240
+VM_CL_checkpvs,                                        // #240
 NULL,                                                  // #241
 NULL,                                                  // #242
 NULL,                                                  // #243
@@ -3362,7 +3987,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)
@@ -3553,9 +4178,113 @@ 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
 };
 
 const int vm_cl_numbuiltins = sizeof(vm_cl_builtins) / sizeof(prvm_builtin_t);
@@ -3580,6 +4309,7 @@ void VM_CL_Cmd_Init(void)
 
 void VM_CL_Cmd_Reset(void)
 {
+       World_End(&cl.world);
        VM_Cmd_Reset();
        VM_Polygons_Reset();
 }