Scaled the weapon models down about 30% to match 2.5.2 sizes. Except the tuba, I...
[divverent/nexuiz.git] / data / qcsrc / server / g_subs.qc
1 void SUB_Null() {};
2 float SUB_True() { return 1; }
3 float SUB_False() { return 0; }
4
5 void(vector destangle, float tspeed, void() func) SUB_CalcAngleMove;
6 void()  SUB_CalcMoveDone;
7 void() SUB_CalcAngleMoveDone;
8 //void() SUB_UseTargets;
9 void() SUB_Remove;
10
11 void spawnfunc_info_null (void)
12 {
13         remove(self);
14         // if anything breaks, tell the mapper to fix his map! info_null is meant to remove itself immediately.
15 }
16
17 void setanim(entity e, vector anim, float looping, float override, float restart)
18 {
19         if (anim_x == e.animstate_startframe)
20         if (anim_y == e.animstate_numframes)
21         if (anim_z == e.animstate_framerate)
22         {
23                 if(restart)
24                 {
25                         if(restart > 0)
26                         if(anim_y == 1) // ZYM animation
27                                 BITXOR_ASSIGN(e.effects, EF_RESTARTANIM_BIT);
28                 }
29                 else
30                         return;
31         }
32         e.animstate_startframe = anim_x;
33         e.animstate_numframes = anim_y;
34         e.animstate_framerate = anim_z;
35         e.animstate_starttime = servertime - 0.1 * serverframetime; // shift it a little bit into the past to prevent float inaccuracy hiccups
36         e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
37         e.animstate_looping = looping;
38         e.animstate_override = override;
39         e.frame = e.animstate_startframe;
40 };
41
42 void updateanim(entity e)
43 {
44         if (time >= e.animstate_endtime)
45         {
46                 if (e.animstate_looping)
47                 {
48                         e.animstate_starttime = e.animstate_endtime;
49                         e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
50                 }
51                 e.animstate_override = FALSE;
52         }
53         e.frame = e.animstate_startframe + bound(0, (time - e.animstate_starttime) * e.animstate_framerate, e.animstate_numframes - 1);
54         //print(ftos(time), " -> ", ftos(e.frame), "\n");
55 };
56
57 float animparseerror;
58 vector animparseline(float animfile)
59 {
60         local string line;
61         local float c;
62         local vector anim;
63         if (animfile < 0)
64                 return '0 1 2';
65         line = fgets(animfile);
66         c = tokenize_console(line);
67         if (c < 3)
68         {
69                 animparseerror = TRUE;
70                 return '0 1 2';
71         }
72         anim_x = stof(argv(0));
73         anim_y = stof(argv(1));
74         anim_z = stof(argv(2));
75         // don't allow completely bogus values
76         if (anim_x < 0 || anim_y < 1 || anim_z < 0.001)
77                 anim = '0 1 2';
78         return anim;
79 };
80
81 /*
82 ==================
83 SUB_Remove
84
85 Remove self
86 ==================
87 */
88 void SUB_Remove (void)
89 {
90         remove (self);
91 }
92
93 /*
94 ==================
95 SUB_Friction
96
97 Applies some friction to self
98 ==================
99 */
100 .float friction;
101 void SUB_Friction (void)
102 {
103         self.nextthink = time;
104         if(self.flags & FL_ONGROUND)
105                 self.velocity = self.velocity * (1 - frametime * self.friction);
106 }
107
108 /*
109 ==================
110 SUB_VanishOrRemove
111
112 Makes client invisible or removes non-client
113 ==================
114 */
115 void SUB_VanishOrRemove (entity ent)
116 {
117         if (ent.flags & FL_CLIENT)
118         {
119                 // vanish
120                 ent.model = "";
121                 ent.effects = 0;
122                 ent.glow_size = 0;
123                 ent.pflags = 0;
124         }
125         else
126         {
127                 // remove
128                 remove (ent);
129         }
130 }
131
132 void SUB_SetFade_Think (void)
133 {
134         self.think = SUB_SetFade_Think;
135         self.nextthink = self.fade_time;
136         self.alpha = 1 - (time - self.fade_time) * self.fade_rate;
137         if (self.alpha < 0.01)
138                 SUB_VanishOrRemove(self);
139         self.alpha = bound(0.01, self.alpha, 1);
140 }
141
142 /*
143 ==================
144 SUB_SetFade
145
146 Fade 'ent' out when time >= 'when'
147 ==================
148 */
149 void SUB_SetFade (entity ent, float when, float fadetime)
150 {
151         //if (ent.flags & FL_CLIENT) // && ent.deadflag != DEAD_NO)
152         //      return;
153         //ent.alpha = 1;
154         ent.fade_rate = 1/fadetime;
155         ent.fade_time = when;
156         ent.think = SUB_SetFade_Think;
157         ent.nextthink = when;
158 }
159
160 /*
161 =============
162 SUB_CalcMove
163
164 calculate self.velocity and self.nextthink to reach dest from
165 self.origin traveling at speed
166 ===============
167 */
168 void SUB_CalcMoveDone (void)
169 {
170         // After moving, set origin to exact final destination
171
172         setorigin (self, self.finaldest);
173         self.velocity = '0 0 0';
174         self.nextthink = -1;
175         if (self.think1)
176                 self.think1 ();
177 }
178
179 void SUB_CalcMove (vector tdest, float tspeed, void() func)
180 {
181         vector  delta;
182         float   traveltime;
183
184         if (!tspeed)
185                 objerror ("No speed is defined!");
186
187         self.think1 = func;
188         self.finaldest = tdest;
189         self.think = SUB_CalcMoveDone;
190
191         if (tdest == self.origin)
192         {
193                 self.velocity = '0 0 0';
194                 self.nextthink = self.ltime + 0.1;
195                 return;
196         }
197
198         delta = tdest - self.origin;
199         traveltime = vlen (delta) / tspeed;
200
201         if (traveltime < 0.1)
202         {
203                 self.velocity = '0 0 0';
204                 self.nextthink = self.ltime + 0.1;
205                 return;
206         }
207
208         self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
209
210         self.nextthink = self.ltime + traveltime;
211 }
212
213 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeed, void() func)
214 {
215         entity  oldself;
216
217         oldself = self;
218         self = ent;
219
220         SUB_CalcMove (tdest, tspeed, func);
221
222         self = oldself;
223 }
224
225 /*
226 =============
227 SUB_CalcAngleMove
228
229 calculate self.avelocity and self.nextthink to reach destangle from
230 self.angles rotating
231
232 The calling function should make sure self.think is valid
233 ===============
234 */
235 void SUB_CalcAngleMoveDone (void)
236 {
237         // After rotating, set angle to exact final angle
238         self.angles = self.finalangle;
239         self.avelocity = '0 0 0';
240         self.nextthink = -1;
241         if (self.think1)
242                 self.think1 ();
243 }
244
245 // FIXME: I fixed this function only for rotation around the main axes
246 void SUB_CalcAngleMove (vector destangle, float tspeed, void() func)
247 {
248         vector  delta;
249         float   traveltime;
250
251         if (!tspeed)
252                 objerror ("No speed is defined!");
253
254         // take the shortest distance for the angles
255         self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
256         self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
257         self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
258         delta = destangle - self.angles;
259         traveltime = vlen (delta) / tspeed;
260
261         self.think1 = func;
262         self.finalangle = destangle;
263         self.think = SUB_CalcAngleMoveDone;
264
265         if (traveltime < 0.1)
266         {
267                 self.avelocity = '0 0 0';
268                 self.nextthink = self.ltime + 0.1;
269                 return;
270         }
271
272         self.avelocity = delta * (1 / traveltime);
273         self.nextthink = self.ltime + traveltime;
274 }
275
276 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeed, void() func)
277 {
278         entity  oldself;
279
280         oldself = self;
281         self = ent;
282
283         SUB_CalcAngleMove (destangle, tspeed, func);
284
285         self = oldself;
286 }
287
288 /*
289 ==================
290 main
291
292 unused but required by the engine
293 ==================
294 */
295 void main (void)
296 {
297
298 }
299
300 // Misc
301
302 /*
303 ==================
304 traceline_antilag
305
306 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
307 Additionally it moves players back into the past before the trace and restores them afterward.
308 ==================
309 */
310 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
311 {
312         local entity player;
313         local float oldsolid;
314
315         // check whether antilagged traces are enabled
316         if (lag < 0.001)
317                 lag = 0;
318         if (clienttype(forent) != CLIENTTYPE_REAL)
319                 lag = 0; // only antilag for clients
320
321         // change shooter to SOLID_BBOX so the shot can hit corpses
322         if(source)
323         {
324                 oldsolid = source.dphitcontentsmask;
325                 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
326         }
327
328         if (lag)
329         {
330                 // take players back into the past
331                 player = player_list;
332                 while (player)
333                 {
334                         antilag_takeback(player, time - lag);
335                         player = player.nextplayer;
336                 }
337         }
338
339         // do the trace
340         if(wz)
341                 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
342         else
343                 tracebox (v1, mi, ma, v2, nomonst, forent);
344
345         // restore players to current positions
346         if (lag)
347         {
348                 player = player_list;
349                 while (player)
350                 {
351                         antilag_restore(player);
352                         player = player.nextplayer;
353                 }
354         }
355
356         // restore shooter solid type
357         if(source)
358                 source.dphitcontentsmask = oldsolid;
359 }
360 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
361 {
362         tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
363 }
364 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
365 {
366         if (cvar("g_antilag") != 2 || source.cvar_cl_noantilag)
367                 lag = 0;
368         traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
369 }
370 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
371 {
372         if (cvar("g_antilag") != 2 || source.cvar_cl_noantilag)
373                 lag = 0;
374         tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
375 }
376 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
377 {
378         tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
379 }
380 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
381 {
382         if (cvar("g_antilag") != 2 || source.cvar_cl_noantilag)
383                 lag = 0;
384         WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
385 }
386 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
387 {
388         if (cvar("g_antilag") != 2 || source.cvar_cl_noantilag)
389                 lag = 0;
390         tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
391 }
392
393 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent) // returns the number of traces done, for benchmarking
394 {
395         vector pos, dir, t;
396         float nudge;
397
398         //nudge = 2 * cvar("collision_impactnudge"); // why not?
399         nudge = 0.5;
400
401         dir = normalize(v2 - v1);
402
403         pos = v1 + dir * nudge;
404
405         float c;
406         c = 0;
407
408         for(;;)
409         {
410                 if((pos - v1) * dir >= (v2 - v1) * dir)
411                 {
412                         // went too far
413                         trace_fraction = 1;
414                         trace_endpos = v2;
415                         return c;
416                 }
417
418                 tracebox(pos, mi, ma, v2, nomonsters, forent);
419                 ++c;
420
421                 if(c == 50)
422                 {
423                         dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
424                         dprint("  Nudging gets us nowhere at ", vtos(pos), "\n");
425                         dprint("  trace_endpos is ", vtos(trace_endpos), "\n");
426                         dprint("  trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
427                 }
428
429                 if(trace_startsolid)
430                 {
431                         // we started inside solid.
432                         // then trace from endpos to pos
433                         t = trace_endpos;
434                         tracebox(t, mi, ma, pos, nomonsters, forent);
435                         ++c;
436                         if(trace_startsolid)
437                         {
438                                 // t is still inside solid? bad
439                                 // force advance, then, and retry
440                                 pos = t + dir * nudge;
441                         }
442                         else
443                         {
444                                 // we actually LEFT solid!
445                                 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
446                                 return c;
447                         }
448                 }
449                 else
450                 {
451                         // pos is outside solid?!? but why?!? never mind, just return it.
452                         trace_endpos = pos;
453                         trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
454                         return c;
455                 }
456         }
457 }
458
459 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent)
460 {
461 #if 0
462         vector pos, dir, t;
463         float nudge;
464
465         //nudge = 2 * cvar("collision_impactnudge"); // why not?
466         nudge = 0.5;
467
468         dir = normalize(v2 - v1);
469
470         pos = v1 + dir * nudge;
471
472         for(;;)
473         {
474                 if((pos - v1) * dir >= (v2 - v1) * dir)
475                 {
476                         // went too far
477                         trace_fraction = 1;
478                         return;
479                 }
480
481                 traceline(pos, v2, nomonsters, forent);
482
483                 if(trace_startsolid)
484                 {
485                         // we started inside solid.
486                         // then trace from endpos to pos
487                         t = trace_endpos;
488                         traceline(t, pos, nomonsters, forent);
489                         if(trace_startsolid)
490                         {
491                                 // t is inside solid? bad
492                                 // force advance, then
493                                 pos = pos + dir * nudge;
494                         }
495                         else
496                         {
497                                 // we actually LEFT solid!
498                                 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
499                                 return;
500                         }
501                 }
502                 else
503                 {
504                         // pos is outside solid?!? but why?!? never mind, just return it.
505                         trace_endpos = pos;
506                         trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
507                         return;
508                 }
509         }
510 #else
511         tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent);
512 }
513
514 /*
515 ==================
516 findbetterlocation
517
518 Returns a point at least 12 units away from walls
519 (useful for explosion animations, although the blast is performed where it really happened)
520 Ripped from DPMod
521 ==================
522 */
523 vector findbetterlocation (vector org, float mindist)
524 {
525         vector  loc;
526         vector vec;
527         float c, h;
528
529         vec = mindist * '1 0 0';
530         c = 0;
531         while (c < 6)
532         {
533                 traceline (org, org + vec, TRUE, world);
534                 vec = vec * -1;
535                 if (trace_fraction < 1)
536                 {
537                         loc = trace_endpos;
538                         traceline (loc, loc + vec, TRUE, world);
539                         if (trace_fraction >= 1)
540                                 org = loc + vec;
541                 }
542                 if (c & 1)
543                 {
544                         h = vec_y;
545                         vec_y = vec_x;
546                         vec_x = vec_z;
547                         vec_z = h;
548                 }
549                 c = c + 1;
550         }
551
552         return org;
553 }
554
555 /*
556 ==================
557 crandom
558
559 Returns a random number between -1.0 and 1.0
560 ==================
561 */
562 float crandom (void)
563 {
564         return 2 * (random () - 0.5);
565 }
566
567 /*
568 ==================
569 Angc used for animations
570 ==================
571 */
572
573
574 float angc (float a1, float a2)
575 {
576         float   a;
577
578         while (a1 > 180)
579                 a1 = a1 - 360;
580         while (a1 < -179)
581                 a1 = a1 + 360;
582
583         while (a2 > 180)
584                 a2 = a2 - 360;
585         while (a2 < -179)
586                 a2 = a2 + 360;
587
588         a = a1 - a2;
589         while (a > 180)
590                 a = a - 360;
591         while (a < -179)
592                 a = a + 360;
593
594         return a;
595 }
596
597 .string lodtarget1;
598 .string lodtarget2;
599 .string lodmodel1;
600 .string lodmodel2;
601 .float lodmodelindex0;
602 .float lodmodelindex1;
603 .float lodmodelindex2;
604 .float loddistance1;
605 .float loddistance2;
606
607 vector NearestPointOnBox(entity box, vector org);
608 float LOD_customize()
609 {
610         float d;
611
612         if(cvar("loddebug"))
613         {
614                 d = cvar("loddebug");
615                 if(d == 1)
616                         self.modelindex = self.lodmodelindex0;
617                 else if(d == 2)
618                         self.modelindex = self.lodmodelindex1;
619                 else // if(d == 3)
620                         self.modelindex = self.lodmodelindex2;
621                 return TRUE;
622         }
623
624         // TODO csqc network this so it only gets sent once
625         d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
626         if(d < self.loddistance1)
627                 self.modelindex = self.lodmodelindex0;
628         else if(!self.lodmodelindex2 || d < self.loddistance2)
629                 self.modelindex = self.lodmodelindex1;
630         else
631                 self.modelindex = self.lodmodelindex2;
632
633         return TRUE;
634 }
635
636 void LOD_uncustomize()
637 {
638         self.modelindex = self.lodmodelindex0;
639 }
640
641 void LODmodel_attach()
642 {
643         entity e;
644
645         if(!self.loddistance1)
646                 self.loddistance1 = 1000;
647         if(!self.loddistance2)
648                 self.loddistance2 = 2000;
649         self.lodmodelindex0 = self.modelindex;
650
651         if(self.lodtarget1 != "")
652         {
653                 e = find(world, targetname, self.lodtarget1);
654                 if(e)
655                 {
656                         self.lodmodel1 = e.model;
657                         remove(e);
658                 }
659         }
660         if(self.lodtarget2 != "")
661         {
662                 e = find(world, targetname, self.lodtarget2);
663                 if(e)
664                 {
665                         self.lodmodel2 = e.model;
666                         remove(e);
667                 }
668         }
669
670         if(cvar("loddebug") < 0)
671         {
672                 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
673         }
674
675         if(self.lodmodel1 != "")
676         {
677                 vector mi, ma;
678                 mi = self.mins;
679                 ma = self.maxs;
680
681                 precache_model(self.lodmodel1);
682                 setmodel(self, self.lodmodel1);
683                 self.lodmodelindex1 = self.modelindex;
684
685                 if(self.lodmodel2 != "")
686                 {
687                         precache_model(self.lodmodel2);
688                         setmodel(self, self.lodmodel2);
689                         self.lodmodelindex2 = self.modelindex;
690                 }
691
692                 self.modelindex = self.lodmodelindex0;
693                 setsize(self, mi, ma);
694         }
695
696         if(self.lodmodelindex1)
697                 SetCustomizer(self, LOD_customize, LOD_uncustomize);
698 }
699
700 void SetBrushEntityModel()
701 {
702         if(self.model != "")
703         {
704                 precache_model(self.model);
705                 setmodel(self, self.model); // no precision needed
706                 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
707         }
708         setorigin(self, self.origin);
709         if(self.scale)
710                 setsize(self, self.mins * self.scale, self.maxs * self.scale);
711         else
712                 setsize(self, self.mins, self.maxs);
713 }
714
715 void SetBrushEntityModelNoLOD()
716 {
717         if(self.model != "")
718         {
719                 precache_model(self.model);
720                 setmodel(self, self.model); // no precision needed
721         }
722         setorigin(self, self.origin);
723         if(self.scale)
724                 setsize(self, self.mins * self.scale, self.maxs * self.scale);
725         else
726                 setsize(self, self.mins, self.maxs);
727 }
728
729 /*
730 ================
731 InitTrigger
732 ================
733 */
734
735 void SetMovedir()
736 {
737         if (self.movedir != '0 0 0')
738                 self.movedir = normalize(self.movedir);
739         else
740         {
741                 makevectors (self.angles);
742                 self.movedir = v_forward;
743         }
744
745         self.angles = '0 0 0';
746 };
747
748 void InitTrigger()
749 {
750 // trigger angles are used for one-way touches.  An angle of 0 is assumed
751 // to mean no restrictions, so use a yaw of 360 instead.
752         if (self.movedir == '0 0 0')
753         if (self.angles != '0 0 0')
754                 SetMovedir ();
755         self.solid = SOLID_TRIGGER;
756         SetBrushEntityModel();
757         self.movetype = MOVETYPE_NONE;
758         self.modelindex = 0;
759         self.model = "";
760 };
761
762 void InitSolidBSPTrigger()
763 {
764 // trigger angles are used for one-way touches.  An angle of 0 is assumed
765 // to mean no restrictions, so use a yaw of 360 instead.
766         if (self.movedir == '0 0 0')
767         if (self.angles != '0 0 0')
768                 SetMovedir ();
769         self.solid = SOLID_BSP;
770         SetBrushEntityModel();
771         self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
772 //      self.modelindex = 0;
773         self.model = "";
774 };
775
776 float InitMovingBrushTrigger()
777 {
778 // trigger angles are used for one-way touches.  An angle of 0 is assumed
779 // to mean no restrictions, so use a yaw of 360 instead.
780         self.solid = SOLID_BSP;
781         SetBrushEntityModel();
782         self.movetype = MOVETYPE_PUSH;
783         if(self.modelindex == 0)
784         {
785                 objerror("InitMovingBrushTrigger: no brushes found!");
786                 return 0;
787         }
788         return 1;
789 };