]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - svvm_cmds.c
make getsurface* functions shared between VMs
[xonotic/darkplaces.git] / svvm_cmds.c
index 6fde46d70d4d12cfb3932908a4696077596402dc..889a8fbb262f5c407bc0dff0316909cc6ed6cbfb 100644 (file)
@@ -19,7 +19,10 @@ char *vm_sv_extensions =
 "DP_CON_SET "
 "DP_CON_SETA "
 "DP_CON_STARTMAP "
+"DP_CSQC_ENTITYNOCULL "
+"DP_CSQC_ENTITYTRANSPARENTSORTING_OFFSET "
 "DP_CSQC_MULTIFRAME_INTERPOLATION "
+"DP_CSQC_SPAWNPARTICLE "
 "DP_EF_ADDITIVE "
 "DP_EF_BLUE "
 "DP_EF_DOUBLESIDED "
@@ -31,6 +34,7 @@ char *vm_sv_extensions =
 "DP_EF_NOSELFSHADOW "
 "DP_EF_NOSHADOW "
 "DP_EF_RED "
+"DP_EF_RESTARTANIM_BIT "
 "DP_EF_STARDUST "
 "DP_EF_TELEPORT_BIT "
 "DP_ENT_ALPHA "
@@ -38,6 +42,7 @@ char *vm_sv_extensions =
 "DP_ENT_CUSTOMCOLORMAP "
 "DP_ENT_EXTERIORMODELTOCLIENT "
 "DP_ENT_GLOW "
+"DP_ENT_GLOWMOD "
 "DP_ENT_LOWPRECISION "
 "DP_ENT_SCALE "
 "DP_ENT_VIEWMODEL "
@@ -45,14 +50,15 @@ char *vm_sv_extensions =
 "DP_GFX_EXTERNALTEXTURES "
 "DP_GFX_EXTERNALTEXTURES_PERMAP "
 "DP_GFX_FOG "
+"DP_GFX_MODEL_INTERPOLATION "
 "DP_GFX_QUAKE3MODELTAGS "
 "DP_GFX_SKINFILES "
 "DP_GFX_SKYBOX "
-"DP_GFX_MODEL_INTERPOLATION "
 "DP_HALFLIFE_MAP "
 "DP_HALFLIFE_MAP_CVAR "
 "DP_HALFLIFE_SPRITE "
 "DP_INPUTBUTTONS "
+"DP_LIGHTSTYLE_STATICVALUE "
 "DP_LITSPRITES "
 "DP_LITSUPPORT "
 "DP_MONSTERWALK "
@@ -60,6 +66,7 @@ char *vm_sv_extensions =
 "DP_MOVETYPEFOLLOW "
 "DP_NULL_MODEL "
 "DP_QC_ASINACOSATANATAN2TAN "
+"DP_QC_AUTOCVARS "
 "DP_QC_CHANGEPITCH "
 "DP_QC_CMD "
 "DP_QC_COPYENTITY "
@@ -70,27 +77,31 @@ char *vm_sv_extensions =
 "DP_QC_CVAR_TYPE "
 "DP_QC_EDICT_NUM "
 "DP_QC_ENTITYDATA "
+"DP_QC_ENTITYSTRING "
 "DP_QC_ETOS "
 "DP_QC_EXTRESPONSEPACKET "
 "DP_QC_FINDCHAIN "
-"DP_QC_FINDCHAIN_TOFIELD "
 "DP_QC_FINDCHAINFLAGS "
 "DP_QC_FINDCHAINFLOAT "
+"DP_QC_FINDCHAIN_TOFIELD "
 "DP_QC_FINDFLAGS "
 "DP_QC_FINDFLOAT "
 "DP_QC_FS_SEARCH "
 "DP_QC_GETLIGHT "
 "DP_QC_GETSURFACE "
+"DP_QC_GETSURFACETRIANGLE "
 "DP_QC_GETSURFACEPOINTATTRIBUTE "
 "DP_QC_GETTAGINFO "
 "DP_QC_GETTAGINFO_BONEPROPERTIES "
 "DP_QC_GETTIME "
 "DP_QC_GETTIME_CDTRACK "
+"DP_QC_LOG "
 "DP_QC_MINMAXBOUND "
 "DP_QC_MULTIPLETEMPSTRINGS "
 "DP_QC_NUM_FOR_EDICT "
 "DP_QC_RANDOMVEC "
 "DP_QC_SINCOSSQRTPOW "
+"DP_QC_SPRINTF "
 "DP_QC_STRFTIME "
 "DP_QC_STRINGBUFFERS "
 "DP_QC_STRINGBUFFERS_CVARLIST "
@@ -114,9 +125,11 @@ char *vm_sv_extensions =
 "DP_QUAKE3_MAP "
 "DP_QUAKE3_MODEL "
 "DP_REGISTERCVAR "
+"DP_SKELETONOBJECTS "
 "DP_SND_DIRECTIONLESSATTNNONE "
 "DP_SND_FAKETRACKS "
 "DP_SND_OGGVORBIS "
+"DP_SND_SETPARAMS "
 "DP_SND_STEREOWAV "
 "DP_SOLIDCORPSE "
 "DP_SPRITE32 "
@@ -137,6 +150,7 @@ char *vm_sv_extensions =
 "DP_SV_ONENTITYNOSPAWNFUNCTION "
 "DP_SV_ONENTITYPREPOSTSPAWNFUNCTION "
 "DP_SV_PING "
+"DP_SV_PING_PACKETLOSS "
 "DP_SV_PLAYERPHYSICS "
 "DP_SV_POINTPARTICLES "
 "DP_SV_POINTSOUND "
@@ -168,6 +182,7 @@ char *vm_sv_extensions =
 "DP_VIEWZOOM "
 "EXT_BITSHIFT "
 "FRIK_FILE "
