]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - prvm_cmds.c
simple video subtitles support, defined in external text files, added a couple of...
[xonotic/darkplaces.git] / prvm_cmds.c
index 145efd8a853cb4c9f8624e0876c97845f4d8f41d..83eb617c73ac90abb894f94d638d3d23101514ae 100644 (file)
@@ -191,11 +191,11 @@ void VM_UpdateEdictSkeleton(prvm_edict_t *ed, const dp_model_t *edmodel, const f
                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;
+               int skeletonindex = -1;
                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)
+               if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.skeletonindex))) skeletonindex = (int)val->_float - 1;
+               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));
@@ -2878,6 +2878,50 @@ void VM_gettime(void)
        }
 }
 
+/*
+=========
+VM_getsoundtime
+
+float  getsoundtime(void)
+=========
+*/
+
+void VM_getsoundtime (void)
+{
+       int entnum, entchannel, pnum;
+       VM_SAFEPARMCOUNT(2,VM_getsoundtime);
+
+       pnum = PRVM_GetProgNr();
+       if (pnum == PRVM_MENUPROG)
+       {
+               VM_Warning("VM_getsoundtime: %s: not supported on this progs\n", PRVM_NAME);
+               PRVM_G_FLOAT(OFS_RETURN) = -1;
+               return;
+       }
+       entnum = ((pnum == PRVM_CLIENTPROG) ? MAX_EDICTS : 0) + PRVM_NUM_FOR_EDICT(PRVM_G_EDICT(OFS_PARM0));
+       entchannel = (int)PRVM_G_FLOAT(OFS_PARM1);
+       if (entchannel <= 0 || entchannel > 8)
+               VM_Warning("VM_getsoundtime: %s: bad channel %i\n", PRVM_NAME, entchannel);
+       PRVM_G_FLOAT(OFS_RETURN) = (float)S_GetEntChannelPosition(entnum, entchannel);
+}
+
+/*
+=========
+VM_GetSoundLen
+
+string soundlength (string sample)
+=========
+*/
+void VM_soundlength (void)
+{
+       const char *s;
+
+       VM_SAFEPARMCOUNT(1, VM_soundlength);
+
+       s = PRVM_G_STRING(OFS_PARM0);
+       PRVM_G_FLOAT(OFS_RETURN) = S_SoundLength(s);
+}
+
 /*
 =========
 VM_loadfromdata
@@ -3346,7 +3390,7 @@ void VM_drawcolorcodedstring(void)
 {
        float *pos,*scale;
        const char  *string;
-       int flag,color;
+       int flag;
        float sx, sy;
        VM_SAFEPARMCOUNT(5,VM_drawstring);
 
@@ -3372,7 +3416,6 @@ void VM_drawcolorcodedstring(void)
        if(pos[2] || scale[2])
                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;
        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;
@@ -3757,7 +3800,7 @@ void VM_cin_open( void )
        VM_CheckEmptyString( file );
     VM_CheckEmptyString( name );
 
-       if( CL_OpenVideo( file, name, MENUOWNER ) )
+       if( CL_OpenVideo( file, name, MENUOWNER, "" ) )
                PRVM_G_FLOAT( OFS_RETURN ) = 1;
        else
                PRVM_G_FLOAT( OFS_RETURN ) = 0;
@@ -4346,18 +4389,16 @@ string  altstr_ins(string altstr, float num, string set)
 void VM_altstr_ins(void)
 {
        int num;
-       const char *setstr;
        const char *set;
-       const char *instr;
        const char *in;
        char *out;
        char outstr[VM_STRINGTEMP_LENGTH];
 
        VM_SAFEPARMCOUNT(3, VM_altstr_ins);
 
-       in = instr = PRVM_G_STRING( OFS_PARM0 );
+       in = PRVM_G_STRING( OFS_PARM0 );
        num = (int)PRVM_G_FLOAT( OFS_PARM1 );
-       set = setstr = PRVM_G_STRING( OFS_PARM2 );
+       set = PRVM_G_STRING( OFS_PARM2 );
 
        out = outstr;
        for( num = num * 2 + 2 ; *in && num > 0 ; *out++ = *in++ )
@@ -4442,19 +4483,34 @@ static int BufStr_SortStringsDOWN (const void *in1, const void *in2)
 VM_buf_create
 creates new buffer, and returns it's index, returns -1 if failed
 float buf_create(void) = #460;
+float newbuf(string format, float flags) = #460;
 ========================
 */
