]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - prvm_cmds.c
make getsurface* functions shared between VMs
[xonotic/darkplaces.git] / prvm_cmds.c
index 96a84546eaff7f49df41eda03cb93caefd63bc0b..145efd8a853cb4c9f8624e0876c97845f4d8f41d 100644 (file)
 #include "libcurl.h"
 #include <time.h>
 
+#include "cl_collision.h"
+#include "clvm_cmds.h"
+#include "ft2.h"
+
 extern cvar_t prvm_backtraceforwarnings;
 
 // LordHavoc: changed this to NOT use a return statement, so that it can be used in functions that must return a value
@@ -23,7 +27,7 @@ void VM_Warning(const char *fmt, ...)
        dpvsnprintf(msg,sizeof(msg),fmt,argptr);
        va_end(argptr);
 
-       Con_Print(msg);
+       Con_DPrint(msg);
 
        // TODO: either add a cvar/cmd to control the state dumping or replace some of the calls with Con_Printf [9/13/2006 Black]
        if(prvm_backtraceforwarnings.integer && recursive != realtime) // NOTE: this compares to the time, just in case if PRVM_PrintState causes a Host_Error and keeps recursive set
@@ -49,6 +53,189 @@ void VM_CheckEmptyString (const char *s)
                PRVM_ERROR ("%s: Bad string", PRVM_NAME);
 }
 
+void VM_GenerateFrameGroupBlend(framegroupblend_t *framegroupblend, const prvm_edict_t *ed)
+{
+       prvm_eval_t *val;
+       // self.frame is the interpolation target (new frame)
+       // self.frame1time is the animation base time for the interpolation target
+       // self.frame2 is the interpolation start (previous frame)
+       // self.frame2time is the animation base time for the interpolation start
+       // self.lerpfrac is the interpolation strength for self.frame2
+       // self.lerpfrac3 is the interpolation strength for self.frame3
+       // self.lerpfrac4 is the interpolation strength for self.frame4
+       // pitch angle on a player model where the animator set up 5 sets of
+       // animations and the csqc simply lerps between sets)
+       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame))) framegroupblend[0].frame = (int) val->_float;
+       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame2))) framegroupblend[1].frame = (int) val->_float;
+       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame3))) framegroupblend[2].frame = (int) val->_float;
+       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame4))) framegroupblend[3].frame = (int) val->_float;
+       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame1time))) framegroupblend[0].start = val->_float;
+       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame2time))) framegroupblend[1].start = val->_float;
+       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame3time))) framegroupblend[2].start = val->_float;
+       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame4time))) framegroupblend[3].start = val->_float;
+       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.lerpfrac))) framegroupblend[1].lerp = val->_float;
+       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.lerpfrac3))) framegroupblend[2].lerp = val->_float;
+       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.lerpfrac4))) framegroupblend[3].lerp = val->_float;
+       // assume that the (missing) lerpfrac1 is whatever remains after lerpfrac2+lerpfrac3+lerpfrac4 are summed
+       framegroupblend[0].lerp = 1 - framegroupblend[1].lerp - framegroupblend[2].lerp - framegroupblend[3].lerp;
+}
+
+// LordHavoc: quite tempting to break apart this function to reuse the
+//            duplicated code, but I suspect it is better for performance
+//            this way
+void VM_FrameBlendFromFrameGroupBlend(frameblend_t *frameblend, const framegroupblend_t *framegroupblend, const dp_model_t *model)
+{
+       int sub2, numframes, f, i, k;
+       int isfirstframegroup = true;
+       int nolerp;
+       double sublerp, lerp, d;
+       const animscene_t *scene;
+       const framegroupblend_t *g;
+       frameblend_t *blend = frameblend;
+
+       memset(blend, 0, MAX_FRAMEBLENDS * sizeof(*blend));
+
+       if (!model || !model->surfmesh.isanimated)
+       {
+               blend[0].lerp = 1;
+               return;
+       }
+
+       nolerp = (model->type == mod_sprite) ? !r_lerpsprites.integer : !r_lerpmodels.integer;
+       numframes = model->numframes;
+       for (k = 0, g = framegroupblend;k < MAX_FRAMEGROUPBLENDS;k++, g++)
+       {
+               f = g->frame;
+               if ((unsigned int)f >= (unsigned int)numframes)
+               {
+                       Con_DPrintf("VM_FrameBlendFromFrameGroupBlend: no such frame %d in model %s\n", f, model->name);
+                       f = 0;
+               }
+               d = lerp = g->lerp;
+               if (lerp <= 0)
+                       continue;
+               if (nolerp)
+               {
+                       if (isfirstframegroup)
+                       {
+                               d = lerp = 1;
+                               isfirstframegroup = false;
+                       }
+                       else
+                               continue;
+               }
+               if (model->animscenes)
+               {
+                       scene = model->animscenes + f;
+                       f = scene->firstframe;
+                       if (scene->framecount > 1)
+                       {
+                               // this code path is only used on .zym models and torches
+                               sublerp = scene->framerate * (cl.time - g->start);
+                               f = (int) floor(sublerp);
+                               sublerp -= f;
+                               sub2 = f + 1;
+                               if (sublerp < (1.0 / 65536.0f))
+                                       sublerp = 0;
+                               if (sublerp > (65535.0f / 65536.0f))
+                                       sublerp = 1;
+                               if (nolerp)
+                                       sublerp = 0;
+                               if (scene->loop)
+                               {
+                                       f = (f % scene->framecount);
+                                       sub2 = (sub2 % scene->framecount);
+                               }
+                               f = bound(0, f, (scene->framecount - 1)) + scene->firstframe;
+                               sub2 = bound(0, sub2, (scene->framecount - 1)) + scene->firstframe;
+                               d = sublerp * lerp;
+                               // two framelerps produced from one animation
+                               if (d > 0)
+                               {
+                                       for (i = 0;i < MAX_FRAMEBLENDS;i++)
+                                       {
+                                               if (blend[i].lerp <= 0 || blend[i].subframe == sub2)
+                                               {
+                                                       blend[i].subframe = sub2;
+                                                       blend[i].lerp += d;
+                                                       break;
+                                               }
+                                       }
+                               }
+                               d = (1 - sublerp) * lerp;
+                       }
+               }
+               if (d > 0)
+               {
+                       for (i = 0;i < MAX_FRAMEBLENDS;i++)
+                       {
+                               if (blend[i].lerp <= 0 || blend[i].subframe == f)
+                               {
+                                       blend[i].subframe = f;
+                                       blend[i].lerp += d;
+                                       break;
+                               }
+                       }
+               }
+       }
+}
+
+void VM_UpdateEdictSkeleton(prvm_edict_t *ed, const dp_model_t *edmodel, const frameblend_t *frameblend)
+{
+       if (ed->priv.server->skeleton.model != edmodel)
+       {
+               VM_RemoveEdictSkeleton(ed);
+               ed->priv.server->skeleton.model = edmodel;
+       }
+       if (!ed->priv.server->skeleton.relativetransforms && ed->priv.server->skeleton.model && ed->priv.server->skeleton.model->num_bones)
+               ed->priv.server->skeleton.relativetransforms = Mem_Alloc(prog->progs_mempool, ed->priv.server->skeleton.model->num_bones * sizeof(matrix4x4_t));
+       if (ed->priv.server->skeleton.relativetransforms)
+       {
+               int skeletonindex = 0;
+               skeleton_t *skeleton;
+               prvm_eval_t *val;
+               if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.skeletonindex))) skeletonindex = (int)val->_float;
+               if (skeletonindex > 0 && skeletonindex < MAX_EDICTS && (skeleton = prog->skeletons[skeletonindex]) && skeleton->model->num_bones == ed->priv.server->skeleton.model->num_bones)
+               {
+                       // custom skeleton controlled by the game (FTE_CSQC_SKELETONOBJECTS)
+                       memcpy(ed->priv.server->skeleton.relativetransforms, skeleton->relativetransforms, ed->priv.server->skeleton.model->num_bones * sizeof(matrix4x4_t));
+               }
+               else
+               {
+                       // generated skeleton from frame animation
+                       int blendindex;
+                       int bonenum;
+                       int numbones = ed->priv.server->skeleton.model->num_bones;
+                       const short *framebones6s;
+                       float lerp;
+                       float scale = ed->priv.server->skeleton.model->num_posescale;
+                       matrix4x4_t *relativetransforms = ed->priv.server->skeleton.relativetransforms;
+                       matrix4x4_t matrix;
+                       memset(relativetransforms, 0, numbones * sizeof(matrix4x4_t));
+                       for (blendindex = 0;blendindex < MAX_FRAMEBLENDS && frameblend[blendindex].lerp > 0;blendindex++)
+                       {
+                               lerp = frameblend[blendindex].lerp;
+                               framebones6s = ed->priv.server->skeleton.model->data_poses6s + 6 * frameblend[blendindex].subframe * numbones;
+                               for (bonenum = 0;bonenum < numbones;bonenum++)
+                               {
+                                       Matrix4x4_FromBonePose6s(&matrix, scale, framebones6s + 6 * bonenum);
+                                       Matrix4x4_Accumulate(&ed->priv.server->skeleton.relativetransforms[bonenum], &matrix, lerp);
+                               }
+                       }
+               }
+       }
+}
+
+void VM_RemoveEdictSkeleton(prvm_edict_t *ed)
+{
+       if (ed->priv.server->skeleton.relativetransforms)
+               Mem_Free(ed->priv.server->skeleton.relativetransforms);
+       memset(&ed->priv.server->skeleton, 0, sizeof(ed->priv.server->skeleton));
+}
+
+
+
+
 //============================================================================
 //BUILT-IN FUNCTIONS
 
