]> de.git.xonotic.org Git - xonotic/darkplaces.git/blobdiff - csprogs.c
Patch by FruitieX -- fixes speed
[xonotic/darkplaces.git] / csprogs.c
index 7748ccf0d2b9f4552b7d7e9d60df223780bf1d14..1ee20b6017214c909f87ed829bfae821e742a7f0 100644 (file)
--- a/csprogs.c
+++ b/csprogs.c
@@ -13,6 +13,7 @@
 #define CSQC_RETURNVAL prog->globals.generic[OFS_RETURN]
 #define CSQC_BEGIN             csqc_tmpprog=prog;prog=0;PRVM_SetProg(PRVM_CLIENTPROG);
 #define CSQC_END               prog=csqc_tmpprog;
+
 static prvm_prog_t *csqc_tmpprog;
 
 //[515]: these are required funcs
@@ -44,7 +45,7 @@ void CL_VM_Error (const char *format, ...)    //[515]: hope it will be never execut
        Cvar_SetValueQuick(&csqc_progsize, -1);
 
 //     Host_AbortCurrentFrame();       //[515]: hmmm... if server says it needs csqc then client MUST disconnect
-       Host_Error(va("CL_VM_Error: %s", errorstring));
+       Host_Error("CL_VM_Error: %s", errorstring);
 }
 void CL_VM_UpdateDmgGlobals (int dmg_take, int dmg_save, vec3_t dmg_origin)
 {
@@ -68,6 +69,22 @@ void CL_VM_UpdateDmgGlobals (int dmg_take, int dmg_save, vec3_t dmg_origin)
                CSQC_END
        }
 }
+
+void CSQC_UpdateNetworkTimes(double newtime, double oldtime)
+{
+       prvm_eval_t *val;
+       if(!cl.csqc_loaded)
+               return;
+       CSQC_BEGIN
+       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.servertime)))
+               val->_float = newtime;
+       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.serverprevtime)))
+               val->_float = oldtime;
+       if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.serverdeltatime)))
+               val->_float = newtime - oldtime;
+       CSQC_END
+}
+
 //[515]: set globals before calling R_UpdateView, WEIRD CRAP
 static void CSQC_SetGlobals (void)
 {
@@ -89,7 +106,7 @@ static void CSQC_SetGlobals (void)
                // CSQC is useless as it can't alter the view origin without
                // completely replacing it
                VectorCopy(cl.csqc_origin, prog->globals.client->pmove_org);
-               VectorCopy(cl.velocity, prog->globals.client->pmove_vel);
+               VectorCopy(cl.movement_velocity, prog->globals.client->pmove_vel);
 
                if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.view_angles)))
                        VectorCopy(cl.viewangles, val->vector);
@@ -130,14 +147,14 @@ qboolean CSQC_AddRenderEdict(prvm_edict_t *ed)
        float scale;
        prvm_eval_t *val;
        entity_render_t *entrender;
-       model_t *model;
+       dp_model_t *model;
        matrix4x4_t tagmatrix, matrix2;
 
        model = CL_GetModelFromEdict(ed);
        if (!model)
                return false;
 
-       entrender = CL_NewTempEntity();
+       entrender = CL_NewTempEntity(0);
        if (!entrender)
                return false;
 
@@ -187,11 +204,24 @@ qboolean CSQC_AddRenderEdict(prvm_edict_t *ed)
        // 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
-       entrender->frame1 = entrender->frame2 = ed->fields.client->frame;
-       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame2))) entrender->frame2 = val->_float;
-       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame1time))) entrender->frame2time = val->_float;
-       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame2time))) entrender->frame1time = val->_float;
-       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.lerpfrac))) entrender->framelerp = val->_float;
+       // self.lerpfrac is the interpolation strength for self.frame
+       // 3+ are for additional blends (the main use for this feature is lerping
+       // pitch angle on a player model where the animator set up 5 sets of
+       // animations and the csqc simply lerps between sets)
+       entrender->framegroupblend[0].frame = entrender->framegroupblend[1].frame = (int) ed->fields.client->frame;
+       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame2))) entrender->framegroupblend[1].frame = (int) val->_float;
+       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame3))) entrender->framegroupblend[2].frame = (int) val->_float;
+       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame4))) entrender->framegroupblend[3].frame = (int) val->_float;
+       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame1time))) entrender->framegroupblend[0].start = val->_float;
+       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame2time))) entrender->framegroupblend[1].start = val->_float;
+       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame3time))) entrender->framegroupblend[2].start = val->_float;
+       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame4time))) entrender->framegroupblend[3].start = val->_float;
+       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.lerpfrac))) entrender->framegroupblend[0].lerp = val->_float;
+       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.lerpfrac3))) entrender->framegroupblend[2].lerp = val->_float;
+       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.lerpfrac4))) entrender->framegroupblend[3].lerp = val->_float;
+       if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.shadertime))) entrender->shadertime = val->_float;
+       // assume that the (missing) lerpfrac2 is whatever remains after lerpfrac+lerpfrac3+lerpfrac4 are summed
+       entrender->framegroupblend[1].lerp = 1 - entrender->framegroupblend[0].lerp - entrender->framegroupblend[2].lerp - entrender->framegroupblend[3].lerp;
 
        // concat the matrices to make the entity relative to its tag
        Matrix4x4_Concat(&entrender->matrix, &tagmatrix, &matrix2);