+
 void VM_buf_create (void)
 {
        prvm_stringbuffer_t *stringbuffer;
        int i;
-       VM_SAFEPARMCOUNT(0, VM_buf_create);
+       
+       VM_SAFEPARMCOUNTRANGE(0, 2, VM_buf_create);
+
+       // VorteX: optional parm1 (buffer format) is unfinished, to keep intact with future databuffers extension must be set to "string"
+       if(prog->argc >= 1 && strcmp(PRVM_G_STRING(OFS_PARM0), "string"))
+       {
+               PRVM_G_FLOAT(OFS_RETURN) = -1;
+               return;
+       }
        stringbuffer = (prvm_stringbuffer_t *) Mem_ExpandableArray_AllocRecord(&prog->stringbuffersarray);
        for (i = 0;stringbuffer != Mem_ExpandableArray_RecordAtIndex(&prog->stringbuffersarray, i);i++);
        stringbuffer->origin = PRVM_AllocationOrigin();
+       // optional flags parm
+       if(prog->argc == 2)
+               stringbuffer->flags = (int)PRVM_G_FLOAT(OFS_PARM1) & 0xFF;
        PRVM_G_FLOAT(OFS_RETURN) = i;
 }
 
+
+
 /*
 ========================
 VM_buf_del
@@ -5044,7 +5100,7 @@ void VM_strstrofs (void)
        if (!match)
                PRVM_G_FLOAT(OFS_RETURN) = -1;
        else
-               PRVM_G_FLOAT(OFS_RETURN) = match - instr;
+               PRVM_G_FLOAT(OFS_RETURN) = u8_strnlen(instr, match-instr);
 }
 
 //#222 string(string s, float index) str2chr (FTE_STRINGS)
@@ -5427,7 +5483,7 @@ void VM_uri_escape (void)
                        || (*p >= 'a' && *p <= 'z')
                        || (*p >= '0' && *p <= '9')
                        || (*p == '-')  || (*p == '_') || (*p == '.')
-                       || (*p == '!')  || (*p == '~') || (*p == '*')
+                       || (*p == '!')  || (*p == '~')
                        || (*p == '\'') || (*p == '(') || (*p == ')'))
                        *q++ = *p;
                else
@@ -5561,7 +5617,7 @@ void VM_uri_get (void)
        handle->prognr = PRVM_GetProgNr();
        handle->starttime = prog->starttime;
        handle->id = id;
-       ret = Curl_Begin_ToMemory(url, (unsigned char *) handle->buffer, sizeof(handle->buffer), uri_to_string_callback, handle);
+       ret = Curl_Begin_ToMemory(url, 0, (unsigned char *) handle->buffer, sizeof(handle->buffer), uri_to_string_callback, handle);
        if(ret)
        {
                PRVM_G_INT(OFS_RETURN) = 1;
@@ -5624,6 +5680,26 @@ void VM_SV_getextresponse (void)
        }
 }
 
+/*
+=========
+Common functions between menu.dat and clsprogs
+=========
+*/
+
+//#349 float() isdemo 
+void VM_CL_isdemo (void)
+{
+       VM_SAFEPARMCOUNT(0, VM_CL_isdemo);
+       PRVM_G_FLOAT(OFS_RETURN) = cls.demoplayback;
+}
+
+//#355 float() videoplaying 
+void VM_CL_videoplaying (void)
+{
+       VM_SAFEPARMCOUNT(0, VM_CL_videoplaying);
+       PRVM_G_FLOAT(OFS_RETURN) = cl_videoplaying;
+}
+
 /*
 =========
 VM_M_callfunction
@@ -5711,6 +5787,9 @@ void VM_sprintf(void)
        char formatbuf[16];
        char *f;
        qboolean isfloat;
+       static int dummyivec[3] = {0, 0, 0};
+       static float dummyvec[3] = {0, 0, 0};
+
 #define PRINTF_ALTERNATE 1
 #define PRINTF_ZEROPAD 2
 #define PRINTF_LEFT 4
@@ -5722,7 +5801,9 @@ void VM_sprintf(void)
        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_VECTOR(a) (((a)>=1 && (a)<prog->argc) ? (PRVM_G_VECTOR(OFS_PARM0 + 3 * (a))) : dummyvec)
 #define GETARG_INT(a) (((a)>=1 && (a)<prog->argc) ? (PRVM_G_INT(OFS_PARM0 + 3 * (a))) : 0)
+#define GETARG_INTVECTOR(a) (((a)>=1 && (a)<prog->argc) ? ((int*) PRVM_G_VECTOR(OFS_PARM0 + 3 * (a))) : dummyivec)
 #define GETARG_STRING(a) (((a)>=1 && (a)<prog->argc) ? (PRVM_G_STRING(OFS_PARM0 + 3 * (a))) : "")
 
        for(;;)
@@ -5732,7 +5813,6 @@ void VM_sprintf(void)
                {
                        case 0:
                                goto finished;
-                               break;
                        case '%':
                                ++s;
 
@@ -5924,6 +6004,16 @@ nolength:
                                                                precision = 6;
                                                        o += dpsnprintf(o, end - o, formatbuf, width, precision, (isfloat ? (double) GETARG_FLOAT(thisarg) : (double) GETARG_INT(thisarg)));
                                                        break;
+                                               case 'v': case 'V':
+                                                       f[-2] += 'g' - 'v';
+                                                       if(precision < 0) // not set
+                                                               precision = 6;
+                                                       o += dpsnprintf(o, end - o, va("%s %s %s", /* NESTED SPRINTF IS NESTED */ formatbuf, formatbuf, formatbuf),
+                                                               width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[0] : (double) GETARG_INTVECTOR(thisarg)[0]),
+                                                               width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[1] : (double) GETARG_INTVECTOR(thisarg)[1]),
+                                                               width, precision, (isfloat ? (double) GETARG_VECTOR(thisarg)[2] : (double) GETARG_INTVECTOR(thisarg)[2])
+                                                       );
+                                                       break;
                                                case 'c':
                                                        if(precision < 0) // not set
                                                                precision = end - o - 1;