@@ -559,15 +746,12 @@ void VM_dprint (void)
 {
        char string[VM_STRINGTEMP_LENGTH];
        VM_SAFEPARMCOUNTRANGE(1, 8, VM_dprint);
-       if (developer.integer)
-       {
-               VM_VarString(0, string, sizeof(string));
+       VM_VarString(0, string, sizeof(string));
 #if 1
-               Con_Printf("%s", string);
+       Con_DPrintf("%s", string);
 #else
-               Con_Printf("%s: %s", PRVM_NAME, string);
+       Con_DPrintf("%s: %s", PRVM_NAME, string);
 #endif
-       }
 }
 
 /*
@@ -789,12 +973,12 @@ void VM_remove (void)
        ed = PRVM_G_EDICT(OFS_PARM0);
        if( PRVM_NUM_FOR_EDICT(ed) <= prog->reserved_edicts )
        {
-               if (developer.integer >= 1)
+               if (developer.integer)
                        VM_Warning( "VM_remove: tried to remove the null entity or a reserved entity!\n" );
        }
        else if( ed->priv.required->free )
        {
-               if (developer.integer >= 1)
+               if (developer.integer)
                        VM_Warning( "VM_remove: tried to remove an already freed entity!\n" );
        }
        else
@@ -1088,7 +1272,7 @@ void VM_precache_sound (void)
 
        s = PRVM_G_STRING(OFS_PARM0);
        PRVM_G_INT(OFS_RETURN) = PRVM_G_INT(OFS_PARM0);
-       VM_CheckEmptyString(s);
+       //VM_CheckEmptyString(s);
 
        if(snd_initialized.integer && !S_PrecacheSound(s, true, true))
        {
@@ -1568,6 +1752,12 @@ void VM_pow (void)
        PRVM_G_FLOAT(OFS_RETURN) = pow(PRVM_G_FLOAT(OFS_PARM0), PRVM_G_FLOAT(OFS_PARM1));
 }
 
+void VM_log (void)
+{
+       VM_SAFEPARMCOUNT(1,VM_log);
+       PRVM_G_FLOAT(OFS_RETURN) = log(PRVM_G_FLOAT(OFS_PARM0));
+}
+
 void VM_Files_Init(void)
 {
        int i;
@@ -1654,14 +1844,14 @@ void VM_fopen(void)
        if (prog->openfiles[filenum] == NULL)
        {
                PRVM_G_FLOAT(OFS_RETURN) = -1;
-               if (developer.integer >= 100)
+               if (developer_extra.integer)
                        VM_Warning("VM_fopen: %s: %s mode %s failed\n", PRVM_NAME, filename, modestring);
        }
        else
        {
                PRVM_G_FLOAT(OFS_RETURN) = filenum;
-               if (developer.integer >= 100)
-                       Con_Printf("VM_fopen: %s: %s mode %s opened as #%i\n", PRVM_NAME, filename, modestring, filenum);
+               if (developer_extra.integer)
+                       Con_DPrintf("VM_fopen: %s: %s mode %s opened as #%i\n", PRVM_NAME, filename, modestring, filenum);
                prog->openfiles_origin[filenum] = PRVM_AllocationOrigin();
        }
 }
@@ -1695,8 +1885,8 @@ void VM_fclose(void)
        prog->openfiles[filenum] = NULL;
        if(prog->openfiles_origin[filenum])
                PRVM_Free((char *)prog->openfiles_origin[filenum]);
-       if (developer.integer >= 100)
-               Con_Printf("VM_fclose: %s: #%i closed\n", PRVM_NAME, filenum);
+       if (developer_extra.integer)
+               Con_DPrintf("VM_fclose: %s: #%i closed\n", PRVM_NAME, filenum);
 }
 
 /*
@@ -1746,8 +1936,8 @@ void VM_fgets(void)
                if (c != '\n')
                        FS_UnGetc(prog->openfiles[filenum], (unsigned char)c);
        }
-       if (developer.integer >= 100)
-               Con_Printf("fgets: %s: %s\n", PRVM_NAME, string);
+       if (developer_extra.integer)
+               Con_DPrintf("fgets: %s: %s\n", PRVM_NAME, string);
        if (c >= 0 || end)
                PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(string);
 }
@@ -1782,8 +1972,8 @@ void VM_fputs(void)
        VM_VarString(1, string, sizeof(string));
        if ((stringlength = (int)strlen(string)))
                FS_Write(prog->openfiles[filenum], string, stringlength);
-       if (developer.integer >= 100)
-               Con_Printf("fputs: %s: %s\n", PRVM_NAME, string);
+       if (developer_extra.integer)
+               Con_DPrintf("fputs: %s: %s\n", PRVM_NAME, string);
 }
 
 /*
@@ -1978,7 +2168,8 @@ void VM_strlen(void)
 {
        VM_SAFEPARMCOUNT(1,VM_strlen);
 
-       PRVM_G_FLOAT(OFS_RETURN) = strlen(PRVM_G_STRING(OFS_PARM0));
+       //PRVM_G_FLOAT(OFS_RETURN) = strlen(PRVM_G_STRING(OFS_PARM0));
+       PRVM_G_FLOAT(OFS_RETURN) = u8_strlen(PRVM_G_STRING(OFS_PARM0));
 }
 
 // DRESK - Decolorized String
@@ -2021,7 +2212,8 @@ void VM_strlennocol(void)
 
        szString = PRVM_G_STRING(OFS_PARM0);
 
-       nCnt = COM_StringLengthNoColors(szString, 0, NULL);
+       //nCnt = COM_StringLengthNoColors(szString, 0, NULL);
+       nCnt = u8_COM_StringLengthNoColors(szString, 0, NULL);
 
        PRVM_G_FLOAT(OFS_RETURN) = nCnt;
 }
@@ -2101,12 +2293,15 @@ string  substring(string s, float start, float length)
 // returns a section of a string as a tempstring
 void VM_substring(void)
 {
-       int start, length, slength, maxlen;
+       int start, length;
+       int u_slength = 0, u_start;
+       size_t u_length;
        const char *s;
        char string[VM_STRINGTEMP_LENGTH];
 
        VM_SAFEPARMCOUNT(3,VM_substring);
 
+       /*
        s = PRVM_G_STRING(OFS_PARM0);
        start = (int)PRVM_G_FLOAT(OFS_PARM1);
        length = (int)PRVM_G_FLOAT(OFS_PARM2);
@@ -2124,6 +2319,40 @@ void VM_substring(void)
        memcpy(string, s + start, length);
        string[length] = 0;
        PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(string);
+       */
+       
+       s = PRVM_G_STRING(OFS_PARM0);
+       start = (int)PRVM_G_FLOAT(OFS_PARM1);
+       length = (int)PRVM_G_FLOAT(OFS_PARM2);
+
+       if (start < 0) // FTE_STRINGS feature
+       {
+               u_slength = u8_strlen(s);
+               start += u_slength;
+               start = bound(0, start, u_slength);
+       }
+
+       if (length < 0) // FTE_STRINGS feature
+       {
+               if (!u_slength) // it's not calculated when it's not needed above
+                       u_slength = u8_strlen(s);
+               length += u_slength - start + 1;
+       }
+               
+       // positive start, positive length
+       u_start = u8_byteofs(s, start, NULL);
+       if (u_start < 0)
+       {
+               PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString("");
+               return;
+       }
+       u_length = u8_bytelen(s + u_start, length);
+       if (u_length >= sizeof(string)-1)
+               u_length = sizeof(string)-1;
+       
+       memcpy(string, s + u_start, u_length);
+       string[u_length] = 0;
+       PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(string);
 }
 
 /*
@@ -2906,6 +3135,7 @@ string    chr(float ascii)
 */
 void VM_chr(void)
 {
+       /*
        char tmp[2];
        VM_SAFEPARMCOUNT(1, VM_chr);
 
@@ -2913,6 +3143,15 @@ void VM_chr(void)
        tmp[1] = 0;
 
        PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(tmp);
+       */
+       
+       char tmp[8];
+       int len;
+       VM_SAFEPARMCOUNT(1, VM_chr);
+
+       len = u8_fromchar((Uchar)PRVM_G_FLOAT(OFS_PARM0), tmp, sizeof(tmp));
+       tmp[len] = 0;
+       PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(tmp);
 }
 
 //=============================================================================