@@ -212,6 +242,7 @@ qboolean CSQC_AddRenderEdict(prvm_edict_t *ed)
        else
                CL_SetEntityColormapColors(entrender, c);
 
+       entrender->flags &= ~(RENDER_SHADOW | RENDER_LIGHT | RENDER_NOSELFSHADOW);
        // either fullbright or lit
        if (!(entrender->effects & EF_FULLBRIGHT) && !r_fullbright.integer)
                entrender->flags |= RENDER_LIGHT;
@@ -223,6 +254,8 @@ qboolean CSQC_AddRenderEdict(prvm_edict_t *ed)
                entrender->flags |= RENDER_SHADOW;
        if (entrender->flags & RENDER_VIEWMODEL)
                entrender->flags |= RENDER_NOSELFSHADOW;
+       if (entrender->effects & EF_NOSELFSHADOW)
+               entrender->flags |= RENDER_NOSELFSHADOW;
 
        // make the other useful stuff
        CL_UpdateRenderEntity(entrender);
@@ -243,6 +276,7 @@ qboolean CL_VM_InputEvent (qboolean down, int key, int ascii)
                else
                {
                        prog->globals.client->time = cl.time;
+                       prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
                        PRVM_G_FLOAT(OFS_PARM0) = !down; // 0 is down, 1 is up
                        PRVM_G_FLOAT(OFS_PARM1) = key;
                        PRVM_G_FLOAT(OFS_PARM2) = ascii;
@@ -265,11 +299,15 @@ qboolean CL_VM_UpdateView (void)
        CSQC_BEGIN
                //VectorCopy(cl.viewangles, oldangles);
                prog->globals.client->time = cl.time;
+               prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
                CSQC_SetGlobals();
                // clear renderable entity and light lists to prevent crashes if the
                // CSQC_UpdateView function does not call R_ClearScene as it should
                r_refdef.scene.numentities = 0;
                r_refdef.scene.numlights = 0;
+               // pass in width and height as parameters (EXT_CSQC_1)
+               PRVM_G_FLOAT(OFS_PARM0) = vid.width;
+               PRVM_G_FLOAT(OFS_PARM1) = vid.height;
                PRVM_ExecuteProgram(prog->funcoffsets.CSQC_UpdateView, "QC function CSQC_UpdateView is missing");
                //VectorCopy(oldangles, cl.viewangles);
                // Dresk : Reset Dmg Globals Here
@@ -289,6 +327,7 @@ qboolean CL_VM_ConsoleCommand (const char *cmd)
        if (prog->funcoffsets.CSQC_ConsoleCommand)
        {
                prog->globals.client->time = cl.time;
+               prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
                restorevm_tempstringsbuf_cursize = vm_tempstringsbuf.cursize;
                PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(cmd);
                PRVM_ExecuteProgram(prog->funcoffsets.CSQC_ConsoleCommand, "QC function CSQC_ConsoleCommand is missing");
@@ -310,6 +349,7 @@ qboolean CL_VM_Parse_TempEntity (void)
        {
                t = msg_readcount;
                prog->globals.client->time = cl.time;
+               prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
                PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Parse_TempEntity, "QC function CSQC_Parse_TempEntity is missing");
                r = CSQC_RETURNVAL;
                if(!r)
@@ -342,6 +382,46 @@ void CL_VM_Parse_StuffCmd (const char *msg)
                csqc_progsize.flags = sizeflags;
                return;
        }
+
+       if(cls.demoplayback)
+       if(!strncmp(msg, "curl --clear_autodownload\ncurl --pak --forthismap --as ", 55))
+       {
+               // special handling for map download commands
+               // run these commands IMMEDIATELY, instead of waiting for a client frame
+               // that way, there is no black screen when playing back demos
+               // I know this is a really ugly hack, but I can't think of any better way
+               // FIXME find the actual CAUSE of this, and make demo playback WAIT
+               // until all maps are loaded, then remove this hack
+
+               char buf[MAX_INPUTLINE];
+               const char *p, *q;
+               size_t l;
+
+               p = msg;
+
+               for(;;)
+               {
+                       q = strchr(p, '\n');
+                       if(q)
+                               l = q - p;
+                       else
+                               l = strlen(p);
+                       if(l > sizeof(buf) - 1)
+                               l = sizeof(buf) - 1;
+                       strlcpy(buf, p, l + 1); // strlcpy needs a + 1 as it includes the newline!
+
+                       Cmd_ExecuteString(buf, src_command);
+
+                       p += l;
+                       if(*p == '\n')
+                               ++p; // skip the newline and continue
+                       else
+                               break; // end of string or overflow
+               }
+               Cmd_ExecuteString("curl --clear_autodownload", src_command); // don't inhibit CSQC loading
+               return;
+       }
+
        if(!cl.csqc_loaded)
        {
                Cbuf_AddText(msg);
@@ -351,6 +431,7 @@ void CL_VM_Parse_StuffCmd (const char *msg)
        if(prog->funcoffsets.CSQC_Parse_StuffCmd)
        {
                prog->globals.client->time = cl.time;
+               prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
                restorevm_tempstringsbuf_cursize = vm_tempstringsbuf.cursize;
                PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(msg);
                PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Parse_StuffCmd, "QC function CSQC_Parse_StuffCmd is missing");
@@ -365,6 +446,7 @@ static void CL_VM_Parse_Print (const char *msg)
 {
        int restorevm_tempstringsbuf_cursize;
        prog->globals.client->time = cl.time;
+       prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
        restorevm_tempstringsbuf_cursize = vm_tempstringsbuf.cursize;
        PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(msg);
        PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Parse_Print, "QC function CSQC_Parse_Print is missing");
@@ -409,20 +491,21 @@ void CL_VM_Parse_CenterPrint (const char *msg)
        int restorevm_tempstringsbuf_cursize;
        if(!cl.csqc_loaded)
        {
-               SCR_CenterPrint((char*)msg);
+               SCR_CenterPrint(msg);
                return;
        }
        CSQC_BEGIN
        if(prog->funcoffsets.CSQC_Parse_CenterPrint)
        {
                prog->globals.client->time = cl.time;
+               prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
                restorevm_tempstringsbuf_cursize = vm_tempstringsbuf.cursize;
                PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(msg);
                PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Parse_CenterPrint, "QC function CSQC_Parse_CenterPrint is missing");
                vm_tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize;
        }
        else
-               SCR_CenterPrint((char*)msg);
+               SCR_CenterPrint(msg);
        CSQC_END
 }
 