@@ -5981,6 +6071,80 @@ static dp_model_t *getmodel(prvm_edict_t *ed)
        }
 }
 
+typedef struct
+{
+       unsigned int progid;
+       dp_model_t *model;
+       frameblend_t frameblend[MAX_FRAMEBLENDS];
+       skeleton_t *skeleton_p;
+       skeleton_t skeleton;
+       float *data_vertex3f;
+       float *data_svector3f;
+       float *data_tvector3f;
+       float *data_normal3f;
+       int max_vertices;
+       float *buf_vertex3f;
+       float *buf_svector3f;
+       float *buf_tvector3f;
+       float *buf_normal3f;
+}
+animatemodel_cache_t;
+static animatemodel_cache_t animatemodel_cache;
+
+void animatemodel(dp_model_t *model, prvm_edict_t *ed)
+{
+       prvm_eval_t *val;
+       skeleton_t *skeleton;
+       int skeletonindex = -1;
+       qboolean need = false;
+       if(!model->AnimateVertices)
+       {
+               animatemodel_cache.data_vertex3f = model->surfmesh.data_vertex3f;
+               animatemodel_cache.data_svector3f = model->surfmesh.data_svector3f;
+               animatemodel_cache.data_tvector3f = model->surfmesh.data_tvector3f;
+               animatemodel_cache.data_normal3f = model->surfmesh.data_normal3f;
+               return;
+       }
+       if(animatemodel_cache.progid != prog->id)
+               memset(&animatemodel_cache, 0, sizeof(animatemodel_cache));
+       need |= (animatemodel_cache.model != model);
+       VM_GenerateFrameGroupBlend(ed->priv.server->framegroupblend, ed);
+       VM_FrameBlendFromFrameGroupBlend(ed->priv.server->frameblend, ed->priv.server->framegroupblend, model);
+       need |= (memcmp(&animatemodel_cache.frameblend, &ed->priv.server->frameblend, sizeof(ed->priv.server->frameblend)));
+       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.skeletonindex))) skeletonindex = (int)val->_float - 1;
+       if (!(skeletonindex >= 0 && skeletonindex < MAX_EDICTS && (skeleton = prog->skeletons[skeletonindex]) && skeleton->model->num_bones == ed->priv.server->skeleton.model->num_bones))
+               skeleton = NULL;
+       need |= (animatemodel_cache.skeleton_p != skeleton);
+       if(skeleton)
+               need |= (memcmp(&animatemodel_cache.skeleton, skeleton, sizeof(ed->priv.server->skeleton)));
+       if(!need)
+               return;
+       if(model->surfmesh.num_vertices > animatemodel_cache.max_vertices)
+       {
+               animatemodel_cache.max_vertices = model->surfmesh.num_vertices * 2;
+               if(animatemodel_cache.buf_vertex3f) Mem_Free(animatemodel_cache.buf_vertex3f);
+               if(animatemodel_cache.buf_svector3f) Mem_Free(animatemodel_cache.buf_svector3f);
+               if(animatemodel_cache.buf_tvector3f) Mem_Free(animatemodel_cache.buf_tvector3f);
+               if(animatemodel_cache.buf_normal3f) Mem_Free(animatemodel_cache.buf_normal3f);
+               animatemodel_cache.buf_vertex3f = Mem_Alloc(prog->progs_mempool, sizeof(float[3]) * animatemodel_cache.max_vertices);
+               animatemodel_cache.buf_svector3f = Mem_Alloc(prog->progs_mempool, sizeof(float[3]) * animatemodel_cache.max_vertices);
+               animatemodel_cache.buf_tvector3f = Mem_Alloc(prog->progs_mempool, sizeof(float[3]) * animatemodel_cache.max_vertices);
+               animatemodel_cache.buf_normal3f = Mem_Alloc(prog->progs_mempool, sizeof(float[3]) * animatemodel_cache.max_vertices);
+       }
+       animatemodel_cache.data_vertex3f = animatemodel_cache.buf_vertex3f;
+       animatemodel_cache.data_svector3f = animatemodel_cache.buf_svector3f;
+       animatemodel_cache.data_tvector3f = animatemodel_cache.buf_tvector3f;
+       animatemodel_cache.data_normal3f = animatemodel_cache.buf_normal3f;
+       VM_UpdateEdictSkeleton(ed, model, ed->priv.server->frameblend);
+       model->AnimateVertices(model, ed->priv.server->frameblend, &ed->priv.server->skeleton, animatemodel_cache.data_vertex3f, animatemodel_cache.data_normal3f, animatemodel_cache.data_svector3f, animatemodel_cache.data_tvector3f);
+       animatemodel_cache.progid = prog->id;
+       animatemodel_cache.model = model;
+       memcpy(&animatemodel_cache.frameblend, &ed->priv.server->frameblend, sizeof(ed->priv.server->frameblend));
+       animatemodel_cache.skeleton_p = skeleton;
+       if(skeleton)
+               memcpy(&animatemodel_cache.skeleton, skeleton, sizeof(ed->priv.server->skeleton));
+}
+
 static void getmatrix(prvm_edict_t *ed, matrix4x4_t *out)
 {
        switch(PRVM_GetProgNr())
@@ -6028,20 +6192,21 @@ static void applytransform_forward_normal(const vec3_t in, prvm_edict_t *ed, vec
        VectorCopy(p, out);
 }
 
-static void clippointtosurface(dp_model_t *model, msurface_t *surface, vec3_t p, vec3_t out)
+static void clippointtosurface(prvm_edict_t *ed, 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;
+       animatemodel(model, ed);
        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;
+               v[0] = animatemodel_cache.data_vertex3f + e[0] * 3;
+               v[1] = animatemodel_cache.data_vertex3f + e[1] * 3;
+               v[2] = animatemodel_cache.data_vertex3f + e[2] * 3;
                TriangleNormal(v[0], v[1], v[2], facenormal);
                VectorNormalize(facenormal);
                offsetdist = DotProduct(v[0], facenormal) - DotProduct(p, facenormal);
@@ -6104,7 +6269,8 @@ void VM_getsurfacepoint(void)
        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));