@@ -2974,7 +3213,22 @@ void VM_freepic(void)
        Draw_FreePic(s);
 }
 
-dp_font_t *getdrawfont()
+void getdrawfontscale(float *sx, float *sy)
+{
+       vec3_t v;
+       *sx = *sy = 1;
+       if(prog->globaloffsets.drawfontscale >= 0)
+       {
+               VectorCopy(PRVM_G_VECTOR(prog->globaloffsets.drawfontscale), v);
+               if(VectorLength2(v) > 0)
+               {
+                       *sx = v[0];
+                       *sy = v[1];
+               }
+       }
+}
+
+dp_font_t *getdrawfont(void)
 {
        if(prog->globaloffsets.drawfont >= 0)
        {
@@ -2999,6 +3253,7 @@ void VM_drawcharacter(void)
        float *pos,*scale,*rgb;
        char   character;
        int flag;
+       float sx, sy;
        VM_SAFEPARMCOUNT(6,VM_drawcharacter);
 
        character = (char) PRVM_G_FLOAT(OFS_PARM1);
@@ -3031,7 +3286,8 @@ void VM_drawcharacter(void)
                return;
        }
 
-       DrawQ_String_Font(pos[0], pos[1], &character, 1, scale[0], scale[1], rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM4), flag, NULL, true, getdrawfont());
+       getdrawfontscale(&sx, &sy);
+       DrawQ_String_Scale(pos[0], pos[1], &character, 1, scale[0], scale[1], sx, sy, rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM4), flag, NULL, true, getdrawfont());
        PRVM_G_FLOAT(OFS_RETURN) = 1;
 }
 
