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