+       animatemodel(model, ed);
+       applytransform_forward(&(animatemodel_cache.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;
@@ -6132,22 +6298,24 @@ void VM_getsurfacepointattribute(void)
                return;
        attributetype = (int) PRVM_G_FLOAT(OFS_PARM3);
 
+       animatemodel(model, ed);
+
        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));
+                       applytransform_forward(&(animatemodel_cache.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));
+                       applytransform_forward_direction(&(animatemodel_cache.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));
+                       applytransform_forward_direction(&(animatemodel_cache.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));
+                       applytransform_forward_direction(&(animatemodel_cache.data_normal3f + 3 * surface->num_firstvertex)[pointnum * 3], ed, PRVM_G_VECTOR(OFS_RETURN));
                        break;
                // float SPA_TEXCOORDS0 = 4;
                case 4: {
@@ -6189,7 +6357,8 @@ void VM_getsurfacenormal(void)
                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);
+       animatemodel(model, PRVM_G_EDICT(OFS_PARM0));
+       TriangleNormal((animatemodel_cache.data_vertex3f + 3 * surface->num_firstvertex), (animatemodel_cache.data_vertex3f + 3 * surface->num_firstvertex) + 3, (animatemodel_cache.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));
 }
@@ -6225,6 +6394,8 @@ void VM_getsurfacenearpoint(void)
        if (!model || !model->num_surfaces)
                return;
 