@@ -3047,6 +3303,7 @@ void VM_drawstring(void)
        float *pos,*scale,*rgb;
        const char  *string;
        int flag;
+       float sx, sy;
        VM_SAFEPARMCOUNT(6,VM_drawstring);
 
        string = PRVM_G_STRING(OFS_PARM1);
@@ -3072,7 +3329,9 @@ void VM_drawstring(void)
        if(pos[2] || scale[2])
                Con_Printf("VM_drawstring: z value%s from %s discarded\n",(pos[2] && scale[2]) ? "s" : " ",((pos[2] && scale[2]) ? "pos and scale" : (pos[2] ? "pos" : "scale")));
 
-       DrawQ_String_Font(pos[0], pos[1], string, 0, scale[0], scale[1], rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM4), flag, NULL, true, getdrawfont());
+       getdrawfontscale(&sx, &sy);
+       DrawQ_String_Scale(pos[0], pos[1], string, 0, scale[0], scale[1], sx, sy, rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM4), flag, NULL, true, getdrawfont());
+       //Font_DrawString(pos[0], pos[1], string, 0, scale[0], scale[1], rgb[0], rgb[1], rgb[2], PRVM_G_FLOAT(OFS_PARM4), flag, NULL, true);
        PRVM_G_FLOAT(OFS_RETURN) = 1;
 }
 
@@ -3088,6 +3347,7 @@ void VM_drawcolorcodedstring(void)
        float *pos,*scale;
        const char  *string;
        int flag,color;
+       float sx, sy;
        VM_SAFEPARMCOUNT(5,VM_drawstring);
 
        string = PRVM_G_STRING(OFS_PARM1);
@@ -3113,7 +3373,8 @@ void VM_drawcolorcodedstring(void)
                Con_Printf("VM_drawcolorcodedstring: z value%s from %s discarded\n",(pos[2] && scale[2]) ? "s" : " ",((pos[2] && scale[2]) ? "pos and scale" : (pos[2] ? "pos" : "scale")));
 
        color = -1;
