try to put a movement controller into SUB_CalcMove to allow for non-linear movement...
[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_controller_think (void)
181 {
182         float movephase;
183         float traveltime;
184         vector delta;
185         if(time < self.animstate_endtime) {
186                 delta = self.finaldest - self.origin;
187                 traveltime = self.animstate_endtime - self.animstate_starttime;
188                 
189                 self.owner.velocity = delta * (1/traveltime);   // QuakeC doesn't allow vector/float division
190                 self.nextthink = time;
191         } else {
192                 self = self.owner;
193                 self.think();
194         }
195 }
196
197 void SUB_CalcMove (vector tdest, float tspeed, void() func)
198 {
199         vector  delta;
200         float   traveltime;
201         entity controller;
202
203         if (!tspeed)
204                 objerror ("No speed is defined!");
205
206         self.think1 = func;
207         self.finaldest = tdest;
208         self.think = SUB_CalcMoveDone;
209
210         if (tdest == self.origin)
211         {
212                 self.velocity = '0 0 0';
213                 self.nextthink = self.ltime + 0.1;
214                 return;
215         }
216
217         delta = tdest - self.origin;
218         traveltime = vlen (delta) / tspeed;
219
220         if (traveltime < 0.1)
221         {
222                 self.velocity = '0 0 0';
223                 self.nextthink = self.ltime + 0.1;
224                 return;
225         }
226
227         controller = spawn();
228         controller.classname = "SUB_CalcMove_controller";
229         controller.owner = self;
230         controller.origin = self.origin; // starting point
231         controller.finaldest = tdest; // where do we want to end?
232         controller.animstate_starttime = self.ltime;
233         controller.animstate_endtime = self.ltime + traveltime;
234         controller.think = SUB_CalcMove_controller_think;
235         controller.nextthink = time;
236
237         //self.velocity = delta * (1/traveltime);       // QuakeC doesn't allow vector/float division
238
239         //self.nextthink = self.ltime + traveltime;
240 }
241
242 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeed, void() func)
243 {
244         entity  oldself;
245
246         oldself = self;
247         self = ent;
248
249         SUB_CalcMove (tdest, tspeed, func);
250
251         self = oldself;
252 }
253
254 /*
255 =============
256 SUB_CalcAngleMove
257
258 calculate self.avelocity and self.nextthink to reach destangle from
259 self.angles rotating
260
261 The calling function should make sure self.think is valid
262 ===============
263 */
264 void SUB_CalcAngleMoveDone (void)
265 {
266         // After rotating, set angle to exact final angle
267         self.angles = self.finalangle;
268         self.avelocity = '0 0 0';
269         self.nextthink = -1;
270         if (self.think1)
271                 self.think1 ();
272 }
273
274 // FIXME: I fixed this function only for rotation around the main axes
275 void SUB_CalcAngleMove (vector destangle, float tspeed, void() func)
276 {
277         vector  delta;
278         float   traveltime;
279
280         if (!tspeed)
281                 objerror ("No speed is defined!");
282
283         // take the shortest distance for the angles
284         self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
285         self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
286         self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
287         delta = destangle - self.angles;
288         traveltime = vlen (delta) / tspeed;
289
290         self.think1 = func;
291         self.finalangle = destangle;
292         self.think = SUB_CalcAngleMoveDone;
293
294         if (traveltime < 0.1)
295         {
296                 self.avelocity = '0 0 0';
297                 self.nextthink = self.ltime + 0.1;
298                 return;
299         }
300
301         self.avelocity = delta * (1 / traveltime);
302         self.nextthink = self.ltime + traveltime;
303 }
304
305 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeed, void() func)
306 {
307         entity  oldself;
308
309         oldself = self;
310         self = ent;
311
312         SUB_CalcAngleMove (destangle, tspeed, func);
313
314         self = oldself;
315 }
316
317 /*
318 ==================
319 main
320
321 unused but required by the engine
322 ==================
323 */
324 void main (void)
325 {
326
327 }
328
329 // Misc
330
331 /*
332 ==================
333 traceline_antilag
334
335 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
336 Additionally it moves players back into the past before the trace and restores them afterward.
337 ==================
338 */
339 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
340 {
341         local entity player;
342         local float oldsolid;
343
344         // check whether antilagged traces are enabled
345         if (lag < 0.001)
346                 lag = 0;
347         if (clienttype(forent) != CLIENTTYPE_REAL)
348                 lag = 0; // only antilag for clients
349
350         // change shooter to SOLID_BBOX so the shot can hit corpses
351         if(source)
352         {
353                 oldsolid = source.dphitcontentsmask;
354                 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
355         }
356
357         if (lag)
358         {
359                 // take players back into the past
360                 player = player_list;
361                 while (player)
362                 {
363                         antilag_takeback(player, time - lag);
364                         player = player.nextplayer;
365                 }
366         }
367
368         // do the trace
369         if(wz)
370                 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
371         else
372                 tracebox (v1, mi, ma, v2, nomonst, forent);
373
374         // restore players to current positions
375         if (lag)
376         {
377                 player = player_list;
378                 while (player)
379                 {
380                         antilag_restore(player);
381                         player = player.nextplayer;
382                 }
383         }
384
385         // restore shooter solid type
386         if(source)
387                 source.dphitcontentsmask = oldsolid;
388 }
389 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
390 {
391         tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
392 }
393 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
394 {
395         if (cvar("g_antilag") != 2 || source.cvar_cl_noantilag)
396                 lag = 0;
397         traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
398 }
399 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
400 {
401         if (cvar("g_antilag") != 2 || source.cvar_cl_noantilag)
402                 lag = 0;
403         tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
404 }
405 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
406 {
407         tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
408 }
409 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
410 {
411         if (cvar("g_antilag") != 2 || source.cvar_cl_noantilag)
412                 lag = 0;
413         WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
414 }
415 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
416 {
417         if (cvar("g_antilag") != 2 || source.cvar_cl_noantilag)
418                 lag = 0;
419         tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
420 }
421
422 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent) // returns the number of traces done, for benchmarking
423 {
424         vector pos, dir, t;
425         float nudge;
426
427         //nudge = 2 * cvar("collision_impactnudge"); // why not?
428         nudge = 0.5;
429
430         dir = normalize(v2 - v1);
431
432         pos = v1 + dir * nudge;
433
434         float c;
435         c = 0;
436
437         for(;;)
438         {
439                 if((pos - v1) * dir >= (v2 - v1) * dir)
440                 {
441                         // went too far
442                         trace_fraction = 1;
443                         trace_endpos = v2;
444                         return c;
445                 }
446
447                 tracebox(pos, mi, ma, v2, nomonsters, forent);
448                 ++c;
449
450                 if(c == 50)
451                 {
452                         dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
453                         dprint("  Nudging gets us nowhere at ", vtos(pos), "\n");
454                         dprint("  trace_endpos is ", vtos(trace_endpos), "\n");
455                         dprint("  trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
456                 }
457
458                 if(trace_startsolid)
459                 {
460                         // we started inside solid.
461                         // then trace from endpos to pos
462                         t = trace_endpos;
463                         tracebox(t, mi, ma, pos, nomonsters, forent);
464                         ++c;
465                         if(trace_startsolid)
466                         {
467                                 // t is still inside solid? bad
468                                 // force advance, then, and retry
469                                 pos = t + dir * nudge;
470                         }
471                         else
472                         {
473                                 // we actually LEFT solid!
474                                 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
475                                 return c;
476                         }
477                 }
478                 else
479                 {
480                         // pos is outside solid?!? but why?!? never mind, just return it.
481                         trace_endpos = pos;
482                         trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
483                         return c;
484                 }
485         }
486 }
487
488 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent)
489 {
490 #if 0
491         vector pos, dir, t;
492         float nudge;
493
494         //nudge = 2 * cvar("collision_impactnudge"); // why not?
495         nudge = 0.5;
496
497         dir = normalize(v2 - v1);
498
499         pos = v1 + dir * nudge;
500
501         for(;;)
502         {
503                 if((pos - v1) * dir >= (v2 - v1) * dir)
504                 {
505                         // went too far
506                         trace_fraction = 1;
507                         return;
508                 }
509
510                 traceline(pos, v2, nomonsters, forent);
511
512                 if(trace_startsolid)
513                 {
514                         // we started inside solid.
515                         // then trace from endpos to pos
516                         t = trace_endpos;
517                         traceline(t, pos, nomonsters, forent);
518                         if(trace_startsolid)
519                         {
520                                 // t is inside solid? bad
521                                 // force advance, then
522                                 pos = pos + dir * nudge;
523                         }
524                         else
525                         {
526                                 // we actually LEFT solid!
527                                 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
528                                 return;
529                         }
530                 }
531                 else
532                 {
533                         // pos is outside solid?!? but why?!? never mind, just return it.
534                         trace_endpos = pos;
535                         trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
536                         return;
537                 }
538         }
539 #else
540         tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent);
541 }
542
543 /*
544 ==================
545 findbetterlocation
546
547 Returns a point at least 12 units away from walls
548 (useful for explosion animations, although the blast is performed where it really happened)
549 Ripped from DPMod
550 ==================
551 */
552 vector findbetterlocation (vector org, float mindist)
553 {
554         vector  loc;
555         vector vec;
556         float c, h;
557
558         vec = mindist * '1 0 0';
559         c = 0;
560         while (c < 6)
561         {
562                 traceline (org, org + vec, TRUE, world);
563                 vec = vec * -1;
564                 if (trace_fraction < 1)
565                 {
566                         loc = trace_endpos;
567                         traceline (loc, loc + vec, TRUE, world);
568                         if (trace_fraction >= 1)
569                                 org = loc + vec;
570                 }
571                 if (c & 1)
572                 {
573                         h = vec_y;
574                         vec_y = vec_x;
575                         vec_x = vec_z;
576                         vec_z = h;
577                 }
578                 c = c + 1;
579         }
580
581         return org;
582 }
583
584 /*
585 ==================
586 crandom
587
588 Returns a random number between -1.0 and 1.0
589 ==================
590 */
591 float crandom (void)
592 {
593         return 2 * (random () - 0.5);
594 }
595
596 /*
597 ==================
598 Angc used for animations
599 ==================
600 */
601
602
603 float angc (float a1, float a2)
604 {
605         float   a;
606
607         while (a1 > 180)
608                 a1 = a1 - 360;
609         while (a1 < -179)
610                 a1 = a1 + 360;
611
612         while (a2 > 180)
613                 a2 = a2 - 360;
614         while (a2 < -179)
615                 a2 = a2 + 360;
616
617         a = a1 - a2;
618         while (a > 180)
619                 a = a - 360;
620         while (a < -179)
621                 a = a + 360;
622
623         return a;
624 }
625
626 .string lodtarget1;
627 .string lodtarget2;
628 .string lodmodel1;
629 .string lodmodel2;
630 .float lodmodelindex0;
631 .float lodmodelindex1;
632 .float lodmodelindex2;
633 .float loddistance1;
634 .float loddistance2;
635
636 float LOD_customize()
637 {
638         float d;
639
640         if(cvar("loddebug"))
641         {
642                 d = cvar("loddebug");
643                 if(d == 1)
644                         self.modelindex = self.lodmodelindex0;
645                 else if(d == 2 || !self.lodmodelindex2)
646                         self.modelindex = self.lodmodelindex1;
647                 else // if(d == 3)
648                         self.modelindex = self.lodmodelindex2;
649                 return TRUE;
650         }
651
652         // TODO csqc network this so it only gets sent once
653         d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
654         if(d < self.loddistance1)
655                 self.modelindex = self.lodmodelindex0;
656         else if(!self.lodmodelindex2 || d < self.loddistance2)
657                 self.modelindex = self.lodmodelindex1;
658         else
659                 self.modelindex = self.lodmodelindex2;
660
661         return TRUE;
662 }
663
664 void LOD_uncustomize()
665 {
666         self.modelindex = self.lodmodelindex0;
667 }
668
669 void LODmodel_attach()
670 {
671         entity e;
672
673         if(!self.loddistance1)
674                 self.loddistance1 = 1000;
675         if(!self.loddistance2)
676                 self.loddistance2 = 2000;
677         self.lodmodelindex0 = self.modelindex;
678
679         if(self.lodtarget1 != "")
680         {
681                 e = find(world, targetname, self.lodtarget1);
682                 if(e)
683                 {
684                         self.lodmodel1 = e.model;
685                         remove(e);
686                 }
687         }
688         if(self.lodtarget2 != "")
689         {
690                 e = find(world, targetname, self.lodtarget2);
691                 if(e)
692                 {
693                         self.lodmodel2 = e.model;
694                         remove(e);
695                 }
696         }
697
698         if(cvar("loddebug") < 0)
699         {
700                 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
701         }
702
703         if(self.lodmodel1 != "")
704         {
705                 vector mi, ma;
706                 mi = self.mins;
707                 ma = self.maxs;
708
709                 precache_model(self.lodmodel1);
710                 setmodel(self, self.lodmodel1);
711                 self.lodmodelindex1 = self.modelindex;
712
713                 if(self.lodmodel2 != "")
714                 {
715                         precache_model(self.lodmodel2);
716                         setmodel(self, self.lodmodel2);
717                         self.lodmodelindex2 = self.modelindex;
718                 }
719
720                 self.modelindex = self.lodmodelindex0;
721                 setsize(self, mi, ma);
722         }
723
724         if(self.lodmodelindex1)
725                 if not(self.SendEntity)
726                         SetCustomizer(self, LOD_customize, LOD_uncustomize);
727 }
728
729 void SetBrushEntityModel()
730 {
731         if(self.model != "")
732         {
733                 precache_model(self.model);
734                 setmodel(self, self.model); // no precision needed
735                 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
736         }
737         setorigin(self, self.origin);
738         if(self.scale)
739                 setsize(self, self.mins * self.scale, self.maxs * self.scale);
740         else
741                 setsize(self, self.mins, self.maxs);
742 }
743
744 void SetBrushEntityModelNoLOD()
745 {
746         if(self.model != "")
747         {
748                 precache_model(self.model);
749                 setmodel(self, self.model); // no precision needed
750         }
751         setorigin(self, self.origin);
752         if(self.scale)
753                 setsize(self, self.mins * self.scale, self.maxs * self.scale);
754         else
755                 setsize(self, self.mins, self.maxs);
756 }
757
758 /*
759 ================
760 InitTrigger
761 ================
762 */
763
764 void SetMovedir()
765 {
766         if (self.movedir != '0 0 0')
767                 self.movedir = normalize(self.movedir);
768         else
769         {
770                 makevectors (self.angles);
771                 self.movedir = v_forward;
772         }
773
774         self.angles = '0 0 0';
775 };
776
777 void InitTrigger()
778 {
779 // trigger angles are used for one-way touches.  An angle of 0 is assumed
780 // to mean no restrictions, so use a yaw of 360 instead.
781         if (self.movedir == '0 0 0')
782         if (self.angles != '0 0 0')
783                 SetMovedir ();
784         self.solid = SOLID_TRIGGER;
785         SetBrushEntityModel();
786         self.movetype = MOVETYPE_NONE;
787         self.modelindex = 0;
788         self.model = "";
789 };
790
791 void InitSolidBSPTrigger()
792 {
793 // trigger angles are used for one-way touches.  An angle of 0 is assumed
794 // to mean no restrictions, so use a yaw of 360 instead.
795         if (self.movedir == '0 0 0')
796         if (self.angles != '0 0 0')
797                 SetMovedir ();
798         self.solid = SOLID_BSP;
799         SetBrushEntityModel();
800         self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
801 //      self.modelindex = 0;
802         self.model = "";
803 };
804
805 float InitMovingBrushTrigger()
806 {
807 // trigger angles are used for one-way touches.  An angle of 0 is assumed
808 // to mean no restrictions, so use a yaw of 360 instead.
809         self.solid = SOLID_BSP;
810         SetBrushEntityModel();
811         self.movetype = MOVETYPE_PUSH;
812         if(self.modelindex == 0)
813         {
814                 objerror("InitMovingBrushTrigger: no brushes found!");
815                 return 0;
816         }
817         return 1;
818 };