+"FTE_CSQC_SKELETONOBJECTS "
 "FTE_QC_CHECKPVS "
 "FTE_STRINGS "
 "KRIMZON_SV_PARSECLIENTCOMMAND "
@@ -211,7 +226,7 @@ static void VM_SV_setorigin (void)
        }
        org = PRVM_G_VECTOR(OFS_PARM1);
        VectorCopy (org, e->fields.server->origin);
-       SV_LinkEdict (e, false);
+       SV_LinkEdict(e);
 }
 
 // TODO: rotate param isnt used.. could be a bug. please check this and remove it if possible [1/10/2008 Black]
@@ -228,7 +243,7 @@ static void SetMinMaxSize (prvm_edict_t *e, float *min, float *max, qboolean rot
        VectorCopy (max, e->fields.server->maxs);
        VectorSubtract (max, min, e->fields.server->size);
 
-       SV_LinkEdict (e, false);
+       SV_LinkEdict(e);
 }
 
 /*
@@ -296,7 +311,7 @@ static void VM_SV_setmodel (void)
        e->fields.server->model = PRVM_SetEngineString(sv.model_precache[i]);
        e->fields.server->modelindex = i;
 
-       mod = sv.models[i];
+       mod = SV_GetModelByIndex(i);
 
        if (mod)
        {
@@ -582,7 +597,7 @@ static void VM_SV_traceline (void)
        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 = SV_Move (v1, vec3_origin, vec3_origin, v2, move, ent, SV_GenericHitSuperContentsMask(ent));
+       trace = SV_TraceLine(v1, v2, move, ent, SV_GenericHitSuperContentsMask(ent));
 
        VM_SetTraceGlobals(&trace);
 }
@@ -621,7 +636,7 @@ static void VM_SV_tracebox (void)
        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 = SV_Move (v1, m1, m2, v2, move, ent, SV_GenericHitSuperContentsMask(ent));
+       trace = SV_TraceBox(v1, m1, m2, v2, move, ent, SV_GenericHitSuperContentsMask(ent));
 
        VM_SetTraceGlobals(&trace);
 }
@@ -657,7 +672,7 @@ static trace_t SV_Trace_Toss (prvm_edict_t *tossent, prvm_edict_t *ignore)
                VectorMA (tossent->fields.server->angles, 0.05, tossent->fields.server->avelocity, tossent->fields.server->angles);
                VectorScale (tossent->fields.server->velocity, 0.05, move);
                VectorAdd (tossent->fields.server->origin, move, end);
-               trace = SV_Move (tossent->fields.server->origin, tossent->fields.server->mins, tossent->fields.server->maxs, end, MOVE_NORMAL, tossent, SV_GenericHitSuperContentsMask(tossent));
+               trace = SV_TraceBox(tossent->fields.server->origin, tossent->fields.server->mins, tossent->fields.server->maxs, end, MOVE_NORMAL, tossent, SV_GenericHitSuperContentsMask(tossent));
                VectorCopy (trace.endpos, tossent->fields.server->origin);
                tossent->fields.server->velocity[2] -= gravity;
 
@@ -812,8 +827,8 @@ static void VM_SV_checkpvs (void)
 #if 1
        unsigned char *pvs;
 #else
-       static int fatpvsbytes;
-       static unsigned char fatpvs[MAX_MAP_LEAFS/8];
+       int fatpvsbytes;
+       unsigned char fatpvs[MAX_MAP_LEAFS/8];
 #endif
 
        VM_SAFEPARMCOUNT(2, VM_SV_checkpvs);
@@ -910,7 +925,7 @@ static void VM_SV_findradius (void)
        vec3_t org, eorg, mins, maxs;
        int i;
        int numtouchedicts;
-       prvm_edict_t *touchedicts[MAX_EDICTS];
+       static prvm_edict_t *touchedicts[MAX_EDICTS];
        int chainfield;
 
        VM_SAFEPARMCOUNTRANGE(2, 3, VM_SV_findradius);
@@ -1077,19 +1092,19 @@ static void VM_SV_droptofloor (void)
        if (sv_gameplayfix_droptofloorstartsolid_nudgetocorrect.integer)
                SV_UnstickEntity(ent);
 
-       trace = SV_Move (ent->fields.server->origin, ent->fields.server->mins, ent->fields.server->maxs, end, MOVE_NORMAL, ent, SV_GenericHitSuperContentsMask(ent));
+       trace = SV_TraceBox(ent->fields.server->origin, ent->fields.server->mins, ent->fields.server->maxs, end, MOVE_NORMAL, ent, SV_GenericHitSuperContentsMask(ent));
        if (trace.startsolid && sv_gameplayfix_droptofloorstartsolid.integer)
        {
                vec3_t offset, org;
                VectorSet(offset, 0.5f * (ent->fields.server->mins[0] + ent->fields.server->maxs[0]), 0.5f * (ent->fields.server->mins[1] + ent->fields.server->maxs[1]), ent->fields.server->mins[2]);
                VectorAdd(ent->fields.server->origin, offset, org);
-               trace = SV_Move (org, vec3_origin, vec3_origin, end, MOVE_NORMAL, ent, SV_GenericHitSuperContentsMask(ent));
+               trace = SV_TraceLine(org, end, MOVE_NORMAL, ent, SV_GenericHitSuperContentsMask(ent));
                VectorSubtract(trace.endpos, offset, trace.endpos);
                if (trace.startsolid)
                {
                        Con_DPrintf("droptofloor at %f %f %f - COULD NOT FIX BADLY PLACED ENTITY\n", ent->fields.server->origin[0], ent->fields.server->origin[1], ent->fields.server->origin[2]);
                        SV_UnstickEntity(ent);
-                       SV_LinkEdict (ent, false);
+                       SV_LinkEdict(ent);
                        ent->fields.server->flags = (int)ent->fields.server->flags | FL_ONGROUND;
                        ent->fields.server->groundentity = 0;
                        PRVM_G_FLOAT(OFS_RETURN) = 1;
@@ -1099,7 +1114,7 @@ static void VM_SV_droptofloor (void)
                        Con_DPrintf("droptofloor at %f %f %f - FIXED BADLY PLACED ENTITY\n", ent->fields.server->origin[0], ent->fields.server->origin[1], ent->fields.server->origin[2]);
                        VectorCopy (trace.endpos, ent->fields.server->origin);
                        SV_UnstickEntity(ent);
-                       SV_LinkEdict (ent, false);
+                       SV_LinkEdict(ent);
                        ent->fields.server->flags = (int)ent->fields.server->flags | FL_ONGROUND;
                        ent->fields.server->groundentity = PRVM_EDICT_TO_PROG(trace.ent);
                        PRVM_G_FLOAT(OFS_RETURN) = 1;
@@ -1113,7 +1128,7 @@ static void VM_SV_droptofloor (void)
                {
                        if (trace.fraction < 1)
                                VectorCopy (trace.endpos, ent->fields.server->origin);
-                       SV_LinkEdict (ent, false);
+                       SV_LinkEdict(ent);
                        ent->fields.server->flags = (int)ent->fields.server->flags | FL_ONGROUND;
                        ent->fields.server->groundentity = PRVM_EDICT_TO_PROG(trace.ent);
                        PRVM_G_FLOAT(OFS_RETURN) = 1;
@@ -1230,7 +1245,7 @@ static void VM_SV_aim (void)
 // try sending a trace straight
        VectorCopy (prog->globals.server->v_forward, dir);
        VectorMA (start, 2048, dir, end);
-       tr = SV_Move (start, vec3_origin, vec3_origin, end, MOVE_NORMAL, ent, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY);
+       tr = SV_TraceLine(start, end, MOVE_NORMAL, ent, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY);
        if (tr.ent && ((prvm_edict_t *)tr.ent)->fields.server->takedamage == DAMAGE_AIM
        && (!teamplay.integer || ent->fields.server->team <=0 || ent->fields.server->team != ((prvm_edict_t *)tr.ent)->fields.server->team) )
        {
@@ -1262,7 +1277,7 @@ static void VM_SV_aim (void)
                dist = DotProduct (dir, prog->globals.server->v_forward);
                if (dist < bestdist)
                        continue;       // to far to turn
-               tr = SV_Move (start, vec3_origin, vec3_origin, end, MOVE_NORMAL, ent, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY);
+               tr = SV_TraceLine(start, end, MOVE_NORMAL, ent, SUPERCONTENTS_SOLID | SUPERCONTENTS_BODY);
                if (tr.ent == check)
                {       // can shoot at this one
                        bestdist = dist;
@@ -1682,7 +1697,7 @@ static void VM_SV_copyentity (void)
                return;
        }
        memcpy(out->fields.vp, in->fields.vp, prog->progs->entityfields * 4);
-       SV_LinkEdict(out, false);
+       SV_LinkEdict(out);
 }
 
 
@@ -2236,266 +2251,6 @@ static void VM_SV_te_flamejet (void)
        SV_FlushBroadcastMessages();
 }
 
-void clippointtosurface(dp_model_t *model, msurface_t *surface, vec3_t p, vec3_t out)
-{
-       int i, j, k;
-       float *v[3], facenormal[3], edgenormal[3], sidenormal[3], temp[3], offsetdist, dist, bestdist;
-       const int *e;
-       bestdist = 1000000000;
-       VectorCopy(p, out);
-       for (i = 0, e = (model->surfmesh.data_element3i + 3 * surface->num_firsttriangle);i < surface->num_triangles;i++, e += 3)
-       {
-               // clip original point to each triangle of the surface and find the
-               // triangle that is closest
-               v[0] = model->surfmesh.data_vertex3f + e[0] * 3;
-               v[1] = model->surfmesh.data_vertex3f + e[1] * 3;
-               v[2] = model->surfmesh.data_vertex3f + e[2] * 3;
-               TriangleNormal(v[0], v[1], v[2], facenormal);
-               VectorNormalize(facenormal);
-               offsetdist = DotProduct(v[0], facenormal) - DotProduct(p, facenormal);
-               VectorMA(p, offsetdist, facenormal, temp);
-               for (j = 0, k = 2;j < 3;k = j, j++)
-               {
-                       VectorSubtract(v[k], v[j], edgenormal);
-                       CrossProduct(edgenormal, facenormal, sidenormal);
-                       VectorNormalize(sidenormal);
-                       offsetdist = DotProduct(v[k], sidenormal) - DotProduct(temp, sidenormal);
-                       if (offsetdist < 0)
-                               VectorMA(temp, offsetdist, sidenormal, temp);
-               }
-               dist = VectorDistance2(temp, p);
-               if (bestdist > dist)
-               {
-                       bestdist = dist;
-                       VectorCopy(temp, out);
-               }
-       }
-}
-
-static dp_model_t *getmodel(prvm_edict_t *ed)
-{
-       int modelindex;
-       if (!ed || ed->priv.server->free)
-               return NULL;
-       modelindex = (int)ed->fields.server->modelindex;
-       if (modelindex < 1 || modelindex >= MAX_MODELS)
-               return NULL;
-       return sv.models[modelindex];
-}
-
-static msurface_t *getsurface(dp_model_t *model, int surfacenum)
-{
-       if (surfacenum < 0 || surfacenum >= model->nummodelsurfaces)
-               return NULL;
-       return model->data_surfaces + surfacenum + model->firstmodelsurface;
-}
-
-
-//PF_getsurfacenumpoints, // #434 float(entity e, float s) getsurfacenumpoints = #434;
-static void VM_SV_getsurfacenumpoints(void)
-{
-       dp_model_t *model;
-       msurface_t *surface;
-       VM_SAFEPARMCOUNT(2, VM_SV_getsurfacenumpoints);
-       // return 0 if no such surface
-       if (!(model = getmodel(PRVM_G_EDICT(OFS_PARM0))) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1))))
-       {
-               PRVM_G_FLOAT(OFS_RETURN) = 0;
-               return;
-       }
-
-       // note: this (incorrectly) assumes it is a simple polygon
-       PRVM_G_FLOAT(OFS_RETURN) = surface->num_vertices;
-}
-//PF_getsurfacepoint,     // #435 vector(entity e, float s, float n) getsurfacepoint = #435;
-static void VM_SV_getsurfacepoint(void)
-{
-       prvm_edict_t *ed;
-       dp_model_t *model;
-       msurface_t *surface;
-       int pointnum;
-       VM_SAFEPARMCOUNT(3, VM_SV_getsurfacepoint);
-       VectorClear(PRVM_G_VECTOR(OFS_RETURN));
-       ed = PRVM_G_EDICT(OFS_PARM0);
-       if (!(model = getmodel(ed)) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1))))
-               return;
-       // note: this (incorrectly) assumes it is a simple polygon
-       pointnum = (int)PRVM_G_FLOAT(OFS_PARM2);
-       if (pointnum < 0 || pointnum >= surface->num_vertices)
-               return;
-       // FIXME: implement rotation/scaling
-       VectorAdd(&(model->surfmesh.data_vertex3f + 3 * surface->num_firstvertex)[pointnum * 3], ed->fields.server->origin, PRVM_G_VECTOR(OFS_RETURN));
-}
-//PF_getsurfacepointattribute,     // #486 vector(entity e, float s, float n, float a) getsurfacepointattribute = #486;
-// float SPA_POSITION = 0;
-// float SPA_S_AXIS = 1;
-// float SPA_T_AXIS = 2;
-// float SPA_R_AXIS = 3; // same as SPA_NORMAL
-// float SPA_TEXCOORDS0 = 4;
-// float SPA_LIGHTMAP0_TEXCOORDS = 5;
-// float SPA_LIGHTMAP0_COLOR = 6;
-static void VM_SV_getsurfacepointattribute(void)
-{
-       prvm_edict_t *ed;
-       dp_model_t *model;
-       msurface_t *surface;
-       int pointnum;
-       int attributetype;
-
-       VM_SAFEPARMCOUNT(4, VM_SV_getsurfacepoint);
-       VectorClear(PRVM_G_VECTOR(OFS_RETURN));
-       ed = PRVM_G_EDICT(OFS_PARM0);
-       if (!(model = getmodel(ed)) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1))))
-               return;
-       // note: this (incorrectly) assumes it is a simple polygon
-       pointnum = (int)PRVM_G_FLOAT(OFS_PARM2);
-       if (pointnum < 0 || pointnum >= surface->num_vertices)
-               return;
-       // FIXME: implement rotation/scaling
-       attributetype = (int) PRVM_G_FLOAT(OFS_PARM3);
-
-       switch( attributetype ) {
-               // float SPA_POSITION = 0;
-               case 0:
-                       VectorAdd(&(model->surfmesh.data_vertex3f + 3 * surface->num_firstvertex)[pointnum * 3], ed->fields.server->origin, PRVM_G_VECTOR(OFS_RETURN));
-                       break;
-               // float SPA_S_AXIS = 1;
-               case 1:
-                       VectorCopy(&(model->surfmesh.data_svector3f + 3 * surface->num_firstvertex)[pointnum * 3], PRVM_G_VECTOR(OFS_RETURN));
-                       break;
-               // float SPA_T_AXIS = 2;
-               case 2:
-                       VectorCopy(&(model->surfmesh.data_tvector3f + 3 * surface->num_firstvertex)[pointnum * 3], PRVM_G_VECTOR(OFS_RETURN));
-                       break;
-               // float SPA_R_AXIS = 3; // same as SPA_NORMAL
-               case 3:
-                       VectorCopy(&(model->surfmesh.data_normal3f + 3 * surface->num_firstvertex)[pointnum * 3], PRVM_G_VECTOR(OFS_RETURN));
-                       break;
-               // float SPA_TEXCOORDS0 = 4;
-               case 4: {
-                       float *ret = PRVM_G_VECTOR(OFS_RETURN);
-                       float *texcoord = &(model->surfmesh.data_texcoordtexture2f + 2 * surface->num_firstvertex)[pointnum * 2];
-                       ret[0] = texcoord[0];
-                       ret[1] = texcoord[1];
-                       ret[2] = 0.0f;
-                       break;
-               }
-               // float SPA_LIGHTMAP0_TEXCOORDS = 5;
-               case 5: {
-                       float *ret = PRVM_G_VECTOR(OFS_RETURN);
-                       float *texcoord = &(model->surfmesh.data_texcoordlightmap2f + 2 * surface->num_firstvertex)[pointnum * 2];
-                       ret[0] = texcoord[0];
-                       ret[1] = texcoord[1];
-                       ret[2] = 0.0f;
-                       break;
-               }
-               // float SPA_LIGHTMAP0_COLOR = 6;
-               case 6:
-                       // ignore alpha for now..
-                       VectorCopy( &(model->surfmesh.data_lightmapcolor4f + 4 * surface->num_firstvertex)[pointnum * 4], PRVM_G_VECTOR(OFS_RETURN));
-                       break;
-               default:
-                       VectorSet( PRVM_G_VECTOR(OFS_RETURN), 0.0f, 0.0f, 0.0f );
-                       break;
-       }
-}
-//PF_getsurfacenormal,    // #436 vector(entity e, float s) getsurfacenormal = #436;
-static void VM_SV_getsurfacenormal(void)
-{
-       dp_model_t *model;
-       msurface_t *surface;
-       vec3_t normal;
-       VM_SAFEPARMCOUNT(2, VM_SV_getsurfacenormal);
-       VectorClear(PRVM_G_VECTOR(OFS_RETURN));
-       if (!(model = getmodel(PRVM_G_EDICT(OFS_PARM0))) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1))))
-               return;
-       // FIXME: implement rotation/scaling
-       // note: this (incorrectly) assumes it is a simple polygon
-       // note: this only returns the first triangle, so it doesn't work very
-       // well for curved surfaces or arbitrary meshes
-       TriangleNormal((model->surfmesh.data_vertex3f + 3 * surface->num_firstvertex), (model->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + 3, (model->surfmesh.data_vertex3f + 3 * surface->num_firstvertex) + 6, normal);
-       VectorNormalize(normal);
-       VectorCopy(normal, PRVM_G_VECTOR(OFS_RETURN));
-}
-//PF_getsurfacetexture,   // #437 string(entity e, float s) getsurfacetexture = #437;
-static void VM_SV_getsurfacetexture(void)
-{
-       dp_model_t *model;
-       msurface_t *surface;
-       VM_SAFEPARMCOUNT(2, VM_SV_getsurfacetexture);
-       PRVM_G_INT(OFS_RETURN) = OFS_NULL;
-       if (!(model = getmodel(PRVM_G_EDICT(OFS_PARM0))) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1))))
-               return;
-       PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(surface->texture->name);
-}
-//PF_getsurfacenearpoint, // #438 float(entity e, vector p) getsurfacenearpoint = #438;
-static void VM_SV_getsurfacenearpoint(void)
-{
-       int surfacenum, best;
-       vec3_t clipped, p;
-       vec_t dist, bestdist;
-       prvm_edict_t *ed;
-       dp_model_t *model;
-       msurface_t *surface;
-       vec_t *point;
-       VM_SAFEPARMCOUNT(2, VM_SV_getsurfacenearpoint);
-       PRVM_G_FLOAT(OFS_RETURN) = -1;
-       ed = PRVM_G_EDICT(OFS_PARM0);
-       point = PRVM_G_VECTOR(OFS_PARM1);
-
-       if (!ed || ed->priv.server->free)
-               return;
-       model = getmodel(ed);
-       if (!model || !model->num_surfaces)
-               return;
-
-       // FIXME: implement rotation/scaling
-       VectorSubtract(point, ed->fields.server->origin, p);
-       best = -1;
-       bestdist = 1000000000;
-       for (surfacenum = 0;surfacenum < model->nummodelsurfaces;surfacenum++)
-       {
-               surface = model->data_surfaces + surfacenum + model->firstmodelsurface;
-               // first see if the nearest point on the surface's box is closer than the previous match
-               clipped[0] = bound(surface->mins[0], p[0], surface->maxs[0]) - p[0];
-               clipped[1] = bound(surface->mins[1], p[1], surface->maxs[1]) - p[1];
-               clipped[2] = bound(surface->mins[2], p[2], surface->maxs[2]) - p[2];
-               dist = VectorLength2(clipped);
-               if (dist < bestdist)
-               {
-                       // it is, check the nearest point on the actual geometry
-                       clippointtosurface(model, surface, p, clipped);
-                       VectorSubtract(clipped, p, clipped);
-                       dist += VectorLength2(clipped);
-                       if (dist < bestdist)
-                       {
-                               // that's closer too, store it as the best match
-                               best = surfacenum;
-                               bestdist = dist;
-                       }
-               }
-       }
-       PRVM_G_FLOAT(OFS_RETURN) = best;
-}
-//PF_getsurfaceclippedpoint, // #439 vector(entity e, float s, vector p) getsurfaceclippedpoint = #439;
-static void VM_SV_getsurfaceclippedpoint(void)
-{
-       prvm_edict_t *ed;
-       dp_model_t *model;
-       msurface_t *surface;
-       vec3_t p, out;
-       VM_SAFEPARMCOUNT(3, VM_SV_te_getsurfaceclippedpoint);
-       VectorClear(PRVM_G_VECTOR(OFS_RETURN));
-       ed = PRVM_G_EDICT(OFS_PARM0);
-       if (!(model = getmodel(ed)) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1))))
-               return;
-       // FIXME: implement rotation/scaling
-       VectorSubtract(PRVM_G_VECTOR(OFS_PARM2), ed->fields.server->origin, p);
-       clippointtosurface(model, surface, p, out);
-       // FIXME: implement rotation/scaling
-       VectorAdd(out, ed->fields.server->origin, PRVM_G_VECTOR(OFS_RETURN));
-}
-
 //void(entity e, string s) clientcommand = #440; // executes a command string as if it came from the specified client
 //this function originally written by KrimZon, made shorter by LordHavoc
 static void VM_SV_clientcommand (void)
@@ -2525,7 +2280,6 @@ static void VM_SV_setattachment (void)
        prvm_edict_t *tagentity = PRVM_G_EDICT(OFS_PARM1);
        const char *tagname = PRVM_G_STRING(OFS_PARM2);
        prvm_eval_t *v;
-       int modelindex;
        dp_model_t *model;
        VM_SAFEPARMCOUNT(3, VM_SV_setattachment);
 
@@ -2552,8 +2306,8 @@ static void VM_SV_setattachment (void)
                v->_float = 0;
        if (tagentity != NULL && tagentity != prog->edicts && tagname && tagname[0])
        {
-               modelindex = (int)tagentity->fields.server->modelindex;
-               if (modelindex >= 0 && modelindex < MAX_MODELS && (model = sv.models[modelindex]))
+               model = SV_GetModelFromEdict(tagentity);
+               if (model)
                {
                        v->_float = Mod_Alias_GetTagIndexForName(model, (int)tagentity->fields.server->skin, tagname);
                        if (v->_float == 0)
@@ -2570,37 +2324,26 @@ static void VM_SV_setattachment (void)
 int SV_GetTagIndex (prvm_edict_t *e, const char *tagname)
 {
        int i;
-       dp_model_t *model;
 
        i = (int)e->fields.server->modelindex;
        if (i < 1 || i >= MAX_MODELS)
                return -1;
-       model = sv.models[i];
 
-       return Mod_Alias_GetTagIndexForName(model, (int)e->fields.server->skin, tagname);
+       return Mod_Alias_GetTagIndexForName(SV_GetModelByIndex(i), (int)e->fields.server->skin, tagname);
 }
 
 int SV_GetExtendedTagInfo (prvm_edict_t *e, int tagindex, int *parentindex, const char **tagname, matrix4x4_t *tag_localmatrix)
 {
        int r;
        dp_model_t *model;
-       int frame;
-       int modelindex;
 
        *tagname = NULL;
        *parentindex = 0;
        Matrix4x4_CreateIdentity(tag_localmatrix);
 
-       if (tagindex >= 0
-        && (modelindex = (int)e->fields.server->modelindex) >= 1 && modelindex < MAX_MODELS
-        && (model = sv.models[(int)e->fields.server->modelindex])
-        && model->animscenes)
+       if (tagindex >= 0 && (model = SV_GetModelFromEdict(e)) && model->num_bones)
        {
-               frame = (int)e->fields.server->frame;
-               if (frame < 0 || frame >= model->numframes)
-                       frame = 0;
-
-               r = Mod_Alias_GetExtendedTagInfoForIndex(model, (int)e->fields.server->skin, model->animscenes[frame].firstframe, tagindex - 1, parentindex, tagname, tag_localmatrix);
+               r = Mod_Alias_GetExtendedTagInfoForIndex(model, (int)e->fields.server->skin, e->priv.server->frameblend, &e->priv.server->skeleton, tagindex - 1, parentindex, tagname, tag_localmatrix);
 
                if(!r) // success?
                        *parentindex += 1;
@@ -2616,8 +2359,6 @@ void SV_GetEntityMatrix (prvm_edict_t *ent, matrix4x4_t *out, qboolean viewmatri
        prvm_eval_t *val;
        float scale;
        float pitchsign = 1;
-       int modelindex;
-       dp_model_t *model;
 
        scale = 1;
        val = PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.scale);
@@ -2628,37 +2369,20 @@ void SV_GetEntityMatrix (prvm_edict_t *ent, matrix4x4_t *out, qboolean viewmatri
                Matrix4x4_CreateFromQuakeEntity(out, ent->fields.server->origin[0], ent->fields.server->origin[1], ent->fields.server->origin[2] + ent->fields.server->view_ofs[2], ent->fields.server->v_angle[0], ent->fields.server->v_angle[1], ent->fields.server->v_angle[2], scale * cl_viewmodel_scale.value);
        else
        {
-               if (
-                       ((modelindex = (int)ent->fields.server->modelindex) >= 1 && modelindex < MAX_MODELS && (model = sv.models[(int)ent->fields.server->modelindex]))
-                       ?
-                               model->type == mod_alias
-                       :
-                               (
-                                       (((unsigned char)PRVM_EDICTFIELDVALUE(ent, prog->fieldoffsets.pflags)->_float) & PFLAGS_FULLDYNAMIC)
-                                       ||
-                                       ((gamemode == GAME_TENEBRAE) && ((unsigned int)ent->fields.server->effects & (16 | 32)))
-                               )
-               )
-                       pitchsign = -1;
+               pitchsign = SV_GetPitchSign(ent);
                Matrix4x4_CreateFromQuakeEntity(out, ent->fields.server->origin[0], ent->fields.server->origin[1], ent->fields.server->origin[2], pitchsign * ent->fields.server->angles[0], ent->fields.server->angles[1], ent->fields.server->angles[2], scale);
        }
 }
 
 int SV_GetEntityLocalTagMatrix(prvm_edict_t *ent, int tagindex, matrix4x4_t *out)
 {
-       int modelindex;
-       int frame;
        dp_model_t *model;
-       if (tagindex >= 0
-        && (modelindex = (int)ent->fields.server->modelindex) >= 1 && modelindex < MAX_MODELS
-        && (model = sv.models[(int)ent->fields.server->modelindex])
-        && model->animscenes)
+       if (tagindex >= 0 && (model = SV_GetModelFromEdict(ent)) && model->animscenes)
        {
-               // if model has wrong frame, engine automatically switches to model first frame
-               frame = (int)ent->fields.server->frame;
-               if (frame < 0 || frame >= model->numframes)
-                       frame = 0;
-               return Mod_Alias_GetTagMatrix(model, model->animscenes[frame].firstframe, tagindex, out);
+               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;
@@ -2693,7 +2417,11 @@ int SV_GetTagMatrix (matrix4x4_t *out, prvm_edict_t *ent, int tagindex)
        if (modelindex <= 0 || modelindex >= MAX_MODELS)
                return 3;
 
-       model = sv.models[modelindex];
+       model = SV_GetModelByIndex(modelindex);
+
+       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);
 
        tagmatrix = identitymatrix;
        // DP_GFX_QUAKE3MODELTAGS, scan all chain and stop on unattached entity
@@ -2762,7 +2490,7 @@ static void VM_SV_gettagindex (void)
 {
        prvm_edict_t *ent;
        const char *tag_name;
-       int modelindex, tag_index;
+       int tag_index;
 
        VM_SAFEPARMCOUNT(2, VM_SV_gettagindex);
 
@@ -2771,25 +2499,24 @@ static void VM_SV_gettagindex (void)
 
        if (ent == prog->edicts)
        {
-               VM_Warning("gettagindex: can't affect world entity\n");
+               VM_Warning("VM_SV_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_SV_gettagindex(entity #%i): can't affect free entity\n", PRVM_NUM_FOR_EDICT(ent));
                return;
        }
 
-       modelindex = (int)ent->fields.server->modelindex;
        tag_index = 0;
-       if (modelindex <= 0 || modelindex >= MAX_MODELS)
-               Con_DPrintf("gettagindex(entity #%i): null or non-precached model\n", PRVM_NUM_FOR_EDICT(ent));
+       if (!SV_GetModelFromEdict(ent))
+               Con_DPrintf("VM_SV_gettagindex(entity #%i): null or non-precached model\n", PRVM_NUM_FOR_EDICT(ent));
        else
        {
                tag_index = SV_GetTagIndex(ent, tag_name);
                if (tag_index == 0)
-                       if(developer.integer >= 100)
-                               Con_Printf("gettagindex(entity #%i): tag \"%s\" not found\n", PRVM_NUM_FOR_EDICT(ent), tag_name);
+                       if(developer_extra.integer)
+                               Con_DPrintf("VM_SV_gettagindex(entity #%i): tag \"%s\" not found\n", PRVM_NUM_FOR_EDICT(ent), tag_name);
        }
        PRVM_G_FLOAT(OFS_RETURN) = tag_index;
 }
@@ -2806,6 +2533,7 @@ static void VM_SV_gettaginfo (void)
        int returncode;
        prvm_eval_t *val;
        vec3_t fo, le, up, trans;
+       const dp_model_t *model;
 
        VM_SAFEPARMCOUNT(2, VM_SV_gettaginfo);
 
@@ -2815,6 +2543,10 @@ static void VM_SV_gettaginfo (void)
        returncode = SV_GetTagMatrix(&tag_matrix, e, tagindex);
        Matrix4x4_ToVectors(&tag_matrix, prog->globals.server->v_forward, le, prog->globals.server->v_up, PRVM_G_VECTOR(OFS_RETURN));
        VectorScale(le, -1, prog->globals.server->v_right);
+       model = SV_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);
        SV_GetExtendedTagInfo(e, tagindex, &parentindex, &tagname, &tag_localmatrix);
        Matrix4x4_ToVectors(&tag_localmatrix, fo, le, up, trans);
 
@@ -2963,7 +2695,7 @@ static void VM_SV_setmodelindex (void)
        e->fields.server->model = PRVM_SetEngineString(sv.model_precache[i]);
        e->fields.server->modelindex = i;
 
-       mod = sv.models[i];
+       mod = SV_GetModelByIndex(i);
 
        if (mod)
        {
@@ -3078,6 +2810,315 @@ static void VM_SV_setpause(void) {
        MSG_WriteByte(&sv.reliable_datagram, sv.paused);
 }
 
+// #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_SV_skel_create(void)
+{
+       int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0);
+       dp_model_t *model = SV_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_SV_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 = SV_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_FromBonePose6s(&matrix, model->num_posescale, model->data_poses6s + 6 * (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_SV_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_SV_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_SV_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_SV_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_SV_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_SV_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_SV_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_SV_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_SV_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_SV_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_SV_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_SV_frameforname(void)
+{
+       int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0);
+       dp_model_t *model = SV_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_SV_frameduration(void)
+{
+       int modelindex = (int)PRVM_G_FLOAT(OFS_PARM0);
+       dp_model_t *model = SV_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;
+}
+
+
 prvm_builtin_t vm_sv_builtins[] = {
 NULL,                                                  // #0 NULL function (not callable) (QUAKE)
 VM_makevectors,                                        // #1 void(vector ang) makevectors (QUAKE)
@@ -3344,21 +3385,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_SV_skel_create,                             // #263 float(float modlindex) skel_create = #263; // (DP_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_SV_skel_build,                              // #264 float(float skel, entity ent, float modlindex, float retainfrac, float firstbone, float lastbone) skel_build = #264; // (DP_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_SV_skel_get_numbones,               // #265 float(float skel) skel_get_numbones = #265; // (DP_SKELETONOBJECTS) returns how many bones exist in the created skeleton
+VM_SV_skel_get_bonename,               // #266 string(float skel, float bonenum) skel_get_bonename = #266; // (DP_SKELETONOBJECTS) returns name of bone (as a tempstring)
+VM_SV_skel_get_boneparent,             // #267 float(float skel, float bonenum) skel_get_boneparent = #267; // (DP_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_SV_skel_find_bone,                  // #268 float(float skel, string tagname) skel_find_bone = #268; // (DP_SKELETONOBJECTS) get number of bone with specified name, 0 on failure, tagindex (bonenum+1) on success, same as using gettagindex on the modelindex
+VM_SV_skel_get_bonerel,                        // #269 vector(float skel, float bonenum) skel_get_bonerel = #269; // (DP_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_SV_skel_get_boneabs,                        // #270 vector(float skel, float bonenum) skel_get_boneabs = #270; // (DP_SKELETONOBJECTS) get matrix of bone in skeleton in model space - sets v_forward, v_right, v_up, returns origin (relative to entity)
+VM_SV_skel_set_bone,                   // #271 void(float skel, float bonenum, vector org) skel_set_bone = #271; // (DP_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_SV_skel_mul_bone,                   // #272 void(float skel, float bonenum, vector org) skel_mul_bone = #272; // (DP_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_SV_skel_mul_bones,                  // #273 void(float skel, float startbone, float endbone, vector org) skel_mul_bones = #273; // (DP_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_SV_skel_copybones,                  // #274 void(float skeldst, float skelsrc, float startbone, float endbone) skel_copybones = #274; // (DP_SKELETONOBJECTS) copy bone matrices (relative to their parents) from one skeleton to another, useful for copying a skeleton to a corpse
+VM_SV_skel_delete,                             // #275 void(float skel) skel_delete = #275; // (DP_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_SV_frameforname,                            // #276 float(float modlindex, string framename) frameforname = #276; // (DP_SKELETONOBJECTS) finds number of a specified frame in the animation, returns -1 if no match found
+VM_SV_frameduration,                   // #277 float(float modlindex, float framenum) frameduration = #277; // (DP_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
@@ -3517,12 +3558,12 @@ VM_SV_te_lightning3,                    // #430 void(entity own, vector start, vector end) te_lig
 VM_SV_te_beam,                                 // #431 void(entity own, vector start, vector end) te_beam (DP_TE_STANDARDEFFECTBUILTINS)
 VM_vectorvectors,                              // #432 void(vector dir) vectorvectors (DP_QC_VECTORVECTORS)
 VM_SV_te_plasmaburn,                   // #433 void(vector org) te_plasmaburn (DP_TE_PLASMABURN)
-VM_SV_getsurfacenumpoints,             // #434 float(entity e, float s) getsurfacenumpoints (DP_QC_GETSURFACE)
-VM_SV_getsurfacepoint,                 // #435 vector(entity e, float s, float n) getsurfacepoint (DP_QC_GETSURFACE)
-VM_SV_getsurfacenormal,                        // #436 vector(entity e, float s) getsurfacenormal (DP_QC_GETSURFACE)
-VM_SV_getsurfacetexture,               // #437 string(entity e, float s) getsurfacetexture (DP_QC_GETSURFACE)
-VM_SV_getsurfacenearpoint,             // #438 float(entity e, vector p) getsurfacenearpoint (DP_QC_GETSURFACE)
-VM_SV_getsurfaceclippedpoint,  // #439 vector(entity e, float s, vector p) getsurfaceclippedpoint (DP_QC_GETSURFACE)
+VM_getsurfacenumpoints,                // #434 float(entity e, float s) getsurfacenumpoints (DP_QC_GETSURFACE)
+VM_getsurfacepoint,                    // #435 vector(entity e, float s, float n) getsurfacepoint (DP_QC_GETSURFACE)
+VM_getsurfacenormal,                   // #436 vector(entity e, float s) getsurfacenormal (DP_QC_GETSURFACE)
+VM_getsurfacetexture,          // #437 string(entity e, float s) getsurfacetexture (DP_QC_GETSURFACE)
+VM_getsurfacenearpoint,                // #438 float(entity e, vector p) getsurfacenearpoint (DP_QC_GETSURFACE)
+VM_getsurfaceclippedpoint,     // #439 vector(entity e, float s, vector p) getsurfaceclippedpoint (DP_QC_GETSURFACE)
 VM_SV_clientcommand,                   // #440 void(entity e, string s) clientcommand (KRIMZON_SV_PARSECLIENTCOMMAND)
 VM_tokenize,                                   // #441 float(string s) tokenize (KRIMZON_SV_PARSECLIENTCOMMAND)
 VM_argv,                                               // #442 string(float n) argv (KRIMZON_SV_PARSECLIENTCOMMAND)
@@ -3569,7 +3610,7 @@ VM_cvar_defstring,                                // #482 string(string s) cvar_defstring (DP_QC_CVAR_DEFSTR
 VM_SV_pointsound,                              // #483 void(vector origin, string sample, float volume, float attenuation) (DP_SV_POINTSOUND)
 VM_strreplace,                                 // #484 string(string search, string replace, string subject) strreplace (DP_QC_STRREPLACE)
 VM_strireplace,                                        // #485 string(string search, string replace, string subject) strireplace (DP_QC_STRREPLACE)
-VM_SV_getsurfacepointattribute,// #486 vector(entity e, float s, float n, float a) getsurfacepointattribute = #486;
+VM_getsurfacepointattribute,// #486 vector(entity e, float s, float n, float a) getsurfacepointattribute = #486;
 NULL,                                                  // #487
 NULL,                                                  // #488
 NULL,                                                  // #489
@@ -3612,10 +3653,10 @@ NULL,                                                   // #525
 NULL,                                                  // #526
 NULL,                                                  // #527
 NULL,                                                  // #528
-NULL,                                                  // #529
-NULL,                                                  // #530
+VM_loadfromdata,                               // #529
+VM_loadfromfile,                               // #530
 VM_SV_setpause,                                        // #531 void(float pause) setpause = #531;
-NULL,                                                  // #532
+VM_log,                                                        // #532
 NULL,                                                  // #533
 NULL,                                                  // #534
 NULL,                                                  // #535
@@ -3688,15 +3729,15 @@ NULL,                                                   // #601
 NULL,                                                  // #602
 NULL,                                                  // #603
 NULL,                                                  // #604
-NULL,                                                  // #605
-NULL,                                                  // #606
-NULL,                                                  // #607
+VM_callfunction,                               // #605
+VM_writetofile,                                        // #606
+VM_isfunction,                                 // #607
 NULL,                                                  // #608
 NULL,                                                  // #609
 NULL,                                                  // #610
 NULL,                                                  // #611
 NULL,                                                  // #612
-NULL,                                                  // #613
+VM_parseentitydata,                            // #613
 NULL,                                                  // #614
 NULL,                                                  // #615
 NULL,                                                  // #616
@@ -3707,8 +3748,13 @@ NULL,                                                    // #620
 NULL,                                                  // #621
 NULL,                                                  // #622
 NULL,                                                  // #623
-VM_getextresponse,                             // #624 string getextresponse(void)
+VM_SV_getextresponse,                  // #624 string getextresponse(void)
 NULL,                                                  // #625
+NULL,                                                  // #626
+VM_sprintf,                     // #627 string sprintf(string format, ...)
+VM_getsurfacenumtriangles,             // #628 float(entity e, float s) getsurfacenumpoints (DP_QC_GETSURFACETRIANGLE)
+VM_getsurfacetriangle,                 // #629 vector(entity e, float s, float n) getsurfacepoint (DP_QC_GETSURFACETRIANGLE)
+NULL,                                                  // #630
 };
 
 const int vm_sv_numbuiltins = sizeof(vm_sv_builtins) / sizeof(prvm_builtin_t);
@@ -3720,6 +3766,7 @@ void VM_SV_Cmd_Init(void)
 
 void VM_SV_Cmd_Reset(void)
 {
+       World_End(&sv.world);
        if(prog->funcoffsets.SV_Shutdown)
        {
                func_t s = prog->funcoffsets.SV_Shutdown;