-       DrawQ_String_Font(pos[0], pos[1], string, 0, scale[0], scale[1], 1, 1, 1, PRVM_G_FLOAT(OFS_PARM3), flag, NULL, false, getdrawfont());
+       getdrawfontscale(&sx, &sy);
+       DrawQ_String_Scale(pos[0], pos[1], string, 0, scale[0], scale[1], sx, sy, 1, 1, 1, PRVM_G_FLOAT(OFS_PARM3), flag, NULL, false, getdrawfont());
        PRVM_G_FLOAT(OFS_RETURN) = 1;
 }
 /*
@@ -3126,10 +3387,39 @@ float   stringwidth(string text, float allowColorCodes, float size)
 void VM_stringwidth(void)
 {
        const char  *string;
-       float sz, mult; // sz is intended font size so we can later add freetype support, mult is font size multiplier in pixels per character cell
+       float *szv;
+       float mult; // sz is intended font size so we can later add freetype support, mult is font size multiplier in pixels per character cell
        int colors;
+       float sx, sy;
+       size_t maxlen = 0;
        VM_SAFEPARMCOUNTRANGE(2,3,VM_drawstring);
 
+       getdrawfontscale(&sx, &sy);
+       if(prog->argc == 3)
+       {
+               szv = PRVM_G_VECTOR(OFS_PARM2);
+               mult = 1;
+       }
+       else
+       {
+               // we want the width for 8x8 font size, divided by 8
+               static float defsize[] = {8, 8};
+               szv = defsize;
+               mult = 0.125;
+               // to make sure snapping is turned off, ALWAYS use a nontrivial scale in this case
+               if(sx >= 0.9 && sx <= 1.1)
+               {
+                       mult *= 2;
+                       sx /= 2;
+                       sy /= 2;
+               }
+       }
+
+       string = PRVM_G_STRING(OFS_PARM0);
+       colors = (int)PRVM_G_FLOAT(OFS_PARM1);
+
+       PRVM_G_FLOAT(OFS_RETURN) = DrawQ_TextWidth_UntilWidth_TrackColors_Scale(string, &maxlen, szv[0], szv[1], sx, sy, NULL, !colors, getdrawfont(), 1000000000) * mult;
+/*
        if(prog->argc == 3)
        {
                mult = sz = PRVM_G_FLOAT(OFS_PARM2);
@@ -3143,7 +3433,9 @@ void VM_stringwidth(void)
        string = PRVM_G_STRING(OFS_PARM0);
        colors = (int)PRVM_G_FLOAT(OFS_PARM1);
 
-       PRVM_G_FLOAT(OFS_RETURN) = DrawQ_TextWidth_Font(string, 0, !colors, getdrawfont()) * mult; // 1x1 characters, don't actually draw
+       PRVM_G_FLOAT(OFS_RETURN) = DrawQ_TextWidth(string, 0, !colors, getdrawfont()) * mult; // 1x1 characters, don't actually draw
+*/
+
 }
 /*
 =========
@@ -3877,7 +4169,7 @@ void VM_bitshift (void)
        int n1, n2;
        VM_SAFEPARMCOUNT(2, VM_bitshift);
 
-       n1 = (int)fabs((int)PRVM_G_FLOAT(OFS_PARM0));
+       n1 = (int)fabs((float)((int)PRVM_G_FLOAT(OFS_PARM0)));
        n2 = (int)PRVM_G_FLOAT(OFS_PARM1);
        if(!n1)
                PRVM_G_FLOAT(OFS_RETURN) = n1;
@@ -4740,6 +5032,7 @@ void VM_strstrofs (void)
        instr = PRVM_G_STRING(OFS_PARM0);
        match = PRVM_G_STRING(OFS_PARM1);
        firstofs = (prog->argc > 2)?(int)PRVM_G_FLOAT(OFS_PARM2):0;
+       firstofs = u8_bytelen(instr, firstofs);
 
        if (firstofs && (firstofs < 0 || firstofs > (int)strlen(instr)))
        {
@@ -4758,10 +5051,20 @@ void VM_strstrofs (void)
 void VM_str2chr (void)
 {
        const char *s;
+       Uchar ch;
+       int index;
        VM_SAFEPARMCOUNT(2, VM_str2chr);
        s = PRVM_G_STRING(OFS_PARM0);
-       if((unsigned)PRVM_G_FLOAT(OFS_PARM1) < strlen(s))
-               PRVM_G_FLOAT(OFS_RETURN) = (unsigned char)s[(unsigned)PRVM_G_FLOAT(OFS_PARM1)];
+       index = u8_bytelen(s, (int)PRVM_G_FLOAT(OFS_PARM1));
+
+       if((unsigned)index < strlen(s))
+       {
+               if (utf8_enable.integer)
+                       ch = u8_getchar_noendptr(s + index);
+               else
+                       ch = (unsigned char)s[index];
+               PRVM_G_FLOAT(OFS_RETURN) = ch;
+       }
        else
                PRVM_G_FLOAT(OFS_RETURN) = 0;
 }
@@ -4769,6 +5072,7 @@ void VM_str2chr (void)
 //#223 string(float c, ...) chr2str (FTE_STRINGS)
 void VM_chr2str (void)
 {
+       /*
        char    t[9];
        int             i;
        VM_SAFEPARMCOUNTRANGE(0, 8, VM_chr2str);
@@ -4776,6 +5080,15 @@ void VM_chr2str (void)
                t[i] = (unsigned char)PRVM_G_FLOAT(OFS_PARM0+i*3);
        t[i] = 0;
        PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(t);
+       */
+       char t[9 * 4 + 1];
+       int i;
+       size_t len = 0;
+       VM_SAFEPARMCOUNTRANGE(0, 8, VM_chr2str);
+       for(i = 0; i < prog->argc && len < sizeof(t)-1; ++i)
+               len += u8_fromchar((Uchar)PRVM_G_FLOAT(OFS_PARM0+i*3), t + len, sizeof(t)-1);
+       t[len] = 0;
+       PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(t);
 }
 
 static int chrconv_number(int i, int base, int conv)
@@ -5044,6 +5357,38 @@ void VM_SetTraceGlobals(const trace_t *trace)
                val->string = trace->hittexture ? PRVM_SetTempString(trace->hittexture->name) : 0;
 }
 
+void VM_ClearTraceGlobals(void)
+{
+       // clean up all trace globals when leaving the VM (anti-triggerbot safeguard)
+       prvm_eval_t *val;
+       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.trace_allsolid)))
+               val->_float = 0;
+       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.trace_startsolid)))
+               val->_float = 0;
+       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.trace_fraction)))
+               val->_float = 0;
+       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.trace_inwater)))
+               val->_float = 0;
+       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.trace_inopen)))
+               val->_float = 0;
+       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.trace_endpos)))
+               VectorClear(val->vector);
+       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.trace_plane_normal)))
+               VectorClear(val->vector);
+       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.trace_plane_dist)))
+               val->_float = 0;
+       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.trace_ent)))
+               val->edict = PRVM_EDICT_TO_PROG(prog->edicts);
+       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.trace_dpstartcontents)))
+               val->_float = 0;
+       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.trace_dphitcontents)))
+               val->_float = 0;
+       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.trace_dphitq3surfaceflags)))
+               val->_float = 0;
+       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.trace_dphittexturename)))
+               val->string = 0;
+}
+
 //=============
 
 void VM_Cmd_Init(void)
@@ -5249,17 +5594,712 @@ void VM_netaddress_resolve (void)
 }
 
 //string(void) getextresponse = #624; // returns the next extResponse packet that was sent to this client
-void VM_getextresponse (void)
+void VM_CL_getextresponse (void)
 {
        VM_SAFEPARMCOUNT(0,VM_argv);
 
-       if (net_extresponse_count <= 0)
+       if (cl_net_extresponse_count <= 0)
                PRVM_G_INT(OFS_RETURN) = OFS_NULL;
        else
        {
                int first;
-               --net_extresponse_count;
-               first = (net_extresponse_last + NET_EXTRESPONSE_MAX - net_extresponse_count) % NET_EXTRESPONSE_MAX;
-               PRVM_G_INT(OFS_RETURN) = PRVM_SetEngineString(net_extresponse[first]);
+               --cl_net_extresponse_count;
+               first = (cl_net_extresponse_last + NET_EXTRESPONSE_MAX - cl_net_extresponse_count) % NET_EXTRESPONSE_MAX;
+               PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(cl_net_extresponse[first]);
        }
 }