@@ -450,7 +533,7 @@ void CL_VM_UpdateShowingScoresState (int showingscores)
                CSQC_END
        }
 }
-qboolean CL_VM_Event_Sound(int sound_num, int volume, int channel, float attenuation, int ent, vec3_t pos)
+qboolean CL_VM_Event_Sound(int sound_num, float volume, int channel, float attenuation, int ent, vec3_t pos)
 {
        qboolean r = false;
        if(cl.csqc_loaded)
@@ -459,6 +542,7 @@ qboolean CL_VM_Event_Sound(int sound_num, int volume, int channel, float attenua
                if(prog->funcoffsets.CSQC_Event_Sound)
                {
                        prog->globals.client->time = cl.time;
+                       prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
                        PRVM_G_FLOAT(OFS_PARM0) = ent;
                        PRVM_G_FLOAT(OFS_PARM1) = channel;
                        PRVM_G_INT(OFS_PARM2) = PRVM_SetTempString(cl.sound_name[sound_num] );
@@ -519,6 +603,7 @@ float CL_VM_Event (float event)             //[515]: needed ? I'd say "YES", but don't know
        if(prog->funcoffsets.CSQC_Event)
        {
                prog->globals.client->time = cl.time;
+               prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
                PRVM_G_FLOAT(OFS_PARM0) = event;
                PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Event, "QC function CSQC_Event is missing");
                r = CSQC_RETURNVAL;
@@ -554,7 +639,13 @@ void CSQC_ReadEntities (void)
                                        cl.csqc_server2csqcentitynumber[realentnum] = 0;
                                }
                                else
-                                       Con_Printf("Bad csqc_server2csqcentitynumber map\n");   //[515]: never happens ?
+                               {
+                                       // LordHavoc: removing an entity that is already gone on
+                                       // the csqc side is possible for legitimate reasons (such
+                                       // as a repeat of the remove message), so no warning is
+                                       // needed
+                                       //Con_Printf("Bad csqc_server2csqcentitynumber map\n"); //[515]: never happens ?
+                               }
                        }
                        else
                        {
@@ -566,8 +657,6 @@ void CSQC_ReadEntities (void)
                                                ed = PRVM_ED_Alloc();
                                                ed->fields.client->entnum = realentnum;
                                                prog->globals.client->self = cl.csqc_server2csqcentitynumber[realentnum] = PRVM_EDICT_TO_PROG(ed);
-                                               PRVM_G_FLOAT(OFS_PARM0) = 1;
-                                               PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Ent_Update, "QC function CSQC_Ent_Update is missing");
                                        }
                                        else
                                        {
@@ -577,8 +666,10 @@ void CSQC_ReadEntities (void)
                                                // make sure no one gets wrong ideas
                                                prog->globals.client->self = 0;
                                                PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Ent_Spawn, "QC function CSQC_Ent_Spawn is missing");
