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