+
+void VM_SV_getextresponse (void)
+{
+       VM_SAFEPARMCOUNT(0,VM_argv);
+
+       if (sv_net_extresponse_count <= 0)
+               PRVM_G_INT(OFS_RETURN) = OFS_NULL;
+       else
+       {
+               int first;
+               --sv_net_extresponse_count;
+               first = (sv_net_extresponse_last + NET_EXTRESPONSE_MAX - sv_net_extresponse_count) % NET_EXTRESPONSE_MAX;
+               PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(sv_net_extresponse[first]);
+       }
+}
+
+/*
+=========
+VM_M_callfunction
+
+       callfunction(...,string function_name)
+Extension: pass
+=========
+*/
+mfunction_t *PRVM_ED_FindFunction (const char *name);
+void VM_callfunction(void)
+{
+       mfunction_t *func;
+       const char *s;
+
+       VM_SAFEPARMCOUNTRANGE(1, 8, VM_callfunction);
+
+       s = PRVM_G_STRING(OFS_PARM0+(prog->argc - 1)*3);
+
+       VM_CheckEmptyString(s);
+
+       func = PRVM_ED_FindFunction(s);
+
+       if(!func)
+               PRVM_ERROR("VM_callfunciton: function %s not found !", s);
+       else if (func->first_statement < 0)
+       {
+               // negative statements are built in functions
+               int builtinnumber = -func->first_statement;
+               prog->xfunction->builtinsprofile++;
+               if (builtinnumber < prog->numbuiltins && prog->builtins[builtinnumber])
+                       prog->builtins[builtinnumber]();
+               else
+                       PRVM_ERROR("No such builtin #%i in %s; most likely cause: outdated engine build. Try updating!", builtinnumber, PRVM_NAME);
+       }
+       else if(func - prog->functions > 0)
+       {
+               prog->argc--;
+               PRVM_ExecuteProgram(func - prog->functions,"");
+               prog->argc++;
+       }
+}
+
+/*
+=========
+VM_isfunction
+
+float  isfunction(string function_name)
+=========
+*/
+mfunction_t *PRVM_ED_FindFunction (const char *name);
+void VM_isfunction(void)
+{
+       mfunction_t *func;
+       const char *s;
+
+       VM_SAFEPARMCOUNT(1, VM_isfunction);
+
+       s = PRVM_G_STRING(OFS_PARM0);
+
+       VM_CheckEmptyString(s);
+
+       func = PRVM_ED_FindFunction(s);
+
+       if(!func)
+               PRVM_G_FLOAT(OFS_RETURN) = false;
+       else
+               PRVM_G_FLOAT(OFS_RETURN) = true;
+}
+
+/*
+=========
+VM_sprintf
+
+string sprintf(string format, ...)
+=========
+*/
+
+void VM_sprintf(void)
+{
+       const char *s, *s0;
+       char outbuf[MAX_INPUTLINE];
+       char *o = outbuf, *end = outbuf + sizeof(outbuf), *err;
+       int argpos = 1;
+       int width, precision, thisarg, flags;
+       char formatbuf[16];
+       char *f;
+       qboolean isfloat;
+#define PRINTF_ALTERNATE 1
+#define PRINTF_ZEROPAD 2
+#define PRINTF_LEFT 4
+#define PRINTF_SPACEPOSITIVE 8
+#define PRINTF_SIGNPOSITIVE 16
+
+       formatbuf[0] = '%';
+
+       s = PRVM_G_STRING(OFS_PARM0);
+
+#define GETARG_FLOAT(a) (((a)>=1 && (a)<prog->argc) ? (PRVM_G_FLOAT(OFS_PARM0 + 3 * (a))) : 0)
+#define GETARG_INT(a) (((a)>=1 && (a)<prog->argc) ? (PRVM_G_INT(OFS_PARM0 + 3 * (a))) : 0)
+#define GETARG_STRING(a) (((a)>=1 && (a)<prog->argc) ? (PRVM_G_STRING(OFS_PARM0 + 3 * (a))) : "")
+
+       for(;;)
+       {
+               s0 = s;
+               switch(*s)
+               {
+                       case 0:
+                               goto finished;
+                               break;
+                       case '%':
+                               ++s;
+
+                               if(*s == '%')
+                                       goto verbatim;
+
+                               // complete directive format:
+                               // %3$*1$.*2$ld
+                               
+                               width = -1;
+                               precision = -1;
+                               thisarg = -1;
+                               flags = 0;
+
+                               // is number following?
+                               if(*s >= '0' && *s <= '9')
+                               {
+                                       width = strtol(s, &err, 10);
+                                       if(!err)
+                                       {
+                                               VM_Warning("VM_sprintf: invalid directive in %s: %s\n", PRVM_NAME, s0);
+                                               goto finished;
+                                       }
+                                       if(*err == '$')
+                                       {
+                                               thisarg = width;
+                                               width = -1;
+                                               s = err + 1;
+                                       }
+                                       else
+                                       {
+                                               if(*s == '0')
+                                               {
+                                                       flags |= PRINTF_ZEROPAD;
+                                                       if(width == 0)
+                                                               width = -1; // it was just a flag
+                                               }
+                                               s = err;
+                                       }
+                               }
+
+                               if(width < 0)
+                               {
+                                       for(;;)
+                                       {
+                                               switch(*s)
+                                               {
+                                                       case '#': flags |= PRINTF_ALTERNATE; break;
+                                                       case '0': flags |= PRINTF_ZEROPAD; break;
+                                                       case '-': flags |= PRINTF_LEFT; break;
+                                                       case ' ': flags |= PRINTF_SPACEPOSITIVE; break;
+                                                       case '+': flags |= PRINTF_SIGNPOSITIVE; break;
+                                                       default:
+                                                               goto noflags;
+                                               }
+                                               ++s;
+                                       }
+noflags:
+                                       if(*s == '*')
+                                       {
+                                               ++s;
+                                               if(*s >= '0' && *s <= '9')
+                                               {
+                                                       width = strtol(s, &err, 10);
+                                                       if(!err || *err != '$')
+                                                       {
+                                                               VM_Warning("VM_sprintf: invalid directive in %s: %s\n", PRVM_NAME, s0);
+                                                               goto finished;
+                                                       }
+                                                       s = err + 1;
+                                               }
+                                               else
+                                                       width = argpos++;
+                                               width = GETARG_FLOAT(width);
+                                               if(width < 0)
+                                               {
+                                                       flags |= PRINTF_LEFT;
+                                                       width = -width;
+                                               }
+                                       }
+                                       else if(*s >= '0' && *s <= '9')
+                                       {
+                                               width = strtol(s, &err, 10);
+                                               if(!err)
+                                               {
+                                                       VM_Warning("VM_sprintf: invalid directive in %s: %s\n", PRVM_NAME, s0);
+                                                       goto finished;
+                                               }
+                                               s = err;
+                                               if(width < 0)
+                                               {
+                                                       flags |= PRINTF_LEFT;
+                                                       width = -width;
+                                               }
+                                       }
+                                       // otherwise width stays -1
+                               }
+
+                               if(*s == '.')
+                               {
+                                       ++s;
+                                       if(*s == '*')
+                                       {
+                                               ++s;
+                                               if(*s >= '0' && *s <= '9')
+                                               {
+                                                       precision = strtol(s, &err, 10);
+                                                       if(!err || *err != '$')
+                                                       {
+                                                               VM_Warning("VM_sprintf: invalid directive in %s: %s\n", PRVM_NAME, s0);
+                                                               goto finished;
+                                                       }
+                                                       s = err + 1;
+                                               }
+                                               else
+                                                       precision = argpos++;
+                                               precision = GETARG_FLOAT(precision);
+                                       }
+                                       else if(*s >= '0' && *s <= '9')
+                                       {
+                                               precision = strtol(s, &err, 10);
+                                               if(!err)
+                                               {
+                                                       VM_Warning("VM_sprintf: invalid directive in %s: %s\n", PRVM_NAME, s0);
+                                                       goto finished;
+                                               }
+                                               s = err;
+                                       }
+                                       else
+                                       {
+                                               VM_Warning("VM_sprintf: invalid directive in %s: %s\n", PRVM_NAME, s0);
+                                               goto finished;
+                                       }
+                               }
+
+                               isfloat = true;
+                               for(;;)
+                               {
+                                       switch(*s)
+                                       {
+                                               case 'h': isfloat = true; break;
+                                               case 'l': isfloat = false; break;
+                                               case 'L': isfloat = false; break;
+                                               case 'j': break;
+                                               case 'z': break;
+                                               case 't': break;
+                                               default:
+                                                       goto nolength;
+                                       }
+                                       ++s;
+                               }
+nolength:
+
+                               if(thisarg < 0)
+                                       thisarg = argpos++;
+
+                               if(o < end - 1)
+                               {
+                                       f = &formatbuf[1];
+                                       if(*s != 's' && *s != 'c')
+                                               if(flags & PRINTF_ALTERNATE) *f++ = '#';
+                                       if(flags & PRINTF_ZEROPAD) *f++ = '0';
+                                       if(flags & PRINTF_LEFT) *f++ = '-';
+                                       if(flags & PRINTF_SPACEPOSITIVE) *f++ = ' ';
+                                       if(flags & PRINTF_SIGNPOSITIVE) *f++ = '+';
+                                       *f++ = '*';
+                                       *f++ = '.';
+                                       *f++ = '*';
+                                       *f++ = *s;
+                                       *f++ = 0;
+
+                                       if(width < 0) // not set
+                                               width = 0;
+
+                                       switch(*s)
+                                       {
+                                               case 'd': case 'i':
+                                                       if(precision < 0) // not set
+                                                               precision = 1;
+                                                       o += dpsnprintf(o, end - o, formatbuf, width, precision, (isfloat ? (int) GETARG_FLOAT(thisarg) : (int) GETARG_INT(thisarg)));
+                                                       break;
+                                               case 'o': case 'u': case 'x': case 'X':
+                                                       if(precision < 0) // not set
+                                                               precision = 1;
+                                                       o += dpsnprintf(o, end - o, formatbuf, width, precision, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg)));
+                                                       break;
+                                               case 'e': case 'E': case 'f': case 'F': case 'g': case 'G':
+                                                       if(precision < 0) // not set
+                                                               precision = 6;
+                                                       o += dpsnprintf(o, end - o, formatbuf, width, precision, (isfloat ? (double) GETARG_FLOAT(thisarg) : (double) GETARG_INT(thisarg)));
+                                                       break;
+                                               case 'c':
+                                                       if(precision < 0) // not set
+                                                               precision = end - o - 1;
+                                                       if(flags & PRINTF_ALTERNATE)
+                                                               o += dpsnprintf(o, end - o, formatbuf, width, precision, (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg)));
+                                                       else
+                                                       {
+                                                               unsigned int c = (isfloat ? (unsigned int) GETARG_FLOAT(thisarg) : (unsigned int) GETARG_INT(thisarg));
+                                                               const char *buf = u8_encodech(c, NULL);
+                                                               if(!buf)
+                                                                       buf = "";
+                                                               o += u8_strpad(o, end - o, buf, (flags & PRINTF_LEFT) != 0, width, precision);
+                                                       }
+                                                       break;
+                                               case 's':
+                                                       if(precision < 0) // not set
+                                                               precision = end - o - 1;
+                                                       if(flags & PRINTF_ALTERNATE)
+                                                               o += dpsnprintf(o, end - o, formatbuf, width, precision, GETARG_STRING(thisarg));
+                                                       else
+                                                               o += u8_strpad(o, end - o, GETARG_STRING(thisarg), (flags & PRINTF_LEFT) != 0, width, precision);
+                                                       break;
+                                               default:
+                                                       VM_Warning("VM_sprintf: invalid directive in %s: %s\n", PRVM_NAME, s0);
+                                                       goto finished;
+                                       }
+                               }
+                               ++s;
+                               break;
+                       default:
+verbatim:
+                               if(o < end - 1)
+                                       *o++ = *s++;
+                               break;
+               }
+       }
+finished:
+       *o = 0;
+       PRVM_G_INT(OFS_RETURN) = PRVM_SetTempString(outbuf);
+}
+
+
+// surface querying
+
+static dp_model_t *getmodel(prvm_edict_t *ed)
+{
+       switch(PRVM_GetProgNr())
+       {
+               case PRVM_SERVERPROG:
+                       return SV_GetModelFromEdict(ed);
+               case PRVM_CLIENTPROG:
+                       return CL_GetModelFromEdict(ed);
+               default:
+                       return NULL;
+       }
+}
+
+static void getmatrix(prvm_edict_t *ed, matrix4x4_t *out)
+{
+       switch(PRVM_GetProgNr())
+       {
+               case PRVM_SERVERPROG:
+                       SV_GetEntityMatrix(ed, out, false);
+                       break;
+               case PRVM_CLIENTPROG:
+                       CL_GetEntityMatrix(ed, out, false);
+                       break;
+               default:
+                       *out = identitymatrix;
+                       break;
+       }
+}
+
+static void applytransform_forward(const vec3_t in, prvm_edict_t *ed, vec3_t out)
+{
+       matrix4x4_t m;
+       getmatrix(ed, &m);
+       Matrix4x4_Transform(&m, in, out);
+}
+
+static void applytransform_forward_direction(const vec3_t in, prvm_edict_t *ed, vec3_t out)
+{
+       matrix4x4_t m;
+       getmatrix(ed, &m);
+       Matrix4x4_Transform3x3(&m, in, out);
+}
+
+static void applytransform_inverted(const vec3_t in, prvm_edict_t *ed, vec3_t out)
+{
+       matrix4x4_t m, n;
+       getmatrix(ed, &m);
+       Matrix4x4_Invert_Full(&m, &n);
+       Matrix4x4_Transform3x3(&n, in, out);
+}
+
+static void applytransform_forward_normal(const vec3_t in, prvm_edict_t *ed, vec3_t out)
+{
+       matrix4x4_t m;
+       float p[4];
+       getmatrix(ed, &m);
+       Matrix4x4_TransformPositivePlane(&m, in[0], in[1], in[2], 0, p);
+       VectorCopy(p, out);
+}
+
+static 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 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;
+void VM_getsurfacenumpoints(void)
+{
+       dp_model_t *model;
+       msurface_t *surface;
+       VM_SAFEPARMCOUNT(2, VM_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;
+void VM_getsurfacepoint(void)
+{
+       prvm_edict_t *ed;
+       dp_model_t *model;
+       msurface_t *surface;
+       int pointnum;
+       VM_SAFEPARMCOUNT(3, VM_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;
+       applytransform_forward(&(model->surfmesh.data_vertex3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, 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;
+void VM_getsurfacepointattribute(void)
+{
+       prvm_edict_t *ed;
+       dp_model_t *model;
+       msurface_t *surface;
+       int pointnum;
+       int attributetype;
+
+       VM_SAFEPARMCOUNT(4, VM_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;
+       pointnum = (int)PRVM_G_FLOAT(OFS_PARM2);
+       if (pointnum < 0 || pointnum >= surface->num_vertices)
+               return;
+       attributetype = (int) PRVM_G_FLOAT(OFS_PARM3);
+
+       switch( attributetype ) {
+               // float SPA_POSITION = 0;
+               case 0:
+                       applytransform_forward(&(model->surfmesh.data_vertex3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, PRVM_G_VECTOR(OFS_RETURN));
+                       break;
+               // float SPA_S_AXIS = 1;
+               case 1:
+                       applytransform_forward_direction(&(model->surfmesh.data_svector3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, PRVM_G_VECTOR(OFS_RETURN));
+                       break;
+               // float SPA_T_AXIS = 2;
+               case 2:
+                       applytransform_forward_direction(&(model->surfmesh.data_tvector3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, PRVM_G_VECTOR(OFS_RETURN));
+                       break;
+               // float SPA_R_AXIS = 3; // same as SPA_NORMAL
+               case 3:
+                       applytransform_forward_direction(&(model->surfmesh.data_normal3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, 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;
+void VM_getsurfacenormal(void)
+{
+       dp_model_t *model;
+       msurface_t *surface;
+       vec3_t normal;
+       VM_SAFEPARMCOUNT(2, VM_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;
+       // 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);
+       applytransform_forward_normal(normal, PRVM_G_EDICT(OFS_PARM0), PRVM_G_VECTOR(OFS_RETURN));
+       VectorNormalize(PRVM_G_VECTOR(OFS_RETURN));
+}
+//PF_getsurfacetexture,   // #437 string(entity e, float s) getsurfacetexture = #437;
+void VM_getsurfacetexture(void)
+{
+       dp_model_t *model;
+       msurface_t *surface;
+       VM_SAFEPARMCOUNT(2, VM_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;
+void VM_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_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;
+
+       applytransform_inverted(point, ed, 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;
+void VM_getsurfaceclippedpoint(void)
+{
+       prvm_edict_t *ed;
+       dp_model_t *model;
+       msurface_t *surface;
+       vec3_t p, out;
+       VM_SAFEPARMCOUNT(3, VM_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;
+       applytransform_inverted(PRVM_G_VECTOR(OFS_PARM2), ed, p);
+       clippointtosurface(model, surface, p, out);
+       VectorAdd(out, ed->fields.server->origin, PRVM_G_VECTOR(OFS_RETURN));
+}
+
+//PF_getsurfacenumtriangles, // #??? float(entity e, float s) getsurfacenumtriangles = #???;
+void VM_getsurfacenumtriangles(void)
+{
+       dp_model_t *model;
+       msurface_t *surface;
+       VM_SAFEPARMCOUNT(2, VM_SV_getsurfacenumtriangles);
+       // 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_triangles;
+}
+//PF_getsurfacetriangle,     // #??? vector(entity e, float s, float n) getsurfacetriangle = #???;
+void VM_getsurfacetriangle(void)
+{
+       prvm_edict_t *ed;
+       dp_model_t *model;
+       msurface_t *surface;
+       int trinum;
+       VM_SAFEPARMCOUNT(3, VM_SV_getsurfacetriangle);
+       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;
+       trinum = (int)PRVM_G_FLOAT(OFS_PARM2);
+       if (trinum < 0 || trinum >= surface->num_triangles)
+               return;
+       // FIXME: implement rotation/scaling
+       VectorCopy(&(model->surfmesh.data_element3i + 3 * surface->num_firsttriangle)[trinum * 3], PRVM_G_VECTOR(OFS_RETURN));
+}