-                                               cl.csqc_server2csqcentitynumber[realentnum] = PRVM_EDICT( PRVM_G_INT( OFS_RETURN ) );
+                                               prog->globals.client->self = cl.csqc_server2csqcentitynumber[realentnum] = PRVM_EDICT( PRVM_G_INT( OFS_RETURN ) );
                                        }
+                                       PRVM_G_FLOAT(OFS_PARM0) = 1;
+                                       PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Ent_Update, "QC function CSQC_Ent_Update is missing");
                                }
                                else {
                                        PRVM_G_FLOAT(OFS_PARM0) = 0;
@@ -649,6 +740,46 @@ qboolean CL_VM_CB_LoadEdict(prvm_edict_t *ent)
 
 void Cmd_ClearCsqcFuncs (void);
 
+// returns true if the packet is valid, false if end of file is reached
+// used for dumping the CSQC download into demo files
+qboolean MakeDownloadPacket(const char *filename, unsigned char *data, unsigned long len, int crc, int cnt, sizebuf_t *buf, int protocol)
+{
+       int packetsize = buf->maxsize - 7; // byte short long
+       int npackets = (len + packetsize - 1) / (packetsize);
+
+       if(protocol == PROTOCOL_QUAKEWORLD)
+               return false; // CSQC can't run in QW anyway
+
+       SZ_Clear(buf);
+       if(cnt == 0)
+       {
+               MSG_WriteByte(buf, svc_stufftext);
+               MSG_WriteString(buf, va("\ncl_downloadbegin %lu %s\n", len, filename));
+               return true;
+       }
+       else if(cnt >= 1 && cnt <= npackets)
+       {
+               unsigned long thispacketoffset = (cnt - 1) * packetsize;
+               int thispacketsize = len - thispacketoffset;
+               if(thispacketsize > packetsize)
+                       thispacketsize = packetsize;
+
+               MSG_WriteByte(buf, svc_downloaddata);
+               MSG_WriteLong(buf, thispacketoffset);
+               MSG_WriteShort(buf, thispacketsize);
+               SZ_Write(buf, data + thispacketoffset, thispacketsize);
+
+               return true;
+       }
+       else if(cnt == npackets + 1)
+       {
+               MSG_WriteByte(buf, svc_stufftext);
+               MSG_WriteString(buf, va("\ncl_downloadfinished %lu %d\n", len, crc));
+               return true;
+       }
+       return false;
+}
+
 void CL_VM_Init (void)
 {
        const char* csprogsfn;
@@ -681,16 +812,19 @@ void CL_VM_Init (void)
        if (csprogsdata)
        {
                csprogsdatacrc = CRC_Block(csprogsdata, csprogsdatasize);
-               Mem_Free(csprogsdata);
                if (csprogsdatacrc != requiredcrc || csprogsdatasize != requiredsize)
                {
                        if (cls.demoplayback)
                        {
                                Con_Printf("^1Warning: Your %s is not the same version as the demo was recorded with (CRC/size are %i/%i but should be %i/%i)\n", csqc_progname.string, csprogsdatacrc, (int)csprogsdatasize, requiredcrc, requiredsize);
-                               return;
+                               // Mem_Free(csprogsdata);
+                               // return;
+                               // We WANT to continue here, and play the demo with different csprogs!
+                               // After all, this is just a warning. Sure things may go wrong from here.
                        }
                        else
                        {
+                               Mem_Free(csprogsdata);
                                Con_Printf("^1Your %s is not the same version as the server (CRC is %i/%i but should be %i/%i)\n", csqc_progname.string, csprogsdatacrc, (int)csprogsdatasize, requiredcrc, requiredsize);
                                CL_Disconnect();
                                return;
@@ -744,25 +878,53 @@ void CL_VM_Init (void)
                CL_VM_Error("CSQC %s ^2failed to load\n", csprogsfn);
                if(!sv.active)
                        CL_Disconnect();
+               Mem_Free(csprogsdata);
                return;
        }
 
        Con_Printf("CSQC %s ^5loaded (crc=%i, size=%i)\n", csprogsfn, csprogsdatacrc, (int)csprogsdatasize);
 
+       if(cls.demorecording)
+       {
+               if(cls.demo_lastcsprogssize != csprogsdatasize || cls.demo_lastcsprogscrc != csprogsdatacrc)
+               {
+                       int i;
+                       char buf[NET_MAXMESSAGE];
+                       sizebuf_t sb;
+                       unsigned char *demobuf; fs_offset_t demofilesize;
+
+                       sb.data = (unsigned char *) buf;
+                       sb.maxsize = sizeof(buf);
+                       i = 0;
+
+                       CL_CutDemo(&demobuf, &demofilesize);
+                       while(MakeDownloadPacket(csqc_progname.string, csprogsdata, csprogsdatasize, csprogsdatacrc, i++, &sb, cls.protocol))
+                               CL_WriteDemoMessage(&sb);
+                       CL_PasteDemo(&demobuf, &demofilesize);
+
+                       cls.demo_lastcsprogssize = csprogsdatasize;
+                       cls.demo_lastcsprogscrc = csprogsdatacrc;
+               }
+       }
+       Mem_Free(csprogsdata);
+
        // check if OP_STATE animation is possible in this dat file
        if (prog->fieldoffsets.nextthink >= 0 && prog->fieldoffsets.frame >= 0 && prog->fieldoffsets.think >= 0 && prog->globaloffsets.self >= 0)
                prog->flag |= PRVM_OP_STATE;
 
        // set time
        prog->globals.client->time = cl.time;
+       prog->globals.client->self = 0;
 
-       prog->globals.client->mapname = PRVM_SetEngineString(cl.worldmodel->name);
+       prog->globals.client->mapname = cl.worldmodel ? PRVM_SetEngineString(cl.worldmodel->name) : PRVM_SetEngineString("");
        prog->globals.client->player_localentnum = cl.playerentity;
 
        // set map description (use world entity 0)
        val = PRVM_EDICTFIELDVALUE(prog->edicts, prog->fieldoffsets.message);
        if(val)
                val->string = PRVM_SetEngineString(cl.levelname);
+       VectorCopy(cl.world.mins, prog->edicts->fields.client->mins);
+       VectorCopy(cl.world.maxs, prog->edicts->fields.client->maxs);
 
        // call the prog init
        PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Init, "QC function CSQC_Init is missing");
@@ -780,12 +942,13 @@ void CL_VM_Init (void)
 void CL_VM_ShutDown (void)
 {
        Cmd_ClearCsqcFuncs();
-       Cvar_SetValueQuick(&csqc_progcrc, -1);
-       Cvar_SetValueQuick(&csqc_progsize, -1);
+       //Cvar_SetValueQuick(&csqc_progcrc, -1);
+       //Cvar_SetValueQuick(&csqc_progsize, -1);
        if(!cl.csqc_loaded)
                return;
        CSQC_BEGIN
                prog->globals.client->time = cl.time;
+               prog->globals.client->self = 0;
                if (prog->funcoffsets.CSQC_Shutdown)
                        PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Shutdown, "QC function CSQC_Shutdown is missing");
                PRVM_ResetProg();
@@ -793,3 +956,32 @@ void CL_VM_ShutDown (void)
        Con_Print("CSQC ^1unloaded\n");
        cl.csqc_loaded = false;
 }
+
+qboolean CL_VM_GetEntitySoundOrigin(int entnum, vec3_t out)
+{
+       prvm_edict_t *ed;
+       dp_model_t *mod;
+       matrix4x4_t matrix;
+       qboolean r = 0;
+
+       CSQC_BEGIN;
+
+       // FIXME consider attachments here!
+
+       ed = PRVM_EDICT_NUM(entnum - 32768);
+
+       if(!ed->priv.required->free)
+       {
+               mod = CL_GetModelFromEdict(ed);
+               VectorCopy(ed->fields.client->origin, out);
+               if(CL_GetTagMatrix (&matrix, ed, 0) == 0)
+                       Matrix4x4_OriginFromMatrix(&matrix, out);
+               if (mod && mod->soundfromcenter)
+                       VectorMAMAM(1.0f, out, 0.5f, mod->normalmins, 0.5f, mod->normalmaxs, out);
+               r = 1;
+       }
+
+       CSQC_END;
+
+       return r;
+}