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