+       animatemodel(model, ed);
+
        applytransform_inverted(point, ed, p);
        best = -1;
        bestdist = 1000000000;
@@ -6239,7 +6410,7 @@ void VM_getsurfacenearpoint(void)
                if (dist < bestdist)
                {
                        // it is, check the nearest point on the actual geometry
-                       clippointtosurface(model, surface, p, clipped);
+                       clippointtosurface(ed, model, surface, p, clipped);
                        VectorSubtract(clipped, p, clipped);
                        dist += VectorLength2(clipped);
                        if (dist < bestdist)
@@ -6264,8 +6435,9 @@ void VM_getsurfaceclippedpoint(void)
        ed = PRVM_G_EDICT(OFS_PARM0);
        if (!(model = getmodel(ed)) || !(surface = getsurface(model, (int)PRVM_G_FLOAT(OFS_PARM1))))
                return;
+       animatemodel(model, ed);
        applytransform_inverted(PRVM_G_VECTOR(OFS_PARM2), ed, p);
-       clippointtosurface(model, surface, p, out);
+       clippointtosurface(ed, model, surface, p, out);
        VectorAdd(out, ed->fields.server->origin, PRVM_G_VECTOR(OFS_RETURN));
 }
 
@@ -6288,6 +6460,7 @@ void VM_getsurfacenumtriangles(void)
 //PF_getsurfacetriangle,     // #??? vector(entity e, float s, float n) getsurfacetriangle = #???;
 void VM_getsurfacetriangle(void)
 {
+       const vec3_t d = {-1, -1, -1};
        prvm_edict_t *ed;
        dp_model_t *model;
        msurface_t *surface;
@@ -6301,5 +6474,5 @@ void VM_getsurfacetriangle(void)
        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));
+       VectorMA(&(model->surfmesh.data_element3i + 3 * surface->num_firsttriangle)[trinum * 3], surface->num_firstvertex, d, PRVM_G_VECTOR(OFS_RETURN));
 }