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