]> git.xonotic.org Git - xonotic/darkplaces.git/blob - csprogs.c
make DP compile with C++ again
[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                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();
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         entrender->frame1 = entrender->frame2 = (int) ed->fields.client->frame;
208         if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame2))) entrender->frame2 = (int) val->_float;
209         if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame1time))) entrender->frame2time = val->_float;
210         if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.frame2time))) entrender->frame1time = val->_float;
211         if ((val = PRVM_EDICTFIELDVALUE(ed, prog->fieldoffsets.lerpfrac))) entrender->framelerp = val->_float;
212
213         // concat the matrices to make the entity relative to its tag
214         Matrix4x4_Concat(&entrender->matrix, &tagmatrix, &matrix2);
215
216         if(renderflags)
217         {
218                 if(renderflags & RF_VIEWMODEL)  entrender->flags |= RENDER_VIEWMODEL;
219                 if(renderflags & RF_EXTERNALMODEL)entrender->flags |= RENDER_EXTERIORMODEL;
220                 if(renderflags & RF_DEPTHHACK)  entrender->effects |= EF_NODEPTHTEST;
221                 if(renderflags & RF_ADDITIVE)           entrender->effects |= EF_ADDITIVE;
222         }
223
224         c = (int)ed->fields.client->colormap;
225         if (c <= 0)
226                 CL_SetEntityColormapColors(entrender, -1);
227         else if (c <= cl.maxclients && cl.scores != NULL)
228                 CL_SetEntityColormapColors(entrender, cl.scores[c-1].colors);
229         else
230                 CL_SetEntityColormapColors(entrender, c);
231
232         entrender->flags &= ~(RENDER_SHADOW | RENDER_LIGHT | RENDER_NOSELFSHADOW);
233         // either fullbright or lit
234         if (!(entrender->effects & EF_FULLBRIGHT) && !r_fullbright.integer)
235                 entrender->flags |= RENDER_LIGHT;
236         // hide player shadow during intermission or nehahra movie
237         if (!(entrender->effects & (EF_NOSHADOW | EF_ADDITIVE | EF_NODEPTHTEST))
238          &&  (entrender->alpha >= 1)
239          && !(entrender->flags & RENDER_VIEWMODEL)
240          && (!(entrender->flags & RENDER_EXTERIORMODEL) || (!cl.intermission && cls.protocol != PROTOCOL_NEHAHRAMOVIE && !cl_noplayershadow.integer)))
241                 entrender->flags |= RENDER_SHADOW;
242         if (entrender->flags & RENDER_VIEWMODEL)
243                 entrender->flags |= RENDER_NOSELFSHADOW;
244         if (entrender->effects & EF_NOSELFSHADOW)
245                 entrender->flags |= RENDER_NOSELFSHADOW;
246
247         // make the other useful stuff
248         CL_UpdateRenderEntity(entrender);
249
250         return true;
251 }
252
253 qboolean CL_VM_InputEvent (qboolean down, int key, int ascii)
254 {
255         qboolean r;
256
257         if(!cl.csqc_loaded)
258                 return false;
259
260         CSQC_BEGIN
261                 if (!prog->funcoffsets.CSQC_InputEvent)
262                         r = false;
263                 else
264                 {
265                         prog->globals.client->time = cl.time;
266                         prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
267                         PRVM_G_FLOAT(OFS_PARM0) = !down; // 0 is down, 1 is up
268                         PRVM_G_FLOAT(OFS_PARM1) = key;
269                         PRVM_G_FLOAT(OFS_PARM2) = ascii;
270                         PRVM_ExecuteProgram(prog->funcoffsets.CSQC_InputEvent, "QC function CSQC_InputEvent is missing");
271                         r = CSQC_RETURNVAL;
272                 }
273         CSQC_END
274         return r;
275 }
276
277 qboolean CL_VM_UpdateView (void)
278 {
279         vec3_t emptyvector;
280         emptyvector[0] = 0;
281         emptyvector[1] = 0;
282         emptyvector[2] = 0;
283 //      vec3_t oldangles;
284         if(!cl.csqc_loaded)
285                 return false;
286         CSQC_BEGIN
287                 //VectorCopy(cl.viewangles, oldangles);
288                 prog->globals.client->time = cl.time;
289                 prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
290                 CSQC_SetGlobals();
291                 // clear renderable entity and light lists to prevent crashes if the
292                 // CSQC_UpdateView function does not call R_ClearScene as it should
293                 r_refdef.scene.numentities = 0;
294                 r_refdef.scene.numlights = 0;
295                 // pass in width and height as parameters (EXT_CSQC_1)
296                 PRVM_G_FLOAT(OFS_PARM0) = vid.width;
297                 PRVM_G_FLOAT(OFS_PARM1) = vid.height;
298                 PRVM_ExecuteProgram(prog->funcoffsets.CSQC_UpdateView, "QC function CSQC_UpdateView is missing");
299                 //VectorCopy(oldangles, cl.viewangles);
300                 // Dresk : Reset Dmg Globals Here
301                 CL_VM_UpdateDmgGlobals(0, 0, emptyvector);
302         CSQC_END
303         return true;
304 }
305
306 extern sizebuf_t vm_tempstringsbuf;
307 qboolean CL_VM_ConsoleCommand (const char *cmd)
308 {
309         int restorevm_tempstringsbuf_cursize;
310         qboolean r = false;
311         if(!cl.csqc_loaded)
312                 return false;
313         CSQC_BEGIN
314         if (prog->funcoffsets.CSQC_ConsoleCommand)
315         {
316                 prog->globals.client->time = cl.time;
317                 prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
318                 restorevm_tempstringsbuf_cursize = vm_tempstringsbuf.cursize;
319                 PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(cmd);
320                 PRVM_ExecuteProgram(prog->funcoffsets.CSQC_ConsoleCommand, "QC function CSQC_ConsoleCommand is missing");
321                 vm_tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize;
322                 r = CSQC_RETURNVAL;
323         }
324         CSQC_END
325         return r;
326 }
327
328 qboolean CL_VM_Parse_TempEntity (void)
329 {
330         int                     t;
331         qboolean        r = false;
332         if(!cl.csqc_loaded)
333                 return false;
334         CSQC_BEGIN
335         if(prog->funcoffsets.CSQC_Parse_TempEntity)
336         {
337                 t = msg_readcount;
338                 prog->globals.client->time = cl.time;
339                 prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
340                 PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Parse_TempEntity, "QC function CSQC_Parse_TempEntity is missing");
341                 r = CSQC_RETURNVAL;
342                 if(!r)
343                 {
344                         msg_readcount = t;
345                         msg_badread = false;
346                 }
347         }
348         CSQC_END
349         return r;
350 }
351
352 void CL_VM_Parse_StuffCmd (const char *msg)
353 {
354         int restorevm_tempstringsbuf_cursize;
355         if(msg[0] == 'c')
356         if(msg[1] == 's')
357         if(msg[2] == 'q')
358         if(msg[3] == 'c')
359         {
360                 // if this is setting a csqc variable, deprotect csqc_progcrc
361                 // temporarily so that it can be set by the cvar command,
362                 // and then reprotect it afterwards
363                 int crcflags = csqc_progcrc.flags;
364                 int sizeflags = csqc_progcrc.flags;
365                 csqc_progcrc.flags &= ~CVAR_READONLY;
366                 csqc_progsize.flags &= ~CVAR_READONLY;
367                 Cmd_ExecuteString (msg, src_command);
368                 csqc_progcrc.flags = crcflags;
369                 csqc_progsize.flags = sizeflags;
370                 return;
371         }
372
373         if(cls.demoplayback)
374         if(!strncmp(msg, "curl --clear_autodownload\ncurl --pak --forthismap --as ", 55))
375         {
376                 // special handling for map download commands
377                 // run these commands IMMEDIATELY, instead of waiting for a client frame
378                 // that way, there is no black screen when playing back demos
379                 // I know this is a really ugly hack, but I can't think of any better way
380                 // FIXME find the actual CAUSE of this, and make demo playback WAIT
381                 // until all maps are loaded, then remove this hack
382
383                 char buf[MAX_INPUTLINE];
384                 const char *p, *q;
385                 size_t l;
386
387                 p = msg;
388
389                 for(;;)
390                 {
391                         q = strchr(p, '\n');
392                         if(q)
393                                 l = q - p;
394                         else
395                                 l = strlen(p);
396                         if(l > sizeof(buf) - 1)
397                                 l = sizeof(buf) - 1;
398                         strlcpy(buf, p, l + 1); // strlcpy needs a + 1 as it includes the newline!
399
400                         Cmd_ExecuteString(buf, src_command);
401
402                         p += l;
403                         if(*p == '\n')
404                                 ++p; // skip the newline and continue
405                         else
406                                 break; // end of string or overflow
407                 }
408                 Cmd_ExecuteString("curl --clear_autodownload", src_command); // don't inhibit CSQC loading
409                 return;
410         }
411
412         if(!cl.csqc_loaded)
413         {
414                 Cbuf_AddText(msg);
415                 return;
416         }
417         CSQC_BEGIN
418         if(prog->funcoffsets.CSQC_Parse_StuffCmd)
419         {
420                 prog->globals.client->time = cl.time;
421                 prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
422                 restorevm_tempstringsbuf_cursize = vm_tempstringsbuf.cursize;
423                 PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(msg);
424                 PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Parse_StuffCmd, "QC function CSQC_Parse_StuffCmd is missing");
425                 vm_tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize;
426         }
427         else
428                 Cbuf_AddText(msg);
429         CSQC_END
430 }
431
432 static void CL_VM_Parse_Print (const char *msg)
433 {
434         int restorevm_tempstringsbuf_cursize;
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_Print, "QC function CSQC_Parse_Print is missing");
440         vm_tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize;
441 }
442
443 void CSQC_AddPrintText (const char *msg)
444 {
445         size_t i;
446         if(!cl.csqc_loaded)
447         {
448                 Con_Print(msg);
449                 return;
450         }
451         CSQC_BEGIN
452         if(prog->funcoffsets.CSQC_Parse_Print)
453         {
454                 // FIXME: is this bugged?
455                 i = strlen(msg)-1;
456                 if(msg[i] != '\n' && msg[i] != '\r')
457                 {
458                         if(strlen(cl.csqc_printtextbuf)+i >= MAX_INPUTLINE)
459                         {
460                                 CL_VM_Parse_Print(cl.csqc_printtextbuf);
461                                 cl.csqc_printtextbuf[0] = 0;
462                         }
463                         else
464                                 strlcat(cl.csqc_printtextbuf, msg, MAX_INPUTLINE);
465                         return;
466                 }
467                 strlcat(cl.csqc_printtextbuf, msg, MAX_INPUTLINE);
468                 CL_VM_Parse_Print(cl.csqc_printtextbuf);
469                 cl.csqc_printtextbuf[0] = 0;
470         }
471         else
472                 Con_Print(msg);
473         CSQC_END
474 }
475
476 void CL_VM_Parse_CenterPrint (const char *msg)
477 {
478         int restorevm_tempstringsbuf_cursize;
479         if(!cl.csqc_loaded)
480         {
481                 SCR_CenterPrint(msg);
482                 return;
483         }
484         CSQC_BEGIN
485         if(prog->funcoffsets.CSQC_Parse_CenterPrint)
486         {
487                 prog->globals.client->time = cl.time;
488                 prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
489                 restorevm_tempstringsbuf_cursize = vm_tempstringsbuf.cursize;
490                 PRVM_G_INT(OFS_PARM0) = PRVM_SetTempString(msg);
491                 PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Parse_CenterPrint, "QC function CSQC_Parse_CenterPrint is missing");
492                 vm_tempstringsbuf.cursize = restorevm_tempstringsbuf_cursize;
493         }
494         else
495                 SCR_CenterPrint(msg);
496         CSQC_END
497 }
498
499 void CL_VM_UpdateIntermissionState (int intermission)
500 {
501         prvm_eval_t *val;
502         if(cl.csqc_loaded)
503         {
504                 CSQC_BEGIN
505                 val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.intermission);
506                 if(val)
507                         val->_float = intermission;
508                 CSQC_END
509         }
510 }
511 void CL_VM_UpdateShowingScoresState (int showingscores)
512 {
513         prvm_eval_t *val;
514         if(cl.csqc_loaded)
515         {
516                 CSQC_BEGIN
517                 val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.sb_showscores);
518                 if(val)
519                         val->_float = showingscores;
520                 CSQC_END
521         }
522 }
523 qboolean CL_VM_Event_Sound(int sound_num, float volume, int channel, float attenuation, int ent, vec3_t pos)
524 {
525         qboolean r = false;
526         if(cl.csqc_loaded)
527         {
528                 CSQC_BEGIN
529                 if(prog->funcoffsets.CSQC_Event_Sound)
530                 {
531                         prog->globals.client->time = cl.time;
532                         prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
533                         PRVM_G_FLOAT(OFS_PARM0) = ent;
534                         PRVM_G_FLOAT(OFS_PARM1) = channel;
535                         PRVM_G_INT(OFS_PARM2) = PRVM_SetTempString(cl.sound_name[sound_num] );
536                         PRVM_G_FLOAT(OFS_PARM3) = volume;
537                         PRVM_G_FLOAT(OFS_PARM4) = attenuation;
538                         VectorCopy(pos, PRVM_G_VECTOR(OFS_PARM5) );
539                         PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Event_Sound, "QC function CSQC_Event_Sound is missing");
540                         r = CSQC_RETURNVAL;
541                 }
542                 CSQC_END
543         }
544
545         return r;
546 }
547 void CL_VM_UpdateCoopDeathmatchGlobals (int gametype)
548 {
549         // Avoid global names for clean(er) coding
550         int localcoop;
551         int localdeathmatch;
552
553         prvm_eval_t *val;
554         if(cl.csqc_loaded)
555         {
556                 if(gametype == GAME_COOP)
557                 {
558                         localcoop = 1;
559                         localdeathmatch = 0;
560                 }
561                 else
562                 if(gametype == GAME_DEATHMATCH)
563                 {
564                         localcoop = 0;
565                         localdeathmatch = 1;
566                 }
567                 else
568                 {
569                         // How did the ServerInfo send an unknown gametype?
570                         // Better just assign the globals as 0...
571                         localcoop = 0;
572                         localdeathmatch = 0;
573                 }
574                 CSQC_BEGIN
575                 val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.coop);
576                 if(val)
577                         val->_float = localcoop;
578                 val = PRVM_GLOBALFIELDVALUE(prog->globaloffsets.deathmatch);
579                 if(val)
580                         val->_float = localdeathmatch;
581                 CSQC_END
582         }
583 }
584 float CL_VM_Event (float event)         //[515]: needed ? I'd say "YES", but don't know for what :D
585 {
586         float r = 0;
587         if(!cl.csqc_loaded)
588                 return 0;
589         CSQC_BEGIN
590         if(prog->funcoffsets.CSQC_Event)
591         {
592                 prog->globals.client->time = cl.time;
593                 prog->globals.client->self = cl.csqc_server2csqcentitynumber[cl.playerentity];
594                 PRVM_G_FLOAT(OFS_PARM0) = event;
595                 PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Event, "QC function CSQC_Event is missing");
596                 r = CSQC_RETURNVAL;
597         }
598         CSQC_END
599         return r;
600 }
601
602 void CSQC_ReadEntities (void)
603 {
604         unsigned short entnum, oldself, realentnum;
605         if(!cl.csqc_loaded)
606         {
607                 Host_Error ("CSQC_ReadEntities: CSQC is not loaded");
608                 return;
609         }
610
611         CSQC_BEGIN
612                 prog->globals.client->time = cl.time;
613                 oldself = prog->globals.client->self;
614                 while(1)
615                 {
616                         entnum = MSG_ReadShort();
617                         if(!entnum || msg_badread)
618                                 return;
619                         realentnum = entnum & 0x7FFF;
620                         prog->globals.client->self = cl.csqc_server2csqcentitynumber[realentnum];
621                         if(entnum & 0x8000)
622                         {
623                                 if(prog->globals.client->self)
624                                 {
625                                         PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Ent_Remove, "QC function CSQC_Ent_Remove is missing");
626                                         cl.csqc_server2csqcentitynumber[realentnum] = 0;
627                                 }
628                                 else
629                                 {
630                                         // LordHavoc: removing an entity that is already gone on
631                                         // the csqc side is possible for legitimate reasons (such
632                                         // as a repeat of the remove message), so no warning is
633                                         // needed
634                                         //Con_Printf("Bad csqc_server2csqcentitynumber map\n"); //[515]: never happens ?
635                                 }
636                         }
637                         else
638                         {
639                                 if(!prog->globals.client->self)
640                                 {
641                                         if(!prog->funcoffsets.CSQC_Ent_Spawn)
642                                         {
643                                                 prvm_edict_t    *ed;
644                                                 ed = PRVM_ED_Alloc();
645                                                 ed->fields.client->entnum = realentnum;
646                                                 prog->globals.client->self = cl.csqc_server2csqcentitynumber[realentnum] = PRVM_EDICT_TO_PROG(ed);
647                                         }
648                                         else
649                                         {
650                                                 // entity( float entnum ) CSQC_Ent_Spawn;
651                                                 // the qc function should set entnum, too (this way it also can return world [2/1/2008 Andreas]
652                                                 PRVM_G_FLOAT(OFS_PARM0) = (float) realentnum;
653                                                 // make sure no one gets wrong ideas
654                                                 prog->globals.client->self = 0;
655                                                 PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Ent_Spawn, "QC function CSQC_Ent_Spawn is missing");
656                                                 prog->globals.client->self = cl.csqc_server2csqcentitynumber[realentnum] = PRVM_EDICT( PRVM_G_INT( OFS_RETURN ) );
657                                         }
658                                         PRVM_G_FLOAT(OFS_PARM0) = 1;
659                                         PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Ent_Update, "QC function CSQC_Ent_Update is missing");
660                                 }
661                                 else {
662                                         PRVM_G_FLOAT(OFS_PARM0) = 0;
663                                         PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Ent_Update, "QC function CSQC_Ent_Update is missing");
664                                 }
665                         }
666                 }
667                 prog->globals.client->self = oldself;
668         CSQC_END
669 }
670
671 void CL_VM_CB_BeginIncreaseEdicts(void)
672 {
673         // links don't survive the transition, so unlink everything
674         World_UnlinkAll(&cl.world);
675 }
676
677 void CL_VM_CB_EndIncreaseEdicts(void)
678 {
679         int i;
680         prvm_edict_t *ent;
681
682         // link every entity except world
683         for (i = 1, ent = prog->edicts;i < prog->max_edicts;i++, ent++)
684                 if (!ent->priv.server->free)
685                         CL_LinkEdict(ent);
686 }
687
688 void CL_VM_CB_InitEdict(prvm_edict_t *e)
689 {
690         e->priv.server->move = false; // don't move on first frame
691 }
692
693 void CL_VM_CB_FreeEdict(prvm_edict_t *ed)
694 {
695         World_UnlinkEdict(ed);
696         memset(ed->fields.client, 0, sizeof(*ed->fields.client));
697 }
698
699 void CL_VM_CB_CountEdicts(void)
700 {
701         int             i;
702         prvm_edict_t    *ent;
703         int             active = 0, models = 0, solid = 0;
704
705         for (i=0 ; i<prog->num_edicts ; i++)
706         {
707                 ent = PRVM_EDICT_NUM(i);
708                 if (ent->priv.server->free)
709                         continue;
710                 active++;
711                 if (ent->fields.client->solid)
712                         solid++;
713                 if (ent->fields.client->model)
714                         models++;
715         }
716
717         Con_Printf("num_edicts:%3i\n", prog->num_edicts);
718         Con_Printf("active    :%3i\n", active);
719         Con_Printf("view      :%3i\n", models);
720         Con_Printf("touch     :%3i\n", solid);
721 }
722
723 qboolean CL_VM_CB_LoadEdict(prvm_edict_t *ent)
724 {
725         return true;
726 }
727
728 void Cmd_ClearCsqcFuncs (void);
729
730 // returns true if the packet is valid, false if end of file is reached
731 // used for dumping the CSQC download into demo files
732 qboolean MakeDownloadPacket(const char *filename, unsigned char *data, unsigned long len, int crc, int cnt, sizebuf_t *buf, int protocol)
733 {
734         int packetsize = buf->maxsize - 7; // byte short long
735         int npackets = (len + packetsize - 1) / (packetsize);
736
737         if(protocol == PROTOCOL_QUAKEWORLD)
738                 return false; // CSQC can't run in QW anyway
739
740         SZ_Clear(buf);
741         if(cnt == 0)
742         {
743                 MSG_WriteByte(buf, svc_stufftext);
744                 MSG_WriteString(buf, va("\ncl_downloadbegin %lu %s\n", len, filename));
745                 return true;
746         }
747         else if(cnt >= 1 && cnt <= npackets)
748         {
749                 unsigned long thispacketoffset = (cnt - 1) * packetsize;
750                 int thispacketsize = len - thispacketoffset;
751                 if(thispacketsize > packetsize)
752                         thispacketsize = packetsize;
753
754                 MSG_WriteByte(buf, svc_downloaddata);
755                 MSG_WriteLong(buf, thispacketoffset);
756                 MSG_WriteShort(buf, thispacketsize);
757                 SZ_Write(buf, data + thispacketoffset, thispacketsize);
758
759                 return true;
760         }
761         else if(cnt == npackets + 1)
762         {
763                 MSG_WriteByte(buf, svc_stufftext);
764                 MSG_WriteString(buf, va("\ncl_downloadfinished %lu %d\n", len, crc));
765                 return true;
766         }
767         return false;
768 }
769
770 void CL_VM_Init (void)
771 {
772         const char* csprogsfn;
773         unsigned char *csprogsdata;
774         fs_offset_t csprogsdatasize;
775         int csprogsdatacrc, requiredcrc;
776         int requiredsize;
777         prvm_eval_t *val;
778
779         // reset csqc_progcrc after reading it, so that changing servers doesn't
780         // expect csqc on the next server
781         requiredcrc = csqc_progcrc.integer;
782         requiredsize = csqc_progsize.integer;
783         Cvar_SetValueQuick(&csqc_progcrc, -1);
784         Cvar_SetValueQuick(&csqc_progsize, -1);
785
786         // if the server is not requesting a csprogs, then we're done here
787         if (requiredcrc < 0)
788                 return;
789
790         // see if the requested csprogs.dat file matches the requested crc
791         csprogsdatacrc = -1;
792         csprogsfn = va("dlcache/%s.%i.%i", csqc_progname.string, requiredsize, requiredcrc);
793         csprogsdata = FS_LoadFile(csprogsfn, tempmempool, true, &csprogsdatasize);
794         if (!csprogsdata)
795         {
796                 csprogsfn = csqc_progname.string;
797                 csprogsdata = FS_LoadFile(csprogsfn, tempmempool, true, &csprogsdatasize);
798         }
799         if (csprogsdata)
800         {
801                 csprogsdatacrc = CRC_Block(csprogsdata, csprogsdatasize);
802                 if (csprogsdatacrc != requiredcrc || csprogsdatasize != requiredsize)
803                 {
804                         if (cls.demoplayback)
805                         {
806                                 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);
807                                 // Mem_Free(csprogsdata);
808                                 // return;
809                                 // We WANT to continue here, and play the demo with different csprogs!
810                                 // After all, this is just a warning. Sure things may go wrong from here.
811                         }
812                         else
813                         {
814                                 Mem_Free(csprogsdata);
815                                 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);
816                                 CL_Disconnect();
817                                 return;
818                         }
819                 }
820         }
821         else
822         {
823                 if (requiredcrc >= 0)
824                 {
825                         if (cls.demoplayback)
826                                 Con_Printf("CL_VM_Init: demo requires CSQC, but \"%s\" wasn't found\n", csqc_progname.string);
827                         else
828                                 Con_Printf("CL_VM_Init: server requires CSQC, but \"%s\" wasn't found\n", csqc_progname.string);
829                         CL_Disconnect();
830                 }
831                 return;
832         }
833
834         PRVM_Begin;
835         PRVM_InitProg(PRVM_CLIENTPROG);
836
837         // allocate the mempools
838         prog->progs_mempool = Mem_AllocPool(csqc_progname.string, 0, NULL);
839         prog->headercrc = CL_PROGHEADER_CRC;
840         prog->edictprivate_size = 0; // no private struct used
841         prog->name = CL_NAME;
842         prog->num_edicts = 1;
843         prog->max_edicts = 512;
844         prog->limit_edicts = CL_MAX_EDICTS;
845         prog->reserved_edicts = 0;
846         prog->edictprivate_size = sizeof(edict_engineprivate_t);
847         // TODO: add a shared extension string #define and add real support for csqc extension strings [12/5/2007 Black]
848         prog->extensionstring = vm_sv_extensions;
849         prog->builtins = vm_cl_builtins;
850         prog->numbuiltins = vm_cl_numbuiltins;
851         prog->begin_increase_edicts = CL_VM_CB_BeginIncreaseEdicts;
852         prog->end_increase_edicts = CL_VM_CB_EndIncreaseEdicts;
853         prog->init_edict = CL_VM_CB_InitEdict;
854         prog->free_edict = CL_VM_CB_FreeEdict;
855         prog->count_edicts = CL_VM_CB_CountEdicts;
856         prog->load_edict = CL_VM_CB_LoadEdict;
857         prog->init_cmd = VM_CL_Cmd_Init;
858         prog->reset_cmd = VM_CL_Cmd_Reset;
859         prog->error_cmd = CL_VM_Error;
860
861         PRVM_LoadProgs(csprogsfn, cl_numrequiredfunc, cl_required_func, 0, NULL, 0, NULL);
862
863         if (!prog->loaded)
864         {
865                 CL_VM_Error("CSQC %s ^2failed to load\n", csprogsfn);
866                 if(!sv.active)
867                         CL_Disconnect();
868                 Mem_Free(csprogsdata);
869                 return;
870         }
871
872         Con_Printf("CSQC %s ^5loaded (crc=%i, size=%i)\n", csprogsfn, csprogsdatacrc, (int)csprogsdatasize);
873
874         if(cls.demorecording)
875         {
876                 if(cls.demo_lastcsprogssize != csprogsdatasize || cls.demo_lastcsprogscrc != csprogsdatacrc)
877                 {
878                         int i;
879                         char buf[NET_MAXMESSAGE];
880                         sizebuf_t sb;
881                         unsigned char *demobuf; fs_offset_t demofilesize;
882
883                         sb.data = (unsigned char *) buf;
884                         sb.maxsize = sizeof(buf);
885                         i = 0;
886
887                         CL_CutDemo(&demobuf, &demofilesize);
888                         while(MakeDownloadPacket(csqc_progname.string, csprogsdata, csprogsdatasize, csprogsdatacrc, i++, &sb, cls.protocol))
889                                 CL_WriteDemoMessage(&sb);
890                         CL_PasteDemo(&demobuf, &demofilesize);
891
892                         cls.demo_lastcsprogssize = csprogsdatasize;
893                         cls.demo_lastcsprogscrc = csprogsdatacrc;
894                 }
895         }
896         Mem_Free(csprogsdata);
897
898         // check if OP_STATE animation is possible in this dat file
899         if (prog->fieldoffsets.nextthink >= 0 && prog->fieldoffsets.frame >= 0 && prog->fieldoffsets.think >= 0 && prog->globaloffsets.self >= 0)
900                 prog->flag |= PRVM_OP_STATE;
901
902         // set time
903         prog->globals.client->time = cl.time;
904         prog->globals.client->self = 0;
905
906         prog->globals.client->mapname = cl.worldmodel ? PRVM_SetEngineString(cl.worldmodel->name) : PRVM_SetEngineString("");
907         prog->globals.client->player_localentnum = cl.playerentity;
908
909         // set map description (use world entity 0)
910         val = PRVM_EDICTFIELDVALUE(prog->edicts, prog->fieldoffsets.message);
911         if(val)
912                 val->string = PRVM_SetEngineString(cl.levelname);
913         VectorCopy(cl.world.mins, prog->edicts->fields.client->mins);
914         VectorCopy(cl.world.maxs, prog->edicts->fields.client->maxs);
915
916         // call the prog init
917         PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Init, "QC function CSQC_Init is missing");
918
919         PRVM_End;
920         cl.csqc_loaded = true;
921
922         cl.csqc_vidvars.drawcrosshair = false;
923         cl.csqc_vidvars.drawenginesbar = false;
924
925         // Update Coop and Deathmatch Globals (at this point the client knows them from ServerInfo)
926         CL_VM_UpdateCoopDeathmatchGlobals(cl.gametype);
927 }
928
929 void CL_VM_ShutDown (void)
930 {
931         Cmd_ClearCsqcFuncs();
932         //Cvar_SetValueQuick(&csqc_progcrc, -1);
933         //Cvar_SetValueQuick(&csqc_progsize, -1);
934         if(!cl.csqc_loaded)
935                 return;
936         CSQC_BEGIN
937                 prog->globals.client->time = cl.time;
938                 prog->globals.client->self = 0;
939                 if (prog->funcoffsets.CSQC_Shutdown)
940                         PRVM_ExecuteProgram(prog->funcoffsets.CSQC_Shutdown, "QC function CSQC_Shutdown is missing");
941                 PRVM_ResetProg();
942         CSQC_END
943         Con_Print("CSQC ^1unloaded\n");
944         cl.csqc_loaded = false;
945 }
946
947 qboolean CL_VM_GetEntitySoundOrigin(int entnum, vec3_t out)
948 {
949         prvm_edict_t *ed;
950         dp_model_t *mod;
951         qboolean r = 0;
952
953         CSQC_BEGIN;
954
955         ed = PRVM_EDICT_NUM(entnum - 32768);
956
957         if(!ed->priv.required->free)
958         {
959                 mod = CL_GetModelFromEdict(ed);
960                 VectorCopy(ed->fields.client->origin, out);
961                 if (mod && mod->soundfromcenter)
962                         VectorMAMAM(1.0f, out, 0.5f, mod->normalmins, 0.5f, mod->normalmaxs, out);
963                 r = 1;
964         }
965
966         CSQC_END;
967
968         return r;
969 }