]> git.xonotic.org Git - xonotic/darkplaces.git/blob - csprogs.c
removed NULL checks for PRVM_EDICTFIELDVALUE/GLOBALFIELDVALUE
[xonotic/darkplaces.git] / csprogs.c
1 #include "quakedef.h"
2 #include "progsvm.h"
3 #include "clprogdefs.h"
4 #include "csprogs.h"
5 #include "cl_collision.h"
6 #include "snd_main.h"
7 #include "clvm_cmds.h"
8 #include "prvm_cmds.h"
9
10 //============================================================================
11 // Client prog handling
12 //[515]: omg !!! optimize it ! a lot of hacks here and there also :P
13
14 #define CSQC_RETURNVAL  prog->globals.generic[OFS_RETURN]
15 #define CSQC_BEGIN              csqc_tmpprog=prog;prog=0;PRVM_SetProg(PRVM_CLIENTPROG);
16 #define CSQC_END                prog=csqc_tmpprog;
17
18 static prvm_prog_t *csqc_tmpprog;
19
20 void CL_VM_PreventInformationLeaks(void)
21 {
22         if(!cl.csqc_loaded)
23                 return;
24         CSQC_BEGIN
25                 VM_ClearTraceGlobals();
26                 PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.trace_networkentity) = 0;
27         CSQC_END
28 }
29
30 //[515]: these are required funcs
31 static const char *cl_required_func[] =
32 {
33         "CSQC_Init",
34         "CSQC_InputEvent",
35         "CSQC_UpdateView",
36         "CSQC_ConsoleCommand",
37 };
38
39 static int cl_numrequiredfunc = sizeof(cl_required_func) / sizeof(char*);
40
41 void CL_VM_Error (const char *format, ...) DP_FUNC_PRINTF(1);
42 void CL_VM_Error (const char *format, ...)      //[515]: hope it will be never executed =)
43 {
44         char errorstring[4096];
45         va_list argptr;
46
47         va_start (argptr, format);
48         dpvsnprintf (errorstring, sizeof(errorstring), format, argptr);
49         va_end (argptr);
50 //      Con_Printf( "CL_VM_Error: %s\n", errorstring );
51
52         PRVM_Crash();
53         cl.csqc_loaded = false;
54
55         Cvar_SetValueQuick(&csqc_progcrc, -1);
56         Cvar_SetValueQuick(&csqc_progsize, -1);
57
58 //      Host_AbortCurrentFrame();       //[515]: hmmm... if server says it needs csqc then client MUST disconnect
59         Host_Error("CL_VM_Error: %s", errorstring);
60 }
61 void CL_VM_UpdateDmgGlobals (int dmg_take, int dmg_save, vec3_t dmg_origin)
62 {
63         if(cl.csqc_loaded)
64         {
65                 CSQC_BEGIN
66                 PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.dmg_take) = dmg_take;
67                 PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.dmg_save) = dmg_save;
68                 VectorCopy(dmg_origin, PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.dmg_origin));
69                 CSQC_END
70         }
71 }
72
73 void CSQC_UpdateNetworkTimes(double newtime, double oldtime)
74 {
75         if(!cl.csqc_loaded)
76                 return;
77         CSQC_BEGIN
78         PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.servertime) = newtime;
79         PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.serverprevtime) = oldtime;
80         PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.serverdeltatime) = newtime - oldtime;
81         CSQC_END
82 }
83
84 //[515]: set globals before calling R_UpdateView, WEIRD CRAP
85 static void CSQC_SetGlobals (void)
86 {
87         CSQC_BEGIN
88                 prog->globals.client->time = cl.time;
89                 prog->globals.client->frametime = max(0, cl.time - cl.oldtime);
90                 prog->globals.client->servercommandframe = cls.servermovesequence;
91                 prog->globals.client->clientcommandframe = cl.movecmd[0].sequence;
92                 VectorCopy(cl.viewangles, prog->globals.client->input_angles);
93                 // // FIXME: this actually belongs into getinputstate().. [12/17/2007 Black]
94                 prog->globals.client->input_buttons = cl.movecmd[0].buttons;
95                 VectorSet(prog->globals.client->input_movevalues, cl.movecmd[0].forwardmove, cl.movecmd[0].sidemove, cl.movecmd[0].upmove);
96                 VectorCopy(cl.csqc_vieworiginfromengine, cl.csqc_vieworigin);
97                 VectorCopy(cl.csqc_viewanglesfromengine, cl.csqc_viewangles);
98
99                 // LordHavoc: Spike says not to do this, but without pmove_org the
100                 // CSQC is useless as it can't alter the view origin without
101                 // completely replacing it
102                 Matrix4x4_OriginFromMatrix(&cl.entities[cl.viewentity].render.matrix, prog->globals.client->pmove_org);
103                 VectorCopy(cl.movement_velocity, prog->globals.client->pmove_vel);
104
105                 VectorCopy(cl.viewangles, PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.view_angles));
106                 VectorCopy(cl.punchangle, PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.view_punchangle));
107                 VectorCopy(cl.punchvector, PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.view_punchvector));
108                 prog->globals.client->maxclients = cl.maxclients;
109         CSQC_END
110 }
111
112 void CSQC_Predraw (prvm_edict_t *ed)
113 {
114         int b;
115         if(!ed->fields.client->predraw)
116                 return;
117         b = prog->globals.client->self;
118         prog->globals.client->self = PRVM_EDICT_TO_PROG(ed);
119         PRVM_ExecuteProgram(ed->fields.client->predraw, "CSQC_Predraw: NULL function\n");
120         prog->globals.client->self = b;
121 }
122
123 void CSQC_Think (prvm_edict_t *ed)
124 {
125         int b;
126         if(ed->fields.client->think)
127         if(ed->fields.client->nextthink && ed->fields.client->nextthink <= prog->globals.client->time)
128         {
129                 ed->fields.client->nextthink = 0;
130                 b = prog->globals.client->self;
131                 prog->globals.client->self = PRVM_EDICT_TO_PROG(ed);
132                 PRVM_ExecuteProgram(ed->fields.client->think, "CSQC_Think: NULL function\n");
133                 prog->globals.client->self = b;
134         }
135 }
136
137 extern cvar_t cl_noplayershadow;
138 extern cvar_t r_equalize_entities_fullbright;
139 qboolean CSQC_AddRenderEdict(prvm_edict_t *ed, int edictnum)
140 {
141         int renderflags;
142         int c;
143         float scale;
144         entity_render_t *entrender;
145         dp_model_t *model;
146
147         model = CL_GetModelFromEdict(ed);
148         if (!model)
149                 return false;
150
151         if (edictnum)
152         {
153                 if (r_refdef.scene.numentities >= r_refdef.scene.maxentities)
154                         return false;
155                 entrender = cl.csqcrenderentities + edictnum;
156                 r_refdef.scene.entities[r_refdef.scene.numentities++] = entrender;
157                 entrender->entitynumber = edictnum + MAX_EDICTS;
158                 //entrender->shadertime = 0; // shadertime was set by spawn()
159                 entrender->flags = 0;
160                 entrender->alpha = 1;
161                 entrender->scale = 1;
162                 VectorSet(entrender->colormod, 1, 1, 1);
163                 VectorSet(entrender->glowmod, 1, 1, 1);
164                 entrender->allowdecals = true;
165         }
166         else
167         {
168                 entrender = CL_NewTempEntity(0);
169                 if (!entrender)
170                         return false;
171         }
172
173         entrender->userwavefunc_param[0] = PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.userwavefunc_param0);
174         entrender->userwavefunc_param[1] = PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.userwavefunc_param1);
175         entrender->userwavefunc_param[2] = PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.userwavefunc_param2);
176         entrender->userwavefunc_param[3] = PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.userwavefunc_param3);
177
178         entrender->model = model;
179         entrender->skinnum = (int)ed->fields.client->skin;
180         entrender->effects |= entrender->model->effects;
181         renderflags = (int)PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.renderflags);
182         entrender->alpha = PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.alpha);
183         entrender->scale = scale = PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.scale);
184         VectorCopy(PRVM_EDICTFIELDVECTOR(ed, prog->fieldoffsets.colormod), entrender->colormod);
185         VectorCopy(PRVM_EDICTFIELDVECTOR(ed, prog->fieldoffsets.glowmod), entrender->glowmod);
186         if(ed->fields.client->effects)  entrender->effects |= (int)ed->fields.client->effects;
187         if (!entrender->alpha)
188                 entrender->alpha = 1.0f;
189         if (!entrender->scale)
190                 entrender->scale = scale = 1.0f;
191         if (!VectorLength2(entrender->colormod))
192                 VectorSet(entrender->colormod, 1, 1, 1);
193         if (!VectorLength2(entrender->glowmod))
194                 VectorSet(entrender->glowmod, 1, 1, 1);
195
196         // LordHavoc: use the CL_GetTagMatrix function on self to ensure consistent behavior (duplicate code would be bad)
197         CL_GetTagMatrix(&entrender->matrix, ed, 0);
198
199         // set up the animation data
200         VM_GenerateFrameGroupBlend(ed->priv.server->framegroupblend, ed);
201         VM_FrameBlendFromFrameGroupBlend(ed->priv.server->frameblend, ed->priv.server->framegroupblend, model);
202         VM_UpdateEdictSkeleton(ed, model, ed->priv.server->frameblend);
203         entrender->shadertime = PRVM_EDICTFIELDFLOAT(ed, prog->fieldoffsets.shadertime);
204
205         // transparent offset
206         if (renderflags & RF_USETRANSPARENTOFFSET)
207                 entrender->transparent_offset = PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.transparent_offset);
208
209         if(renderflags)
210         {
211                 if(renderflags & RF_VIEWMODEL)  entrender->flags |= RENDER_VIEWMODEL | RENDER_NODEPTHTEST;
212                 if(renderflags & RF_EXTERNALMODEL)entrender->flags |= RENDER_EXTERIORMODEL;
213                 if(renderflags & RF_NOCULL)             entrender->flags |= RENDER_NOCULL;
214                 if(renderflags & RF_DEPTHHACK)  entrender->flags |= RENDER_NODEPTHTEST;
215                 if(renderflags & RF_ADDITIVE)           entrender->flags |= RENDER_ADDITIVE;
216         }
217
218         c = (int)ed->fields.client->colormap;
219         if (c <= 0)
220                 CL_SetEntityColormapColors(entrender, -1);
221         else if (c <= cl.maxclients && cl.scores != NULL)
222                 CL_SetEntityColormapColors(entrender, cl.scores[c-1].colors);
223         else
224                 CL_SetEntityColormapColors(entrender, c);
225
226         entrender->flags &= ~(RENDER_SHADOW | RENDER_LIGHT | RENDER_NOSELFSHADOW);
227         // either fullbright or lit
228         if(!r_fullbright.integer)
229         {
230                 if (!(entrender->effects & EF_FULLBRIGHT) && !(renderflags & RF_FULLBRIGHT))
231                         entrender->flags |= RENDER_LIGHT;
232                 else if(r_equalize_entities_fullbright.integer)
233                         entrender->flags |= RENDER_LIGHT | RENDER_EQUALIZE;
234         }
235         // hide player shadow during intermission or nehahra movie
236         if (!(entrender->effects & (EF_NOSHADOW | EF_ADDITIVE | EF_NODEPTHTEST))
237          &&  (entrender->alpha >= 1)
238          && !(renderflags & RF_NOSHADOW)
239          && !(entrender->flags & RENDER_VIEWMODEL)
240          && (!(entrender->flags & RENDER_EXTERIORMODEL) || (!cl.intermission && cls.protocol != PROTOCOL_NEHAHRAMOVIE && !cl_noplayershadow.integer)))
241                 entrender->flags |= RENDER_SHADOW;
242         if (entrender->flags & RENDER_VIEWMODEL)
243                 entrender->flags |= RENDER_NOSELFSHADOW;
244         if (entrender->effects & EF_NOSELFSHADOW)
245                 entrender->flags |= RENDER_NOSELFSHADOW;
246         if (entrender->effects & EF_NODEPTHTEST)
247                 entrender->flags |= RENDER_NODEPTHTEST;
248         if (entrender->effects & EF_ADDITIVE)
249                 entrender->flags |= RENDER_ADDITIVE;
250         if (entrender->effects & EF_DOUBLESIDED)
251                 entrender->flags |= RENDER_DOUBLESIDED;
252
253         // make the other useful stuff
254         memcpy(entrender->framegroupblend, ed->priv.server->framegroupblend, sizeof(ed->priv.server->framegroupblend));
255         CL_UpdateRenderEntity(entrender);
256         // override animation data with full control
257         memcpy(entrender->frameblend, ed->priv.server->frameblend, sizeof(ed->priv.server->frameblend));
258         if (ed->priv.server->skeleton.relativetransforms)
259                 entrender->skeleton = &ed->priv.server->skeleton;
260         else
261                 entrender->skeleton = NULL;
262
263         return true;
264 }
265
266 qboolean CL_VM_InputEvent (qboolean down, int key, int ascii)
267 {
268         qboolean r;
269
270         if(!cl.csqc_loaded)
271                 return false;
272
273         CSQC_BEGIN
274                 if (!prog->funcoffsets.CSQC_InputEvent)
275                         r = false;
276                 else
277                 {
278                         prog->globals.client->time = cl.time;
279                         prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
280                         PRVM_G_FLOAT(OFS_PARM0) = !down; // 0 is down, 1 is up
281                         PRVM_G_FLOAT(OFS_PARM1) = key;
282                         PRVM_G_FLOAT(OFS_PARM2) = ascii;
283                         PRVM_ExecuteProgram(prog->funcoffsets.CSQC_InputEvent, "QC function CSQC_InputEvent is missing");
284                         r = CSQC_RETURNVAL != 0;
285                 }
286         CSQC_END
287         return r;
288 }
289
290 qboolean CL_VM_UpdateView (void)
291 {
292         vec3_t emptyvector;
293         emptyvector[0] = 0;
294         emptyvector[1] = 0;
295         emptyvector[2] = 0;
296 //      vec3_t oldangles;
297         if(!cl.csqc_loaded)
298                 return false;
299         R_TimeReport("pre-UpdateView");
300         CSQC_BEGIN
301                 //VectorCopy(cl.viewangles, oldangles);
302                 prog->globals.client->time = cl.time;
303                 prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
304                 CSQC_SetGlobals();
305                 // clear renderable entity and light lists to prevent crashes if the
306                 // CSQC_UpdateView function does not call R_ClearScene as it should
307                 r_refdef.scene.numentities = 0;
308                 r_refdef.scene.numlights = 0;
309                 // pass in width and height as parameters (EXT_CSQC_1)
310                 PRVM_G_FLOAT(OFS_PARM0) = vid.width;
311                 PRVM_G_FLOAT(OFS_PARM1) = vid.height;
312                 PRVM_ExecuteProgram(prog->funcoffsets.CSQC_UpdateView, "QC function CSQC_UpdateView is missing");
313                 //VectorCopy(oldangles, cl.viewangles);
314                 // Dresk : Reset Dmg Globals Here
315                 CL_VM_UpdateDmgGlobals(0, 0, emptyvector);
316         CSQC_END
317         R_TimeReport("UpdateView");
318         return true;
319 }
320
321 extern sizebuf_t vm_tempstringsbuf;
322 qboolean CL_VM_ConsoleCommand (const char *cmd)
323 {
324         int restorevm_tempstringsbuf_cursize;
325         qboolean r = false;
326         if(!cl.csqc_loaded)
327                 return false;
328         CSQC_BEGIN
329         if (prog->funcoffsets.CSQC_ConsoleCommand)
330         {
331                 prog->globals.client->time = cl.time;
332                 prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
333                 restorevm_tempstringsbuf_cursize = vm_tempstringsbuf.cursize;
334                 PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(cmd);
335                 PRVM_ExecuteProgram(prog->funcoffsets.CSQC_ConsoleCommand, "QC function CSQC_ConsoleCommand is missing");
336                 vm_tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize;
337                 r = CSQC_RETURNVAL != 0;
338         }
339         CSQC_END
340         return r;
341 }
342
343 qboolean CL_VM_Parse_TempEntity (void)
344 {
345         int                     t;
346         qboolean        r = false;
347         if(!cl.csqc_loaded)
348                 return false;
349         CSQC_BEGIN
350         if(prog->funcoffsets.CSQC_Parse_TempEntity)
351         {
352                 t = msg_readcount;
353                 prog->globals.client->time = cl.time;
354                 prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
355                 PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Parse_TempEntity, "QC function CSQC_Parse_TempEntity is missing");
356                 r = CSQC_RETURNVAL != 0;
357                 if(!r)
358                 {
359                         msg_readcount = t;
360                         msg_badread = false;
361                 }
362         }
363         CSQC_END
364         return r;
365 }
366
367 void CL_VM_Parse_StuffCmd (const char *msg)
368 {
369         int restorevm_tempstringsbuf_cursize;
370         if(msg[0] == 'c')
371         if(msg[1] == 's')
372         if(msg[2] == 'q')
373         if(msg[3] == 'c')
374         {
375                 // if this is setting a csqc variable, deprotect csqc_progcrc
376                 // temporarily so that it can be set by the cvar command,
377                 // and then reprotect it afterwards
378                 int crcflags = csqc_progcrc.flags;
379                 int sizeflags = csqc_progcrc.flags;
380                 csqc_progcrc.flags &= ~CVAR_READONLY;
381                 csqc_progsize.flags &= ~CVAR_READONLY;
382                 Cmd_ExecuteString (msg, src_command);
383                 csqc_progcrc.flags = crcflags;
384                 csqc_progsize.flags = sizeflags;
385                 return;
386         }
387
388         if(cls.demoplayback)
389         if(!strncmp(msg, "curl --clear_autodownload\ncurl --pak --forthismap --as ", 55))
390         {
391                 // special handling for map download commands
392                 // run these commands IMMEDIATELY, instead of waiting for a client frame
393                 // that way, there is no black screen when playing back demos
394                 // I know this is a really ugly hack, but I can't think of any better way
395                 // FIXME find the actual CAUSE of this, and make demo playback WAIT
396                 // until all maps are loaded, then remove this hack
397
398                 char buf[MAX_INPUTLINE];
399                 const char *p, *q;
400                 size_t l;
401
402                 p = msg;
403
404                 for(;;)
405                 {
406                         q = strchr(p, '\n');
407                         if(q)
408                                 l = q - p;
409                         else
410                                 l = strlen(p);
411                         if(l > sizeof(buf) - 1)
412                                 l = sizeof(buf) - 1;
413                         strlcpy(buf, p, l + 1); // strlcpy needs a + 1 as it includes the newline!
414
415                         Cmd_ExecuteString(buf, src_command);
416
417                         p += l;
418                         if(*p == '\n')
419                                 ++p; // skip the newline and continue
420                         else
421                                 break; // end of string or overflow
422                 }
423                 Cmd_ExecuteString("curl --clear_autodownload", src_command); // don't inhibit CSQC loading
424                 return;
425         }
426
427         if(!cl.csqc_loaded)
428         {
429                 Cbuf_AddText(msg);
430                 return;
431         }
432         CSQC_BEGIN
433         if(prog->funcoffsets.CSQC_Parse_StuffCmd)
434         {
435                 prog->globals.client->time = cl.time;
436                 prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
437                 restorevm_tempstringsbuf_cursize = vm_tempstringsbuf.cursize;
438                 PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(msg);
439                 PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Parse_StuffCmd, "QC function CSQC_Parse_StuffCmd is missing");
440                 vm_tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize;
441         }
442         else
443                 Cbuf_AddText(msg);
444         CSQC_END
445 }
446
447 static void CL_VM_Parse_Print (const char *msg)
448 {
449         int restorevm_tempstringsbuf_cursize;
450         prog->globals.client->time = cl.time;
451         prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
452         restorevm_tempstringsbuf_cursize = vm_tempstringsbuf.cursize;
453         PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(msg);
454         PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Parse_Print, "QC function CSQC_Parse_Print is missing");
455         vm_tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize;
456 }
457
458 void CSQC_AddPrintText (const char *msg)
459 {
460         size_t i;
461         if(!cl.csqc_loaded)
462         {
463                 Con_Print(msg);
464                 return;
465         }
466         CSQC_BEGIN
467         if(prog->funcoffsets.CSQC_Parse_Print)
468         {
469                 // FIXME: is this bugged?
470                 i = strlen(msg)-1;
471                 if(msg[i] != '\n' && msg[i] != '\r')
472                 {
473                         if(strlen(cl.csqc_printtextbuf)+i >= MAX_INPUTLINE)
474                         {
475                                 CL_VM_Parse_Print(cl.csqc_printtextbuf);
476                                 cl.csqc_printtextbuf[0] = 0;
477                         }
478                         else
479                                 strlcat(cl.csqc_printtextbuf, msg, MAX_INPUTLINE);
480                         return;
481                 }
482                 strlcat(cl.csqc_printtextbuf, msg, MAX_INPUTLINE);
483                 CL_VM_Parse_Print(cl.csqc_printtextbuf);
484                 cl.csqc_printtextbuf[0] = 0;
485         }
486         else
487                 Con_Print(msg);
488         CSQC_END
489 }
490
491 void CL_VM_Parse_CenterPrint (const char *msg)
492 {
493         int restorevm_tempstringsbuf_cursize;
494         if(!cl.csqc_loaded)
495         {
496                 SCR_CenterPrint(msg);
497                 return;
498         }
499         CSQC_BEGIN
500         if(prog->funcoffsets.CSQC_Parse_CenterPrint)
501         {
502                 prog->globals.client->time = cl.time;
503                 prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
504                 restorevm_tempstringsbuf_cursize = vm_tempstringsbuf.cursize;
505                 PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(msg);
506                 PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Parse_CenterPrint, "QC function CSQC_Parse_CenterPrint is missing");
507                 vm_tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize;
508         }
509         else
510                 SCR_CenterPrint(msg);
511         CSQC_END
512 }
513
514 void CL_VM_UpdateIntermissionState (int intermission)
515 {
516         if(cl.csqc_loaded)
517         {
518                 CSQC_BEGIN
519                 PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.intermission) = intermission;
520                 CSQC_END
521         }
522 }
523 void CL_VM_UpdateShowingScoresState (int showingscores)
524 {
525         if(cl.csqc_loaded)
526         {
527                 CSQC_BEGIN
528                 PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.sb_showscores) = showingscores;
529                 CSQC_END
530         }
531 }
532 qboolean CL_VM_Event_Sound(int sound_num, float volume, int channel, float attenuation, int ent, vec3_t pos)
533 {
534         qboolean r = false;
535         if(cl.csqc_loaded)
536         {
537                 CSQC_BEGIN
538                 if(prog->funcoffsets.CSQC_Event_Sound)
539                 {
540                         prog->globals.client->time = cl.time;
541                         prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
542                         PRVM_G_FLOAT(OFS_PARM0) = ent;
543                         PRVM_G_FLOAT(OFS_PARM1) = channel;
544                         PRVM_G_INT(OFS_PARM2) = PRVM_SetTempString(cl.sound_name[sound_num] );
545                         PRVM_G_FLOAT(OFS_PARM3) = volume;
546                         PRVM_G_FLOAT(OFS_PARM4) = attenuation;
547                         VectorCopy(pos, PRVM_G_VECTOR(OFS_PARM5) );
548                         PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Event_Sound, "QC function CSQC_Event_Sound is missing");
549                         r = CSQC_RETURNVAL != 0;
550                 }
551                 CSQC_END
552         }
553
554         return r;
555 }
556 void CL_VM_UpdateCoopDeathmatchGlobals (int gametype)
557 {
558         // Avoid global names for clean(er) coding
559         int localcoop;
560         int localdeathmatch;
561
562         if(cl.csqc_loaded)
563         {
564                 if(gametype == GAME_COOP)
565                 {
566                         localcoop = 1;
567                         localdeathmatch = 0;
568                 }
569                 else
570                 if(gametype == GAME_DEATHMATCH)
571                 {
572                         localcoop = 0;
573                         localdeathmatch = 1;
574                 }
575                 else
576                 {
577                         // How did the ServerInfo send an unknown gametype?
578                         // Better just assign the globals as 0...
579                         localcoop = 0;
580                         localdeathmatch = 0;
581                 }
582                 CSQC_BEGIN
583                 PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.coop) = localcoop;
584                 PRVM_GLOBALFIELDFLOAT(prog->globaloffsets.deathmatch) = localdeathmatch;
585                 CSQC_END
586         }
587 }
588 float CL_VM_Event (float event)         //[515]: needed ? I'd say "YES", but don't know for what :D
589 {
590         float r = 0;
591         if(!cl.csqc_loaded)
592                 return 0;
593         CSQC_BEGIN
594         if(prog->funcoffsets.CSQC_Event)
595         {
596                 prog->globals.client->time = cl.time;
597                 prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
598                 PRVM_G_FLOAT(OFS_PARM0) = event;
599                 PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Event, "QC function CSQC_Event is missing");
600                 r = CSQC_RETURNVAL;
601         }
602         CSQC_END
603         return r;
604 }
605
606 void CSQC_ReadEntities (void)
607 {
608         unsigned short entnum, oldself, realentnum;
609         if(!cl.csqc_loaded)
610         {
611                 Host_Error ("CSQC_ReadEntities: CSQC is not loaded");
612                 return;
613         }
614
615         CSQC_BEGIN
616                 prog->globals.client->time = cl.time;
617                 oldself = prog->globals.client->self;
618                 while(1)
619                 {
620                         entnum = MSG_ReadShort();
621                         if(!entnum || msg_badread)
622                                 break;
623                         realentnum = entnum & 0x7FFF;
624                         prog->globals.client->self = cl.csqc_server2csqcentitynumber[realentnum];
625                         if(entnum & 0x8000)
626                         {
627                                 if(prog->globals.client->self)
628                                 {
629                                         PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Ent_Remove, "QC function CSQC_Ent_Remove is missing");
630                                         cl.csqc_server2csqcentitynumber[realentnum] = 0;
631                                 }
632                                 else
633                                 {
634                                         // LordHavoc: removing an entity that is already gone on
635                                         // the csqc side is possible for legitimate reasons (such
636                                         // as a repeat of the remove message), so no warning is
637                                         // needed
638                                         //Con_Printf("Bad csqc_server2csqcentitynumber map\n"); //[515]: never happens ?
639                                 }
640                         }
641                         else
642                         {
643                                 if(!prog->globals.client->self)
644                                 {
645                                         if(!prog->funcoffsets.CSQC_Ent_Spawn)
646                                         {
647                                                 prvm_edict_t    *ed;
648                                                 ed = PRVM_ED_Alloc();
649                                                 ed->fields.client->entnum = realentnum;
650                                                 prog->globals.client->self = cl.csqc_server2csqcentitynumber[realentnum] = PRVM_EDICT_TO_PROG(ed);
651                                         }
652                                         else
653                                         {
654                                                 // entity( float entnum ) CSQC_Ent_Spawn;
655                                                 // the qc function should set entnum, too (this way it also can return world [2/1/2008 Andreas]
656                                                 PRVM_G_FLOAT(OFS_PARM0) = (float) realentnum;
657                                                 // make sure no one gets wrong ideas
658                                                 prog->globals.client->self = 0;
659                                                 PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Ent_Spawn, "QC function CSQC_Ent_Spawn is missing");
660                                                 prog->globals.client->self = cl.csqc_server2csqcentitynumber[realentnum] = PRVM_EDICT( PRVM_G_INT( OFS_RETURN ) );
661                                         }
662                                         PRVM_G_FLOAT(OFS_PARM0) = 1;
663                                         PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Ent_Update, "QC function CSQC_Ent_Update is missing");
664                                 }
665                                 else {
666                                         PRVM_G_FLOAT(OFS_PARM0) = 0;
667                                         PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Ent_Update, "QC function CSQC_Ent_Update is missing");
668                                 }
669                         }
670                 }
671                 prog->globals.client->self = oldself;
672         CSQC_END
673 }
674
675 void CL_VM_CB_BeginIncreaseEdicts(void)
676 {
677         // links don't survive the transition, so unlink everything
678         World_UnlinkAll(&cl.world);
679 }
680
681 void CL_VM_CB_EndIncreaseEdicts(void)
682 {
683         int i;
684         prvm_edict_t *ent;
685
686         // link every entity except world
687         for (i = 1, ent = prog->edicts;i < prog->num_edicts;i++, ent++)
688                 if (!ent->priv.server->free)
689                         CL_LinkEdict(ent);
690 }
691
692 void CL_VM_CB_InitEdict(prvm_edict_t *e)
693 {
694         int edictnum = PRVM_NUM_FOR_EDICT(e);
695         entity_render_t *entrender;
696         CL_ExpandCSQCRenderEntities(edictnum);
697         entrender = cl.csqcrenderentities + edictnum;
698         e->priv.server->move = false; // don't move on first frame
699         memset(entrender, 0, sizeof(*entrender));
700         entrender->shadertime = cl.time;
701 }
702
703 extern void R_DecalSystem_Reset(decalsystem_t *decalsystem);
704
705 void CL_VM_CB_FreeEdict(prvm_edict_t *ed)
706 {
707         entity_render_t *entrender = cl.csqcrenderentities + PRVM_NUM_FOR_EDICT(ed);
708         R_DecalSystem_Reset(&entrender->decalsystem);
709         memset(entrender, 0, sizeof(*entrender));
710         World_UnlinkEdict(ed);
711         memset(ed->fields.client, 0, sizeof(*ed->fields.client));
712         VM_RemoveEdictSkeleton(ed);
713         World_Physics_RemoveFromEntity(&cl.world, ed);
714         World_Physics_RemoveJointFromEntity(&cl.world, ed);
715 }
716
717 void CL_VM_CB_CountEdicts(void)
718 {
719         int             i;
720         prvm_edict_t    *ent;
721         int             active = 0, models = 0, solid = 0;
722
723         for (i=0 ; i<prog->num_edicts ; i++)
724         {
725                 ent = PRVM_EDICT_NUM(i);
726                 if (ent->priv.server->free)
727                         continue;
728                 active++;
729                 if (ent->fields.client->solid)
730                         solid++;
731                 if (ent->fields.client->model)
732                         models++;
733         }
734
735         Con_Printf("num_edicts:%3i\n", prog->num_edicts);
736         Con_Printf("active    :%3i\n", active);
737         Con_Printf("view      :%3i\n", models);
738         Con_Printf("touch     :%3i\n", solid);
739 }
740
741 qboolean CL_VM_CB_LoadEdict(prvm_edict_t *ent)
742 {
743         return true;
744 }
745
746 void Cmd_ClearCsqcFuncs (void);
747
748 // returns true if the packet is valid, false if end of file is reached
749 // used for dumping the CSQC download into demo files
750 qboolean MakeDownloadPacket(const char *filename, unsigned char *data, size_t len, int crc, int cnt, sizebuf_t *buf, int protocol)
751 {
752         int packetsize = buf->maxsize - 7; // byte short long
753         int npackets = (len + packetsize - 1) / (packetsize);
754
755         if(protocol == PROTOCOL_QUAKEWORLD)
756                 return false; // CSQC can't run in QW anyway
757
758         SZ_Clear(buf);
759         if(cnt == 0)
760         {
761                 MSG_WriteByte(buf, svc_stufftext);
762                 MSG_WriteString(buf, va("\ncl_downloadbegin %lu %s\n", (unsigned long)len, filename));
763                 return true;
764         }
765         else if(cnt >= 1 && cnt <= npackets)
766         {
767                 unsigned long thispacketoffset = (cnt - 1) * packetsize;
768                 int thispacketsize = len - thispacketoffset;
769                 if(thispacketsize > packetsize)
770                         thispacketsize = packetsize;
771
772                 MSG_WriteByte(buf, svc_downloaddata);
773                 MSG_WriteLong(buf, thispacketoffset);
774                 MSG_WriteShort(buf, thispacketsize);
775                 SZ_Write(buf, data + thispacketoffset, thispacketsize);
776
777                 return true;
778         }
779         else if(cnt == npackets + 1)
780         {
781                 MSG_WriteByte(buf, svc_stufftext);
782                 MSG_WriteString(buf, va("\ncl_downloadfinished %lu %d\n", (unsigned long)len, crc));
783                 return true;
784         }
785         return false;
786 }
787
788 #define CL_REQFIELDS (sizeof(cl_reqfields) / sizeof(prvm_required_field_t))
789
790 prvm_required_field_t cl_reqfields[] =
791 {
792         {ev_entity, "groundentity"},
793         {ev_entity, "tag_entity"},
794         {ev_float, "alpha"},
795         {ev_float, "dimension_hit"},
796         {ev_float, "dimension_solid"},
797         {ev_float, "dphitcontentsmask"},
798         {ev_float, "frame"},
799         {ev_float, "frame1time"},
800         {ev_float, "frame2"},
801         {ev_float, "frame2time"},
802         {ev_float, "frame3"},
803         {ev_float, "frame3time"},
804         {ev_float, "frame4"},
805         {ev_float, "frame4time"},
806         {ev_float, "gravity"},
807         {ev_float, "idealpitch"},
808         {ev_float, "lerpfrac"},
809         {ev_float, "lerpfrac3"},
810         {ev_float, "lerpfrac4"},
811         {ev_float, "movetype"}, // used by ODE code
812         {ev_float, "nextthink"},
813         {ev_float, "pitch_speed"},
814         {ev_float, "renderflags"},
815         {ev_float, "scale"},
816         {ev_float, "shadertime"},
817         {ev_float, "skeletonindex"},
818         {ev_float, "solid"}, // used by ODE code
819         {ev_float, "tag_index"},
820         {ev_float, "userwavefunc_param0"},
821         {ev_float, "userwavefunc_param1"},
822         {ev_float, "userwavefunc_param2"},
823         {ev_float, "userwavefunc_param3"},
824         {ev_function, "camera_transform"},
825         {ev_function, "think"},
826         {ev_string, "classname"},
827         {ev_vector, "colormod"},
828         {ev_vector, "glowmod"},
829
830         // physics
831         //{ev_float, "solid"},
832         //{ev_float, "movetype"},
833         //{ev_float, "modelindex"},
834         {ev_vector, "mass"},
835         //{ev_vector, "origin"},
836         //{ev_vector, "velocity"},
837         //{ev_vector, "axis_forward"},
838         //{ev_vector, "axis_left"},
839         //{ev_vector, "axis_up"},
840         //{ev_vector, "spinvelocity"},
841         //{ev_vector, "angles"},
842         //{ev_vector, "avelocity"},
843 };
844
845 #define CL_REQGLOBALS (sizeof(cl_reqglobals) / sizeof(prvm_required_field_t))
846
847 prvm_required_field_t cl_reqglobals[] =
848 {
849         {ev_entity, "self"},
850         {ev_entity, "trace_ent"},
851         {ev_entity, "trace_networkentity"},
852         {ev_float, "coop"},
853         {ev_float, "deathmatch"},
854         {ev_float, "dmg_save"},
855         {ev_float, "dmg_take"},
856         {ev_float, "drawfont"},
857         {ev_float, "drawfontscale"},
858         {ev_float, "gettaginfo_parent"},
859         {ev_float, "intermission"},
860         {ev_float, "particle_airfriction"},
861         {ev_float, "particle_alpha"},
862         {ev_float, "particle_alphafade"},
863         {ev_float, "particle_angle"},
864         {ev_float, "particle_blendmode"},
865         {ev_float, "particle_bounce"},
866         {ev_float, "particle_delaycollision"},
867         {ev_float, "particle_delayspawn"},
868         {ev_float, "particle_gravity"},
869         {ev_float, "particle_liquidfriction"},
870         {ev_float, "particle_orientation"},
871         {ev_float, "particle_originjitter"},
872         {ev_float, "particle_qualityreduction"},
873         {ev_float, "particle_size"},
874         {ev_float, "particle_sizeincrease"},
875         {ev_float, "particle_spin"},
876         {ev_float, "particle_stainalpha"},
877         {ev_float, "particle_stainsize"},
878         {ev_float, "particle_staintex"},
879         {ev_float, "particle_staintex"},
880         {ev_float, "particle_stretch"},
881         {ev_float, "particle_tex"},
882         {ev_float, "particle_time"},
883         {ev_float, "particle_type"},
884         {ev_float, "particle_velocityjitter"},
885         {ev_float, "particles_alphamax"},
886         {ev_float, "particles_alphamin"},
887         {ev_float, "require_spawnfunc_prefix"},
888         {ev_float, "sb_showscores"},
889         {ev_float, "serverdeltatime"},
890         {ev_float, "serverprevtime"},
891         {ev_float, "servertime"},
892         {ev_float, "time"},
893         {ev_float, "trace_allsolid"},
894         {ev_float, "trace_dphitcontents"},
895         {ev_float, "trace_dphitq3surfaceflags"},
896         {ev_float, "trace_dpstartcontents"},
897         {ev_float, "trace_fraction"},
898         {ev_float, "trace_inopen"},
899         {ev_float, "trace_inwater"},
900         {ev_float, "trace_plane_dist"},
901         {ev_float, "trace_startsolid"},
902         {ev_float, "transparent_offset"},
903         {ev_string, "SV_InitCmd"},
904         {ev_string, "gettaginfo_name"},
905         {ev_string, "trace_dphittexturename"},
906         {ev_string, "worldstatus"},
907         {ev_vector, "dmg_origin"},
908         {ev_vector, "gettaginfo_forward"},
909         {ev_vector, "gettaginfo_offset"},
910         {ev_vector, "gettaginfo_right"},
911         {ev_vector, "gettaginfo_up"},
912         {ev_vector, "particle_color1"},
913         {ev_vector, "particle_color2"},
914         {ev_vector, "particle_staincolor1"},
915         {ev_vector, "particle_staincolor2"},
916         {ev_vector, "particles_colormax"},
917         {ev_vector, "particles_colormin"},
918         {ev_vector, "trace_endpos"},
919         {ev_vector, "trace_plane_normal"},
920         {ev_vector, "v_forward"},
921         {ev_vector, "v_right"},
922         {ev_vector, "v_up"},
923         {ev_vector, "view_angles"},
924         {ev_vector, "view_punchangle"},
925         {ev_vector, "view_punchvector"},
926 };
927
928 void CL_VM_Init (void)
929 {
930         const char* csprogsfn;
931         unsigned char *csprogsdata;
932         fs_offset_t csprogsdatasize;
933         int csprogsdatacrc, requiredcrc;
934         int requiredsize;
935
936         // reset csqc_progcrc after reading it, so that changing servers doesn't
937         // expect csqc on the next server
938         requiredcrc = csqc_progcrc.integer;
939         requiredsize = csqc_progsize.integer;
940         Cvar_SetValueQuick(&csqc_progcrc, -1);
941         Cvar_SetValueQuick(&csqc_progsize, -1);
942
943         // if the server is not requesting a csprogs, then we're done here
944         if (requiredcrc < 0)
945                 return;
946
947         // see if the requested csprogs.dat file matches the requested crc
948         csprogsdatacrc = -1;
949         csprogsfn = va("dlcache/%s.%i.%i", csqc_progname.string, requiredsize, requiredcrc);
950         csprogsdata = FS_LoadFile(csprogsfn, tempmempool, true, &csprogsdatasize);
951         if (!csprogsdata)
952         {
953                 csprogsfn = csqc_progname.string;
954                 csprogsdata = FS_LoadFile(csprogsfn, tempmempool, true, &csprogsdatasize);
955         }
956         if (csprogsdata)
957         {
958                 csprogsdatacrc = CRC_Block(csprogsdata, (size_t)csprogsdatasize);
959                 if (csprogsdatacrc != requiredcrc || csprogsdatasize != requiredsize)
960                 {
961                         if (cls.demoplayback)
962                         {
963                                 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);
964                                 // Mem_Free(csprogsdata);
965                                 // return;
966                                 // We WANT to continue here, and play the demo with different csprogs!
967                                 // After all, this is just a warning. Sure things may go wrong from here.
968                         }
969                         else
970                         {
971                                 Mem_Free(csprogsdata);
972                                 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);
973                                 CL_Disconnect();
974                                 return;
975                         }
976                 }
977         }
978         else
979         {
980                 if (requiredcrc >= 0)
981                 {
982                         if (cls.demoplayback)
983                                 Con_Printf("CL_VM_Init: demo requires CSQC, but \"%s\" wasn't found\n", csqc_progname.string);
984                         else
985                                 Con_Printf("CL_VM_Init: server requires CSQC, but \"%s\" wasn't found\n", csqc_progname.string);
986                         CL_Disconnect();
987                 }
988                 return;
989         }
990
991         PRVM_Begin;
992         PRVM_InitProg(PRVM_CLIENTPROG);
993
994         // allocate the mempools
995         prog->progs_mempool = Mem_AllocPool(csqc_progname.string, 0, NULL);
996         prog->headercrc = CL_PROGHEADER_CRC;
997         prog->edictprivate_size = 0; // no private struct used
998         prog->name = CL_NAME;
999         prog->num_edicts = 1;
1000         prog->max_edicts = 512;
1001         prog->limit_edicts = CL_MAX_EDICTS;
1002         prog->reserved_edicts = 0;
1003         prog->edictprivate_size = sizeof(edict_engineprivate_t);
1004         // TODO: add a shared extension string #define and add real support for csqc extension strings [12/5/2007 Black]
1005         prog->extensionstring = vm_sv_extensions;
1006         prog->builtins = vm_cl_builtins;
1007         prog->numbuiltins = vm_cl_numbuiltins;
1008         prog->begin_increase_edicts = CL_VM_CB_BeginIncreaseEdicts;
1009         prog->end_increase_edicts = CL_VM_CB_EndIncreaseEdicts;
1010         prog->init_edict = CL_VM_CB_InitEdict;
1011         prog->free_edict = CL_VM_CB_FreeEdict;
1012         prog->count_edicts = CL_VM_CB_CountEdicts;
1013         prog->load_edict = CL_VM_CB_LoadEdict;
1014         prog->init_cmd = VM_CL_Cmd_Init;
1015         prog->reset_cmd = VM_CL_Cmd_Reset;
1016         prog->error_cmd = CL_VM_Error;
1017         prog->ExecuteProgram = CLVM_ExecuteProgram;
1018
1019         PRVM_LoadProgs(csprogsfn, cl_numrequiredfunc, cl_required_func, CL_REQFIELDS, cl_reqfields, CL_REQGLOBALS, cl_reqglobals);
1020
1021         if (!prog->loaded)
1022         {
1023                 CL_VM_Error("CSQC %s ^2failed to load\n", csprogsfn);
1024                 if(!sv.active)
1025                         CL_Disconnect();
1026                 Mem_Free(csprogsdata);
1027                 return;
1028         }
1029
1030         Con_DPrintf("CSQC %s ^5loaded (crc=%i, size=%i)\n", csprogsfn, csprogsdatacrc, (int)csprogsdatasize);
1031
1032         if(cls.demorecording)
1033         {
1034                 if(cls.demo_lastcsprogssize != csprogsdatasize || cls.demo_lastcsprogscrc != csprogsdatacrc)
1035                 {
1036                         int i;
1037                         static char buf[NET_MAXMESSAGE];
1038                         sizebuf_t sb;
1039                         unsigned char *demobuf; fs_offset_t demofilesize;
1040
1041                         sb.data = (unsigned char *) buf;
1042                         sb.maxsize = sizeof(buf);
1043                         i = 0;
1044
1045                         CL_CutDemo(&demobuf, &demofilesize);
1046                         while(MakeDownloadPacket(csqc_progname.string, csprogsdata, (size_t)csprogsdatasize, csprogsdatacrc, i++, &sb, cls.protocol))
1047                                 CL_WriteDemoMessage(&sb);
1048                         CL_PasteDemo(&demobuf, &demofilesize);
1049
1050                         cls.demo_lastcsprogssize = csprogsdatasize;
1051                         cls.demo_lastcsprogscrc = csprogsdatacrc;
1052                 }
1053         }
1054         Mem_Free(csprogsdata);
1055
1056         // check if OP_STATE animation is possible in this dat file
1057         if (prog->fieldoffsets.nextthink >= 0 && prog->fieldoffsets.frame >= 0 && prog->fieldoffsets.think >= 0 && prog->globaloffsets.self >= 0)
1058                 prog->flag |= PRVM_OP_STATE;
1059
1060         // set time
1061         prog->globals.client->time = cl.time;
1062         prog->globals.client->self = 0;
1063
1064         prog->globals.client->mapname = PRVM_SetEngineString(cl.worldname);
1065         prog->globals.client->player_localentnum = cl.playerentity;
1066
1067         // set map description (use world entity 0)
1068         PRVM_EDICTFIELDSTRING(prog->edicts, prog->fieldoffsets.message) = PRVM_SetEngineString(cl.worldmessage);
1069         VectorCopy(cl.world.mins, prog->edicts->fields.client->mins);
1070         VectorCopy(cl.world.maxs, prog->edicts->fields.client->maxs);
1071
1072         // call the prog init
1073         PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Init, "QC function CSQC_Init is missing");
1074
1075         PRVM_End;
1076         cl.csqc_loaded = true;
1077
1078         cl.csqc_vidvars.drawcrosshair = false;
1079         cl.csqc_vidvars.drawenginesbar = false;
1080
1081         // Update Coop and Deathmatch Globals (at this point the client knows them from ServerInfo)
1082         CL_VM_UpdateCoopDeathmatchGlobals(cl.gametype);
1083 }
1084
1085 void CL_VM_ShutDown (void)
1086 {
1087         Cmd_ClearCsqcFuncs();
1088         //Cvar_SetValueQuick(&csqc_progcrc, -1);
1089         //Cvar_SetValueQuick(&csqc_progsize, -1);
1090         if(!cl.csqc_loaded)
1091                 return;
1092         CSQC_BEGIN
1093                 prog->globals.client->time = cl.time;
1094                 prog->globals.client->self = 0;
1095                 if (prog->funcoffsets.CSQC_Shutdown)
1096                         PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Shutdown, "QC function CSQC_Shutdown is missing");
1097                 PRVM_ResetProg();
1098         CSQC_END
1099         Con_DPrint("CSQC ^1unloaded\n");
1100         cl.csqc_loaded = false;
1101 }
1102
1103 qboolean CL_VM_GetEntitySoundOrigin(int entnum, vec3_t out)
1104 {
1105         prvm_edict_t *ed;
1106         dp_model_t *mod;
1107         matrix4x4_t matrix;
1108         qboolean r = 0;
1109
1110         CSQC_BEGIN;
1111
1112         // FIXME consider attachments here!
1113
1114         ed = PRVM_EDICT_NUM(entnum - MAX_EDICTS);
1115
1116         if(!ed->priv.required->free)
1117         {
1118                 mod = CL_GetModelFromEdict(ed);
1119                 VectorCopy(ed->fields.client->origin, out);
1120                 if(CL_GetTagMatrix (&matrix, ed, 0) == 0)
1121                         Matrix4x4_OriginFromMatrix(&matrix, out);
1122                 if (mod && mod->soundfromcenter)
1123                         VectorMAMAM(1.0f, out, 0.5f, mod->normalmins, 0.5f, mod->normalmaxs, out);
1124                 r = 1;
1125         }
1126
1127         CSQC_END;
1128
1129         return r;
1130 }
1131
1132 qboolean CL_VM_TransformView(int entnum, matrix4x4_t *viewmatrix, mplane_t *clipplane, vec3_t visorigin)
1133 {
1134         qboolean ret = false;
1135         prvm_edict_t *ed;
1136         vec3_t forward, left, up, origin, ang;
1137         matrix4x4_t mat, matq;
1138
1139         CSQC_BEGIN
1140                 ed = PRVM_EDICT_NUM(entnum);
1141                 // camera:
1142                 //   camera_transform
1143                 if(PRVM_EDICTFIELDFUNCTION(ed, prog->fieldoffsets.camera_transform))
1144                 {
1145                         ret = true;
1146                         if(viewmatrix || clipplane || visorigin)
1147                         {
1148                                 Matrix4x4_ToVectors(viewmatrix, forward, left, up, origin);
1149                                 AnglesFromVectors(ang, forward, up, false);
1150                                 prog->globals.client->time = cl.time;
1151                                 prog->globals.client->self = entnum;
1152                                 VectorCopy(origin, PRVM_G_VECTOR(OFS_PARM0));
1153                                 VectorCopy(ang, PRVM_G_VECTOR(OFS_PARM1));
1154                                 VectorCopy(forward, PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.v_forward));
1155                                 VectorScale(left, -1, PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.v_right));
1156                                 VectorCopy(up, PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.v_up));
1157                                 VectorCopy(origin, PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.trace_endpos));
1158                                 PRVM_ExecuteProgram(PRVM_EDICTFIELDFUNCTION(ed, prog->fieldoffsets.camera_transform), "QC function e.camera_transform is missing");
1159                                 VectorCopy(PRVM_G_VECTOR(OFS_RETURN), origin);
1160                                 VectorCopy(PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.v_forward), forward);
1161                                 VectorScale(PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.v_right), -1, left);
1162                                 VectorCopy(PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.v_up), up);
1163                                 VectorCopy(PRVM_GLOBALFIELDVECTOR(prog->globaloffsets.trace_endpos), visorigin);
1164                                 Matrix4x4_Invert_Full(&mat, viewmatrix);
1165                                 Matrix4x4_FromVectors(viewmatrix, forward, left, up, origin);
1166                                 Matrix4x4_Concat(&matq, viewmatrix, &mat);
1167                                 Matrix4x4_TransformPositivePlane(&matq, clipplane->normal[0], clipplane->normal[1], clipplane->normal[2], clipplane->dist, &clipplane->normal[0]);
1168                         }
1169                 }
1170         CSQC_END
1171
1172         return ret;
1173 }