]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - csprogs.c
56b23ce7c86c3a9d635808e073425f4ec3e2cd0e
[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
9 //============================================================================
10 // Client prog handling
11 //[515]: omg !!! optimize it ! a lot of hacks here and there also :P
12
13 #define CSQC_RETURNVAL  prog->globals.generic[OFS_RETURN]
14 #define CSQC_BEGIN              csqc_tmpprog=prog;prog=0;PRVM_SetProg(PRVM_CLIENTPROG);
15 #define CSQC_END                VM_ClearTraceGlobals(); prog=csqc_tmpprog;
16 // TODO check if the clearing of trace globals takes too much CPU. If it does,
17 // perform it before console command processing instead.
18
19 static prvm_prog_t *csqc_tmpprog;
20
21 //[515]: these are required funcs
22 static char *cl_required_func[] =
23 {
24         "CSQC_Init",
25         "CSQC_InputEvent",
26         "CSQC_UpdateView",
27         "CSQC_ConsoleCommand",
28 };
29
30 static int cl_numrequiredfunc = sizeof(cl_required_func) / sizeof(char*);
31
32 void CL_VM_Error (const char *format, ...) DP_FUNC_PRINTF(1);
33 void CL_VM_Error (const char *format, ...)      //[515]: hope it will be never executed =)
34 {
35         char errorstring[4096];
36         va_list argptr;
37
38         va_start (argptr, format);
39         dpvsnprintf (errorstring, sizeof(errorstring), format, argptr);
40         va_end (argptr);
41 //      Con_Printf( "CL_VM_Error: %s\n", errorstring );
42
43         PRVM_Crash();
44         cl.csqc_loaded = false;
45
46         Cvar_SetValueQuick(&csqc_progcrc, -1);
47         Cvar_SetValueQuick(&csqc_progsize, -1);
48
49 //      Host_AbortCurrentFrame();       //[515]: hmmm... if server says it needs csqc then client MUST disconnect
50         Host_Error("CL_VM_Error: %s", errorstring);
51 }
52 void CL_VM_UpdateDmgGlobals (int dmg_take, int dmg_save, vec3_t dmg_origin)
53 {
54         prvm_eval_t *val;
55         if(cl.csqc_loaded)
56         {
57                 CSQC_BEGIN
58                 val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.dmg_take);
59                 if(val)
60                         val->_float = dmg_take;
61                 val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.dmg_save);
62                 if(val)
63                         val->_float = dmg_save;
64                 val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.dmg_origin);
65                 if(val)
66                 {
67                         val->vector[0] = dmg_origin[0];
68                         val->vector[1] = dmg_origin[1];
69                         val->vector[2] = dmg_origin[2];
70                 }
71                 CSQC_END
72         }
73 }
74
75 void CSQC_UpdateNetworkTimes(double newtime, double oldtime)
76 {
77         prvm_eval_t *val;
78         if(!cl.csqc_loaded)
79                 return;
80         CSQC_BEGIN
81         if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.servertime)))
82                 val->_float = newtime;
83         if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.serverprevtime)))
84                 val->_float = oldtime;
85         if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.serverdeltatime)))
86                 val->_float = newtime - oldtime;
87         CSQC_END
88 }
89
90 //[515]: set globals before calling R_UpdateView, WEIRD CRAP
91 static void CSQC_SetGlobals (void)
92 {
93         prvm_eval_t *val;
94         CSQC_BEGIN
95                 prog->globals.client->time = cl.time;
96                 prog->globals.client->frametime = max(0, cl.time - cl.oldtime);
97                 prog->globals.client->servercommandframe = cls.servermovesequence;
98                 prog->globals.client->clientcommandframe = cl.movecmd[0].sequence;
99                 VectorCopy(cl.viewangles, prog->globals.client->input_angles);
100                 VectorCopy(cl.viewangles, cl.csqc_angles);
101                 // // FIXME: this actually belongs into getinputstate().. [12/17/2007 Black]
102                 prog->globals.client->input_buttons = cl.movecmd[0].buttons;
103                 VectorSet(prog->globals.client->input_movevalues, cl.movecmd[0].forwardmove, cl.movecmd[0].sidemove, cl.movecmd[0].upmove);
104                 //VectorCopy(cl.movement_origin, cl.csqc_origin);
105                 Matrix4x4_OriginFromMatrix(&cl.entities[cl.viewentity].render.matrix, cl.csqc_origin);
106
107                 // LordHavoc: Spike says not to do this, but without pmove_org the
108                 // CSQC is useless as it can't alter the view origin without
109                 // completely replacing it
110                 VectorCopy(cl.csqc_origin, prog->globals.client->pmove_org);
111                 VectorCopy(cl.movement_velocity, prog->globals.client->pmove_vel);
112
113                 if ((val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.view_angles)))
114                         VectorCopy(cl.viewangles, val->vector);
115                 prog->globals.client->maxclients = cl.maxclients;
116         CSQC_END
117 }
118
119 void CSQC_Predraw (prvm_edict_t *ed)
120 {
121         int b;
122         if(!ed->fields.client->predraw)
123                 return;
124         b = prog->globals.client->self;
125         prog->globals.client->self = PRVM_EDICT_TO_PROG(ed);
126         PRVM_ExecuteProgram(ed->fields.client->predraw, "CSQC_Predraw: NULL function\n");
127         prog->globals.client->self = b;
128 }
129
130 void CSQC_Think (prvm_edict_t *ed)
131 {
132         int b;
133         if(ed->fields.client->think)
134         if(ed->fields.client->nextthink && ed->fields.client->nextthink <= prog->globals.client->time)
135         {
136                 ed->fields.client->nextthink = 0;
137                 b = prog->globals.client->self;
138                 prog->globals.client->self = PRVM_EDICT_TO_PROG(ed);
139                 PRVM_ExecuteProgram(ed->fields.client->think, "CSQC_Think: NULL function\n");
140                 prog->globals.client->self = b;
141         }
142 }
143
144 extern cvar_t cl_noplayershadow;
145 qboolean CSQC_AddRenderEdict(prvm_edict_t *ed)
146 {
147         int renderflags;
148         int c;
149         float scale;
150         prvm_eval_t *val;
151         entity_render_t *entrender;
152         dp_model_t *model;
153         matrix4x4_t tagmatrix, matrix2;
154
155         model = CL_GetModelFromEdict(ed);
156         if (!model)
157                 return false;
158
159         entrender = CL_NewTempEntity(0);
160         if (!entrender)
161                 return false;
162
163         entrender->model = model;
164         entrender->skinnum = (int)ed->fields.client->skin;
165         entrender->effects |= entrender->model->effects;
166         scale = 1;
167         renderflags = 0;
168         if((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.renderflags)) && val->_float)     renderflags = (int)val->_float;
169         if((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.alpha)) && val->_float)           entrender->alpha = val->_float;
170         if((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.scale)) && val->_float)           entrender->scale = scale = val->_float;
171         if((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.colormod)) && VectorLength2(val->vector)) VectorCopy(val->vector, entrender->colormod);
172         if((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.effects)) && val->_float) entrender->effects |= (int)val->_float;
173         if((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.tag_entity)) && val->edict)
174         {
175                 int tagentity;
176                 int tagindex = 0;
177                 tagentity = val->edict;
178                 if((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.tag_index)) && val->_float)
179                         tagindex = (int)val->_float;
180                 CL_GetTagMatrix (&tagmatrix, PRVM_PROG_TO_EDICT(tagentity), tagindex);
181         }
182         else
183                 Matrix4x4_CreateIdentity(&tagmatrix);
184
185         if (renderflags & RF_USEAXIS)
186         {
187                 vec3_t left;
188                 VectorNegate(prog->globals.client->v_right, left);
189                 Matrix4x4_FromVectors(&matrix2, prog->globals.client->v_forward, left, prog->globals.client->v_up, ed->fields.client->origin);
190                 Matrix4x4_Scale(&matrix2, scale, 1);
191         }
192         else
193         {
194                 vec3_t angles;
195                 VectorCopy(ed->fields.client->angles, angles);
196                 // if model is alias, reverse pitch direction
197                 if (entrender->model->type == mod_alias)
198                         angles[0] = -angles[0];
199
200                 // set up the render matrix
201                 Matrix4x4_CreateFromQuakeEntity(&matrix2, ed->fields.client->origin[0], ed->fields.client->origin[1], ed->fields.client->origin[2], angles[0], angles[1], angles[2], scale);
202         }
203
204         // set up the animation data
205         // self.frame is the interpolation target (new frame)
206         // self.frame1time is the animation base time for the interpolation target
207         // self.frame2 is the interpolation start (previous frame)
208         // self.frame2time is the animation base time for the interpolation start
209         // self.lerpfrac is the interpolation strength for self.frame
210         // 3+ are for additional blends (the main use for this feature is lerping
211         // pitch angle on a player model where the animator set up 5 sets of
212         // animations and the csqc simply lerps between sets)
213         entrender->framegroupblend[0].frame = entrender->framegroupblend[1].frame = (int) ed->fields.client->frame;
214         if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame2))) entrender->framegroupblend[1].frame = (int) val->_float;
215         if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame3))) entrender->framegroupblend[2].frame = (int) val->_float;
216         if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame4))) entrender->framegroupblend[3].frame = (int) val->_float;
217         if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame1time))) entrender->framegroupblend[0].start = val->_float;
218         if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame2time))) entrender->framegroupblend[1].start = val->_float;
219         if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame3time))) entrender->framegroupblend[2].start = val->_float;
220         if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame4time))) entrender->framegroupblend[3].start = val->_float;
221         if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.lerpfrac))) entrender->framegroupblend[0].lerp = val->_float;
222         if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.lerpfrac3))) entrender->framegroupblend[2].lerp = val->_float;
223         if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.lerpfrac4))) entrender->framegroupblend[3].lerp = val->_float;
224         if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.shadertime))) entrender->shadertime = val->_float;
225         // assume that the (missing) lerpfrac2 is whatever remains after lerpfrac+lerpfrac3+lerpfrac4 are summed
226         entrender->framegroupblend[1].lerp = 1 - entrender->framegroupblend[0].lerp - entrender->framegroupblend[2].lerp - entrender->framegroupblend[3].lerp;
227
228         // concat the matrices to make the entity relative to its tag
229         Matrix4x4_Concat(&entrender->matrix, &tagmatrix, &matrix2);
230
231         if(renderflags)
232         {
233                 if(renderflags & RF_VIEWMODEL)  entrender->flags |= RENDER_VIEWMODEL;
234                 if(renderflags & RF_EXTERNALMODEL)entrender->flags |= RENDER_EXTERIORMODEL;
235                 if(renderflags & RF_DEPTHHACK)  entrender->effects |= EF_NODEPTHTEST;
236                 if(renderflags & RF_ADDITIVE)           entrender->effects |= EF_ADDITIVE;
237         }
238
239         c = (int)ed->fields.client->colormap;
240         if (c <= 0)
241                 CL_SetEntityColormapColors(entrender, -1);
242         else if (c <= cl.maxclients && cl.scores != NULL)
243                 CL_SetEntityColormapColors(entrender, cl.scores[c-1].colors);
244         else
245                 CL_SetEntityColormapColors(entrender, c);
246
247         entrender->flags &= ~(RENDER_SHADOW | RENDER_LIGHT | RENDER_NOSELFSHADOW);
248         // either fullbright or lit
249         if (!(entrender->effects & EF_FULLBRIGHT) && !r_fullbright.integer)
250                 entrender->flags |= RENDER_LIGHT;
251         // hide player shadow during intermission or nehahra movie
252         if (!(entrender->effects & (EF_NOSHADOW | EF_ADDITIVE | EF_NODEPTHTEST))
253          &&  (entrender->alpha >= 1)
254          && !(entrender->flags & RENDER_VIEWMODEL)
255          && (!(entrender->flags & RENDER_EXTERIORMODEL) || (!cl.intermission && cls.protocol != PROTOCOL_NEHAHRAMOVIE && !cl_noplayershadow.integer)))
256                 entrender->flags |= RENDER_SHADOW;
257         if (entrender->flags & RENDER_VIEWMODEL)
258                 entrender->flags |= RENDER_NOSELFSHADOW;
259         if (entrender->effects & EF_NOSELFSHADOW)
260                 entrender->flags |= RENDER_NOSELFSHADOW;
261
262         // make the other useful stuff
263         CL_UpdateRenderEntity(entrender);
264
265         return true;
266 }
267
268 qboolean CL_VM_InputEvent (qboolean down, int key, int ascii)
269 {
270         qboolean r;
271
272         if(!cl.csqc_loaded)
273                 return false;
274
275         CSQC_BEGIN
276                 if (!prog->funcoffsets.CSQC_InputEvent)
277                         r = false;
278                 else
279                 {
280                         prog->globals.client->time = cl.time;
281                         prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
282                         PRVM_G_FLOAT(OFS_PARM0) = !down; // 0 is down, 1 is up
283                         PRVM_G_FLOAT(OFS_PARM1) = key;
284                         PRVM_G_FLOAT(OFS_PARM2) = ascii;
285                         PRVM_ExecuteProgram(prog->funcoffsets.CSQC_InputEvent, "QC function CSQC_InputEvent is missing");
286                         r = CSQC_RETURNVAL != 0;
287                 }
288         CSQC_END
289         return r;
290 }
291
292 qboolean CL_VM_UpdateView (void)
293 {
294         vec3_t emptyvector;
295         emptyvector[0] = 0;
296         emptyvector[1] = 0;
297         emptyvector[2] = 0;
298 //      vec3_t oldangles;
299         if(!cl.csqc_loaded)
300                 return false;
301         CSQC_BEGIN
302                 //VectorCopy(cl.viewangles, oldangles);
303                 prog->globals.client->time = cl.time;
304                 prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
305                 CSQC_SetGlobals();
306                 // clear renderable entity and light lists to prevent crashes if the
307                 // CSQC_UpdateView function does not call R_ClearScene as it should
308                 r_refdef.scene.numentities = 0;
309                 r_refdef.scene.numlights = 0;
310                 // pass in width and height as parameters (EXT_CSQC_1)
311                 PRVM_G_FLOAT(OFS_PARM0) = vid.width;
312                 PRVM_G_FLOAT(OFS_PARM1) = vid.height;
313                 PRVM_ExecuteProgram(prog->funcoffsets.CSQC_UpdateView, "QC function CSQC_UpdateView is missing");
314                 //VectorCopy(oldangles, cl.viewangles);
315                 // Dresk : Reset Dmg Globals Here
316                 CL_VM_UpdateDmgGlobals(0, 0, emptyvector);
317         CSQC_END
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         prvm_eval_t *val;
517         if(cl.csqc_loaded)
518         {
519                 CSQC_BEGIN
520                 val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.intermission);
521                 if(val)
522                         val->_float = intermission;
523                 CSQC_END
524         }
525 }
526 void CL_VM_UpdateShowingScoresState (int showingscores)
527 {
528         prvm_eval_t *val;
529         if(cl.csqc_loaded)
530         {
531                 CSQC_BEGIN
532                 val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.sb_showscores);
533                 if(val)
534                         val->_float = showingscores;
535                 CSQC_END
536         }
537 }
538 qboolean CL_VM_Event_Sound(int sound_num, float volume, int channel, float attenuation, int ent, vec3_t pos)
539 {
540         qboolean r = false;
541         if(cl.csqc_loaded)
542         {
543                 CSQC_BEGIN
544                 if(prog->funcoffsets.CSQC_Event_Sound)
545                 {
546                         prog->globals.client->time = cl.time;
547                         prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
548                         PRVM_G_FLOAT(OFS_PARM0) = ent;
549                         PRVM_G_FLOAT(OFS_PARM1) = channel;
550                         PRVM_G_INT(OFS_PARM2) = PRVM_SetTempString(cl.sound_name[sound_num] );
551                         PRVM_G_FLOAT(OFS_PARM3) = volume;
552                         PRVM_G_FLOAT(OFS_PARM4) = attenuation;
553                         VectorCopy(pos, PRVM_G_VECTOR(OFS_PARM5) );
554                         PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Event_Sound, "QC function CSQC_Event_Sound is missing");
555                         r = CSQC_RETURNVAL != 0;
556                 }
557                 CSQC_END
558         }
559
560         return r;
561 }
562 void CL_VM_UpdateCoopDeathmatchGlobals (int gametype)
563 {
564         // Avoid global names for clean(er) coding
565         int localcoop;
566         int localdeathmatch;
567
568         prvm_eval_t *val;
569         if(cl.csqc_loaded)
570         {
571                 if(gametype == GAME_COOP)
572                 {
573                         localcoop = 1;
574                         localdeathmatch = 0;
575                 }
576                 else
577                 if(gametype == GAME_DEATHMATCH)
578                 {
579                         localcoop = 0;
580                         localdeathmatch = 1;
581                 }
582                 else
583                 {
584                         // How did the ServerInfo send an unknown gametype?
585                         // Better just assign the globals as 0...
586                         localcoop = 0;
587                         localdeathmatch = 0;
588                 }
589                 CSQC_BEGIN
590                 val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.coop);
591                 if(val)
592                         val->_float = localcoop;
593                 val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.deathmatch);
594                 if(val)
595                         val->_float = localdeathmatch;
596                 CSQC_END
597         }
598 }
599 float CL_VM_Event (float event)         //[515]: needed ? I'd say "YES", but don't know for what :D
600 {
601         float r = 0;
602         if(!cl.csqc_loaded)
603                 return 0;
604         CSQC_BEGIN
605         if(prog->funcoffsets.CSQC_Event)
606         {
607                 prog->globals.client->time = cl.time;
608                 prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
609                 PRVM_G_FLOAT(OFS_PARM0) = event;
610                 PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Event, "QC function CSQC_Event is missing");
611                 r = CSQC_RETURNVAL;
612         }
613         CSQC_END
614         return r;
615 }
616
617 void CSQC_ReadEntities (void)
618 {
619         unsigned short entnum, oldself, realentnum;
620         if(!cl.csqc_loaded)
621         {
622                 Host_Error ("CSQC_ReadEntities: CSQC is not loaded");
623                 return;
624         }
625
626         CSQC_BEGIN
627                 prog->globals.client->time = cl.time;
628                 oldself = prog->globals.client->self;
629                 while(1)
630                 {
631                         entnum = MSG_ReadShort();
632                         if(!entnum || msg_badread)
633                                 return;
634                         realentnum = entnum & 0x7FFF;
635                         prog->globals.client->self = cl.csqc_server2csqcentitynumber[realentnum];
636                         if(entnum & 0x8000)
637                         {
638                                 if(prog->globals.client->self)
639                                 {
640                                         PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Ent_Remove, "QC function CSQC_Ent_Remove is missing");
641                                         cl.csqc_server2csqcentitynumber[realentnum] = 0;
642                                 }
643                                 else
644                                 {
645                                         // LordHavoc: removing an entity that is already gone on
646                                         // the csqc side is possible for legitimate reasons (such
647                                         // as a repeat of the remove message), so no warning is
648                                         // needed
649                                         //Con_Printf("Bad csqc_server2csqcentitynumber map\n"); //[515]: never happens ?
650                                 }
651                         }
652                         else
653                         {
654                                 if(!prog->globals.client->self)
655                                 {
656                                         if(!prog->funcoffsets.CSQC_Ent_Spawn)
657                                         {
658                                                 prvm_edict_t    *ed;
659                                                 ed = PRVM_ED_Alloc();
660                                                 ed->fields.client->entnum = realentnum;
661                                                 prog->globals.client->self = cl.csqc_server2csqcentitynumber[realentnum] = PRVM_EDICT_TO_PROG(ed);
662                                         }
663                                         else
664                                         {
665                                                 // entity( float entnum ) CSQC_Ent_Spawn;
666                                                 // the qc function should set entnum, too (this way it also can return world [2/1/2008 Andreas]
667                                                 PRVM_G_FLOAT(OFS_PARM0) = (float) realentnum;
668                                                 // make sure no one gets wrong ideas
669                                                 prog->globals.client->self = 0;
670                                                 PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Ent_Spawn, "QC function CSQC_Ent_Spawn is missing");
671                                                 prog->globals.client->self = cl.csqc_server2csqcentitynumber[realentnum] = PRVM_EDICT( PRVM_G_INT( OFS_RETURN ) );
672                                         }
673                                         PRVM_G_FLOAT(OFS_PARM0) = 1;
674                                         PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Ent_Update, "QC function CSQC_Ent_Update is missing");
675                                 }
676                                 else {
677                                         PRVM_G_FLOAT(OFS_PARM0) = 0;
678                                         PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Ent_Update, "QC function CSQC_Ent_Update is missing");
679                                 }
680                         }
681                 }
682                 prog->globals.client->self = oldself;
683         CSQC_END
684 }
685
686 void CL_VM_CB_BeginIncreaseEdicts(void)
687 {
688         // links don't survive the transition, so unlink everything
689         World_UnlinkAll(&cl.world);
690 }
691
692 void CL_VM_CB_EndIncreaseEdicts(void)
693 {
694         int i;
695         prvm_edict_t *ent;
696
697         // link every entity except world
698         for (i = 1, ent = prog->edicts;i < prog->max_edicts;i++, ent++)
699                 if (!ent->priv.server->free)
700                         CL_LinkEdict(ent);
701 }
702
703 void CL_VM_CB_InitEdict(prvm_edict_t *e)
704 {
705         e->priv.server->move = false; // don't move on first frame
706 }
707
708 void CL_VM_CB_FreeEdict(prvm_edict_t *ed)
709 {
710         World_UnlinkEdict(ed);
711         memset(ed->fields.client, 0, sizeof(*ed->fields.client));
712 }
713
714 void CL_VM_CB_CountEdicts(void)
715 {
716         int             i;
717         prvm_edict_t    *ent;
718         int             active = 0, models = 0, solid = 0;
719
720         for (i=0 ; i<prog->num_edicts ; i++)
721         {
722                 ent = PRVM_EDICT_NUM(i);
723                 if (ent->priv.server->free)
724                         continue;
725                 active++;
726                 if (ent->fields.client->solid)
727                         solid++;
728                 if (ent->fields.client->model)
729                         models++;
730         }
731
732         Con_Printf("num_edicts:%3i\n", prog->num_edicts);
733         Con_Printf("active    :%3i\n", active);
734         Con_Printf("view      :%3i\n", models);
735         Con_Printf("touch     :%3i\n", solid);
736 }
737
738 qboolean CL_VM_CB_LoadEdict(prvm_edict_t *ent)
739 {
740         return true;
741 }
742
743 void Cmd_ClearCsqcFuncs (void);
744
745 // returns true if the packet is valid, false if end of file is reached
746 // used for dumping the CSQC download into demo files
747 qboolean MakeDownloadPacket(const char *filename, unsigned char *data, unsigned long len, int crc, int cnt, sizebuf_t *buf, int protocol)
748 {
749         int packetsize = buf->maxsize - 7; // byte short long
750         int npackets = (len + packetsize - 1) / (packetsize);
751
752         if(protocol == PROTOCOL_QUAKEWORLD)
753                 return false; // CSQC can't run in QW anyway
754
755         SZ_Clear(buf);
756         if(cnt == 0)
757         {
758                 MSG_WriteByte(buf, svc_stufftext);
759                 MSG_WriteString(buf, va("\ncl_downloadbegin %lu %s\n", len, filename));
760                 return true;
761         }
762         else if(cnt >= 1 && cnt <= npackets)
763         {
764                 unsigned long thispacketoffset = (cnt - 1) * packetsize;
765                 int thispacketsize = len - thispacketoffset;
766                 if(thispacketsize > packetsize)
767                         thispacketsize = packetsize;
768
769                 MSG_WriteByte(buf, svc_downloaddata);
770                 MSG_WriteLong(buf, thispacketoffset);
771                 MSG_WriteShort(buf, thispacketsize);
772                 SZ_Write(buf, data + thispacketoffset, thispacketsize);
773
774                 return true;
775         }
776         else if(cnt == npackets + 1)
777         {
778                 MSG_WriteByte(buf, svc_stufftext);
779                 MSG_WriteString(buf, va("\ncl_downloadfinished %lu %d\n", len, crc));
780                 return true;
781         }
782         return false;
783 }
784
785 void CL_VM_Init (void)
786 {
787         const char* csprogsfn;
788         unsigned char *csprogsdata;
789         fs_offset_t csprogsdatasize;
790         int csprogsdatacrc, requiredcrc;
791         int requiredsize;
792         prvm_eval_t *val;
793
794         // reset csqc_progcrc after reading it, so that changing servers doesn't
795         // expect csqc on the next server
796         requiredcrc = csqc_progcrc.integer;
797         requiredsize = csqc_progsize.integer;
798         Cvar_SetValueQuick(&csqc_progcrc, -1);
799         Cvar_SetValueQuick(&csqc_progsize, -1);
800
801         // if the server is not requesting a csprogs, then we're done here
802         if (requiredcrc < 0)
803                 return;
804
805         // see if the requested csprogs.dat file matches the requested crc
806         csprogsdatacrc = -1;
807         csprogsfn = va("dlcache/%s.%i.%i", csqc_progname.string, requiredsize, requiredcrc);
808         csprogsdata = FS_LoadFile(csprogsfn, tempmempool, true, &csprogsdatasize);
809         if (!csprogsdata)
810         {
811                 csprogsfn = csqc_progname.string;
812                 csprogsdata = FS_LoadFile(csprogsfn, tempmempool, true, &csprogsdatasize);
813         }
814         if (csprogsdata)
815         {
816                 csprogsdatacrc = CRC_Block(csprogsdata, csprogsdatasize);
817                 if (csprogsdatacrc != requiredcrc || csprogsdatasize != requiredsize)
818                 {
819                         if (cls.demoplayback)
820                         {
821                                 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);
822                                 // Mem_Free(csprogsdata);
823                                 // return;
824                                 // We WANT to continue here, and play the demo with different csprogs!
825                                 // After all, this is just a warning. Sure things may go wrong from here.
826                         }
827                         else
828                         {
829                                 Mem_Free(csprogsdata);
830                                 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);
831                                 CL_Disconnect();
832                                 return;
833                         }
834                 }
835         }
836         else
837         {
838                 if (requiredcrc >= 0)
839                 {
840                         if (cls.demoplayback)
841                                 Con_Printf("CL_VM_Init: demo requires CSQC, but \"%s\" wasn't found\n", csqc_progname.string);
842                         else
843                                 Con_Printf("CL_VM_Init: server requires CSQC, but \"%s\" wasn't found\n", csqc_progname.string);
844                         CL_Disconnect();
845                 }
846                 return;
847         }
848
849         PRVM_Begin;
850         PRVM_InitProg(PRVM_CLIENTPROG);
851
852         // allocate the mempools
853         prog->progs_mempool = Mem_AllocPool(csqc_progname.string, 0, NULL);
854         prog->headercrc = CL_PROGHEADER_CRC;
855         prog->edictprivate_size = 0; // no private struct used
856         prog->name = CL_NAME;
857         prog->num_edicts = 1;
858         prog->max_edicts = 512;
859         prog->limit_edicts = CL_MAX_EDICTS;
860         prog->reserved_edicts = 0;
861         prog->edictprivate_size = sizeof(edict_engineprivate_t);
862         // TODO: add a shared extension string #define and add real support for csqc extension strings [12/5/2007 Black]
863         prog->extensionstring = vm_sv_extensions;
864         prog->builtins = vm_cl_builtins;
865         prog->numbuiltins = vm_cl_numbuiltins;
866         prog->begin_increase_edicts = CL_VM_CB_BeginIncreaseEdicts;
867         prog->end_increase_edicts = CL_VM_CB_EndIncreaseEdicts;
868         prog->init_edict = CL_VM_CB_InitEdict;
869         prog->free_edict = CL_VM_CB_FreeEdict;
870         prog->count_edicts = CL_VM_CB_CountEdicts;
871         prog->load_edict = CL_VM_CB_LoadEdict;
872         prog->init_cmd = VM_CL_Cmd_Init;
873         prog->reset_cmd = VM_CL_Cmd_Reset;
874         prog->error_cmd = CL_VM_Error;
875
876         PRVM_LoadProgs(csprogsfn, cl_numrequiredfunc, cl_required_func, 0, NULL, 0, NULL);
877
878         if (!prog->loaded)
879         {
880                 CL_VM_Error("CSQC %s ^2failed to load\n", csprogsfn);
881                 if(!sv.active)
882                         CL_Disconnect();
883                 Mem_Free(csprogsdata);
884                 return;
885         }
886
887         Con_Printf("CSQC %s ^5loaded (crc=%i, size=%i)\n", csprogsfn, csprogsdatacrc, (int)csprogsdatasize);
888
889         if(cls.demorecording)
890         {
891                 if(cls.demo_lastcsprogssize != csprogsdatasize || cls.demo_lastcsprogscrc != csprogsdatacrc)
892                 {
893                         int i;
894                         char buf[NET_MAXMESSAGE];
895                         sizebuf_t sb;
896                         unsigned char *demobuf; fs_offset_t demofilesize;
897
898                         sb.data = (unsigned char *) buf;
899                         sb.maxsize = sizeof(buf);
900                         i = 0;
901
902                         CL_CutDemo(&demobuf, &demofilesize);
903                         while(MakeDownloadPacket(csqc_progname.string, csprogsdata, csprogsdatasize, csprogsdatacrc, i++, &sb, cls.protocol))
904                                 CL_WriteDemoMessage(&sb);
905                         CL_PasteDemo(&demobuf, &demofilesize);
906
907                         cls.demo_lastcsprogssize = csprogsdatasize;
908                         cls.demo_lastcsprogscrc = csprogsdatacrc;
909                 }
910         }
911         Mem_Free(csprogsdata);
912
913         // check if OP_STATE animation is possible in this dat file
914         if (prog->fieldoffsets.nextthink >= 0 && prog->fieldoffsets.frame >= 0 && prog->fieldoffsets.think >= 0 && prog->globaloffsets.self >= 0)
915                 prog->flag |= PRVM_OP_STATE;
916
917         // set time
918         prog->globals.client->time = cl.time;
919         prog->globals.client->self = 0;
920
921         prog->globals.client->mapname = cl.worldmodel ? PRVM_SetEngineString(cl.worldmodel->name) : PRVM_SetEngineString("");
922         prog->globals.client->player_localentnum = cl.playerentity;
923
924         // set map description (use world entity 0)
925         val = PRVM_EDICTFIELDVALUE(prog->edicts, prog->fieldoffsets.message);
926         if(val)
927                 val->string = PRVM_SetEngineString(cl.levelname);
928         VectorCopy(cl.world.mins, prog->edicts->fields.client->mins);
929         VectorCopy(cl.world.maxs, prog->edicts->fields.client->maxs);
930
931         // call the prog init
932         PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Init, "QC function CSQC_Init is missing");
933
934         PRVM_End;
935         cl.csqc_loaded = true;
936
937         cl.csqc_vidvars.drawcrosshair = false;
938         cl.csqc_vidvars.drawenginesbar = false;
939
940         // Update Coop and Deathmatch Globals (at this point the client knows them from ServerInfo)
941         CL_VM_UpdateCoopDeathmatchGlobals(cl.gametype);
942 }
943
944 void CL_VM_ShutDown (void)
945 {
946         Cmd_ClearCsqcFuncs();
947         //Cvar_SetValueQuick(&csqc_progcrc, -1);
948         //Cvar_SetValueQuick(&csqc_progsize, -1);
949         if(!cl.csqc_loaded)
950                 return;
951         CSQC_BEGIN
952                 prog->globals.client->time = cl.time;
953                 prog->globals.client->self = 0;
954                 if (prog->funcoffsets.CSQC_Shutdown)
955                         PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Shutdown, "QC function CSQC_Shutdown is missing");
956                 PRVM_ResetProg();
957         CSQC_END
958         Con_Print("CSQC ^1unloaded\n");
959         cl.csqc_loaded = false;
960 }
961
962 qboolean CL_VM_GetEntitySoundOrigin(int entnum, vec3_t out)
963 {
964         prvm_edict_t *ed;
965         dp_model_t *mod;
966         matrix4x4_t matrix;
967         qboolean r = 0;
968
969         CSQC_BEGIN;
970
971         // FIXME consider attachments here!
972
973         ed = PRVM_EDICT_NUM(entnum - 32768);
974
975         if(!ed->priv.required->free)
976         {
977                 mod = CL_GetModelFromEdict(ed);
978                 VectorCopy(ed->fields.client->origin, out);
979                 if(CL_GetTagMatrix (&matrix, ed, 0) == 0)
980                         Matrix4x4_OriginFromMatrix(&matrix, out);
981                 if (mod && mod->soundfromcenter)
982                         VectorMAMAM(1.0f, out, 0.5f, mod->normalmins, 0.5f, mod->normalmaxs, out);
983                 r = 1;
984         }
985
986         CSQC_END;
987
988         return r;
989 }