]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/g_subs.qc
Implement all new platform movetypes. Thanks go to divVerent for the formulas, I...
[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()  SUB_CalcMoveDone;
6 void() SUB_CalcAngleMoveDone;
7 //void() SUB_UseTargets;
8 void() SUB_Remove;
9
10 void spawnfunc_info_null (void)
11 {
12         remove(self);
13         // if anything breaks, tell the mapper to fix his map! info_null is meant to remove itself immediately.
14 }
15
16 void setanim(entity e, vector anim, float looping, float override, float restart)
17 {
18         if (!anim)
19                 return; // no animation was given to us! We can't use this. 
20                 
21         if (anim_x == e.animstate_startframe)
22         if (anim_y == e.animstate_numframes)
23         if (anim_z == e.animstate_framerate)
24         {
25                 if(restart)
26                 {
27                         if(restart > 0)
28                         if(anim_y == 1) // ZYM animation
29                                 BITXOR_ASSIGN(e.effects, EF_RESTARTANIM_BIT);
30                 }
31                 else
32                         return;
33         }
34         e.animstate_startframe = anim_x;
35         e.animstate_numframes = anim_y;
36         e.animstate_framerate = anim_z;
37         e.animstate_starttime = servertime - 0.1 * serverframetime; // shift it a little bit into the past to prevent float inaccuracy hiccups
38         e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
39         e.animstate_looping = looping;
40         e.animstate_override = override;
41         e.frame = e.animstate_startframe;
42         e.frame1time = servertime;
43 }
44
45 void updateanim(entity e)
46 {
47         if (time >= e.animstate_endtime)
48         {
49                 if (e.animstate_looping)
50                 {
51                         e.animstate_starttime = e.animstate_endtime;
52                         e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
53                 }
54                 e.animstate_override = FALSE;
55         }
56         e.frame = e.animstate_startframe + bound(0, (time - e.animstate_starttime) * e.animstate_framerate, e.animstate_numframes - 1);
57         //print(ftos(time), " -> ", ftos(e.frame), "\n");
58 }
59
60 vector animfixfps(entity e, vector a)
61 {
62         // multi-frame anim: keep as-is
63         if(a_y == 1)
64         {
65                 float dur;
66                 dur = frameduration(e.modelindex, a_x);
67                 if(dur > 0)
68                         a_z = 1.0 / dur;
69         }
70         return a;
71 }
72
73 /*
74 ==================
75 SUB_Remove
76
77 Remove self
78 ==================
79 */
80 void SUB_Remove (void)
81 {
82         remove (self);
83 }
84
85 /*
86 ==================
87 SUB_Friction
88
89 Applies some friction to self
90 ==================
91 */
92 .float friction;
93 void SUB_Friction (void)
94 {
95         self.nextthink = time;
96         if(self.flags & FL_ONGROUND)
97                 self.velocity = self.velocity * (1 - frametime * self.friction);
98 }
99
100 /*
101 ==================
102 SUB_VanishOrRemove
103
104 Makes client invisible or removes non-client
105 ==================
106 */
107 void SUB_VanishOrRemove (entity ent)
108 {
109         if (ent.flags & FL_CLIENT)
110         {
111                 // vanish
112                 ent.alpha = -1;
113                 ent.effects = 0;
114                 ent.glow_size = 0;
115                 ent.pflags = 0;
116         }
117         else
118         {
119                 // remove
120                 remove (ent);
121         }
122 }
123
124 void SUB_SetFade_Think (void)
125 {
126         if(self.alpha == 0)
127                 self.alpha = 1;
128         self.think = SUB_SetFade_Think;
129         self.nextthink = time;
130         self.alpha -= frametime * self.fade_rate;
131         if (self.alpha < 0.01)
132                 SUB_VanishOrRemove(self);
133         else
134                 self.nextthink = time;
135 }
136
137 /*
138 ==================
139 SUB_SetFade
140
141 Fade 'ent' out when time >= 'when'
142 ==================
143 */
144 void SUB_SetFade (entity ent, float when, float fadetime)
145 {
146         //if (ent.flags & FL_CLIENT) // && ent.deadflag != DEAD_NO)
147         //      return;
148         //ent.alpha = 1;
149         ent.fade_rate = 1/fadetime;
150         ent.think = SUB_SetFade_Think;
151         ent.nextthink = when;
152 }
153
154 /*
155 =============
156 SUB_CalcMove
157
158 calculate self.velocity and self.nextthink to reach dest from
159 self.origin traveling at speed
160 ===============
161 */
162 void SUB_CalcMoveDone (void)
163 {
164         // After moving, set origin to exact final destination
165
166         setorigin (self, self.finaldest);
167         self.velocity = '0 0 0';
168         self.nextthink = -1;
169         if (self.think1)
170                 self.think1 ();
171 }
172
173 .float bezier_turn;
174 void SUB_CalcMove_controller_think (void)
175 {
176         entity oldself;
177         float traveltime;
178         float phasepos;
179         float nexttick;
180         vector delta;
181         vector delta2;
182         vector veloc;
183         vector nextpos;
184         delta = self.destvec;
185         delta2 = self.destvec2;
186         if(time < self.animstate_endtime) {
187                 nexttick = time + sys_frametime;
188
189                 traveltime = self.animstate_endtime - self.animstate_starttime;
190                 phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
191                 if(!self.platmovetype)
192                         self.platmovetype = 2; // default
193                 switch(self.platmovetype)
194                 {
195                         case 1: // linear
196                                 break;
197                         case 2: // cosine
198                                 /* old version, good for mathematical reference
199                                 phasepos = 3.14159265 + (phasepos * 3.14159265); // range: [pi, 2pi]
200                                 phasepos = cos(phasepos); // cos [pi, 2pi] is in [-1, 1]
201                                 phasepos = phasepos + 1; // correct range to [0, 2]
202                                 phasepos = phasepos / 2; // correct range to [0, 1]
203                                 */
204
205                                 phasepos = (1 - cos(phasepos * 3.14159265)) / 2;
206                                 break;
207                         case 3: // inverted cosine
208                                 phasepos = acos(1 - phasepos * 2) / 3.14159265;
209                                 break;
210                         case 4: // half cosine
211                                 phasepos = (1 - cos(phasepos * (3.14159265 / 2)));
212                                 break;
213                         case 5: // inverted half cosine
214                                 phasepos = sin(phasepos * (3.14159265 / 2));
215                                 break;
216                 }
217                 nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
218                 // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
219
220                 if(nexttick < self.animstate_endtime) {
221                         veloc = nextpos - self.owner.origin;
222                         veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
223                 } else {
224                         veloc = self.finaldest - self.owner.origin;
225                         veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
226                 }
227                 self.owner.velocity = veloc;
228                 if(self.owner.bezier_turn)
229                 {
230                         vector vel;
231                         vel = delta + 2 * delta2 * phasepos;
232                         vel_z = -vel_z; // invert z velocity
233                         vel = vectoangles(vel);
234                         self.owner.angles = vel;
235                 }
236                 self.nextthink = nexttick;
237         } else {
238                 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
239                 oldself = self;
240                 self.owner.think = self.think1;
241                 self = self.owner;
242                 remove(oldself);
243                 self.think();
244         }
245 }
246
247 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest)
248 {
249         // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
250         // 2 * control * t - 2 * control * t * t + dest * t * t
251         // 2 * control * t + (dest - 2 * control) * t * t
252
253         controller.origin = org; // starting point
254         control -= org;
255         dest -= org;
256
257         controller.destvec = 2 * control; // control point
258         controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
259         // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (dest - control)
260 }
261
262 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest)
263 {
264         // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
265         // 2 * control * t - 2 * control * t * t + dest * t * t
266         // 2 * control * t + (dest - 2 * control) * t * t
267
268         controller.origin = org; // starting point
269         dest -= org;
270
271         controller.destvec = dest; // end point
272         controller.destvec2 = '0 0 0';
273 }
274
275 float TSPEED_TIME = -1;
276 float TSPEED_LINEAR = 0;
277 float TSPEED_START = 1;
278 float TSPEED_END = 2;
279 // TODO average too?
280
281 void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeedtype, float tspeed, void() func)
282 {
283         float   traveltime;
284         entity controller;
285
286         if (!tspeed)
287                 objerror ("No speed is defined!");
288
289         self.think1 = func;
290         self.finaldest = tdest;
291         self.think = SUB_CalcMoveDone;
292
293         switch(tspeedtype)
294         {
295                 default:
296                 case TSPEED_START:
297                         traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
298                         break;
299                 case TSPEED_END:
300                         traveltime = 2 * vlen(tcontrol - tdest)       / tspeed;
301                         break;
302                 case TSPEED_LINEAR:
303                         traveltime = vlen(tdest - self.origin)        / tspeed;
304                         break;
305                 case TSPEED_TIME:
306                         traveltime = tspeed;
307                         break;
308         }
309
310         if (traveltime < 0.1) // useless anim
311         {
312                 self.velocity = '0 0 0';
313                 self.nextthink = self.ltime + 0.1;
314                 return;
315         }
316
317         controller = spawn();
318         controller.classname = "SUB_CalcMove_controller";
319         controller.owner = self;
320         controller.platmovetype = self.platmovetype;
321         SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
322         controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
323         controller.animstate_starttime = time;
324         controller.animstate_endtime = time + traveltime;
325         controller.think = SUB_CalcMove_controller_think;
326         controller.think1 = self.think;
327
328         // the thinking is now done by the controller
329         self.think = SUB_Null;
330         self.nextthink = self.ltime + traveltime;
331         
332         // invoke controller
333         self = controller;
334         self.think();
335         self = self.owner;
336 }
337
338 void SUB_CalcMove (vector tdest, float tspeedtype, float tspeed, void() func)
339 {
340         vector  delta;
341         float   traveltime;
342
343         if (!tspeed)
344                 objerror ("No speed is defined!");
345
346         self.think1 = func;
347         self.finaldest = tdest;
348         self.think = SUB_CalcMoveDone;
349
350         if (tdest == self.origin)
351         {
352                 self.velocity = '0 0 0';
353                 self.nextthink = self.ltime + 0.1;
354                 return;
355         }
356
357         delta = tdest - self.origin;
358
359         switch(tspeedtype)
360         {
361                 default:
362                 case TSPEED_START:
363                 case TSPEED_END:
364                 case TSPEED_LINEAR:
365                         traveltime = vlen (delta) / tspeed;
366                         break;
367                 case TSPEED_TIME:
368                         traveltime = tspeed;
369                         break;
370         }
371
372         // Very short animations don't really show off the effect
373         // of controlled animation, so let's just use linear movement.
374         // Alternatively entities can choose to specify non-controlled movement.
375         // The only currently implemented alternative movement is linear (value 1)
376         if (traveltime < 0.15 || self.platmovetype < 2)
377         {
378                 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
379                 self.nextthink = self.ltime + traveltime;
380                 return;
381         }
382
383         // now just run like a bezier curve...
384         SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
385 }
386
387 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void() func)
388 {
389         entity  oldself;
390
391         oldself = self;
392         self = ent;
393
394         SUB_CalcMove (tdest, tspeedtype, tspeed, func);
395
396         self = oldself;
397 }
398
399 /*
400 =============
401 SUB_CalcAngleMove
402
403 calculate self.avelocity and self.nextthink to reach destangle from
404 self.angles rotating
405
406 The calling function should make sure self.think is valid
407 ===============
408 */
409 void SUB_CalcAngleMoveDone (void)
410 {
411         // After rotating, set angle to exact final angle
412         self.angles = self.finalangle;
413         self.avelocity = '0 0 0';
414         self.nextthink = -1;
415         if (self.think1)
416                 self.think1 ();
417 }
418
419 // FIXME: I fixed this function only for rotation around the main axes
420 void SUB_CalcAngleMove (vector destangle, float tspeedtype, float tspeed, void() func)
421 {
422         vector  delta;
423         float   traveltime;
424
425         if (!tspeed)
426                 objerror ("No speed is defined!");
427
428         // take the shortest distance for the angles
429         self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
430         self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
431         self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
432         delta = destangle - self.angles;
433
434         switch(tspeedtype)
435         {
436                 default:
437                 case TSPEED_START:
438                 case TSPEED_END:
439                 case TSPEED_LINEAR:
440                         traveltime = vlen (delta) / tspeed;
441                         break;
442                 case TSPEED_TIME:
443                         traveltime = tspeed;
444                         break;
445         }
446
447         self.think1 = func;
448         self.finalangle = destangle;
449         self.think = SUB_CalcAngleMoveDone;
450
451         if (traveltime < 0.1)
452         {
453                 self.avelocity = '0 0 0';
454                 self.nextthink = self.ltime + 0.1;
455                 return;
456         }
457
458         self.avelocity = delta * (1 / traveltime);
459         self.nextthink = self.ltime + traveltime;
460 }
461
462 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void() func)
463 {
464         entity  oldself;
465
466         oldself = self;
467         self = ent;
468
469         SUB_CalcAngleMove (destangle, tspeedtype, tspeed, func);
470
471         self = oldself;
472 }
473
474 /*
475 ==================
476 main
477
478 unused but required by the engine
479 ==================
480 */
481 void main (void)
482 {
483
484 }
485
486 // Misc
487
488 /*
489 ==================
490 traceline_antilag
491
492 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
493 Additionally it moves players back into the past before the trace and restores them afterward.
494 ==================
495 */
496 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
497 {
498         entity player;
499         float oldsolid;
500
501         // check whether antilagged traces are enabled
502         if (lag < 0.001)
503                 lag = 0;
504         if (clienttype(forent) != CLIENTTYPE_REAL)
505                 lag = 0; // only antilag for clients
506
507         // change shooter to SOLID_BBOX so the shot can hit corpses
508         oldsolid = source.dphitcontentsmask;
509         if(source)
510                 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
511
512         if (lag)
513         {
514                 // take players back into the past
515                 FOR_EACH_PLAYER(player)
516                         if(player != forent)
517                                 antilag_takeback(player, time - lag);
518         }
519
520         // do the trace
521         if(wz)
522                 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
523         else
524                 tracebox (v1, mi, ma, v2, nomonst, forent);
525
526         // restore players to current positions
527         if (lag)
528         {
529                 FOR_EACH_PLAYER(player)
530                         if(player != forent)
531                                 antilag_restore(player);
532         }
533
534         // restore shooter solid type
535         if(source)
536                 source.dphitcontentsmask = oldsolid;
537 }
538 void traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
539 {
540         tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, FALSE);
541 }
542 void traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
543 {
544         if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
545                 lag = 0;
546         traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
547 }
548 void tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
549 {
550         if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
551                 lag = 0;
552         tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, FALSE);
553 }
554 void WarpZone_traceline_antilag_force (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
555 {
556         tracebox_antilag_force_wz(source, v1, '0 0 0', '0 0 0', v2, nomonst, forent, lag, TRUE);
557 }
558 void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag)
559 {
560         if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
561                 lag = 0;
562         WarpZone_traceline_antilag_force(source, v1, v2, nomonst, forent, lag);
563 }
564 void WarpZone_tracebox_antilag (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag)
565 {
566         if (autocvar_g_antilag != 2 || source.cvar_cl_noantilag)
567                 lag = 0;
568         tracebox_antilag_force_wz(source, v1, mi, ma, v2, nomonst, forent, lag, TRUE);
569 }
570
571 float tracebox_inverted (vector v1, vector mi, vector ma, vector v2, float nomonsters, entity forent) // returns the number of traces done, for benchmarking
572 {
573         vector pos, dir, t;
574         float nudge;
575
576         //nudge = 2 * cvar("collision_impactnudge"); // why not?
577         nudge = 0.5;
578
579         dir = normalize(v2 - v1);
580
581         pos = v1 + dir * nudge;
582
583         float c;
584         c = 0;
585
586         for(;;)
587         {
588                 if((pos - v1) * dir >= (v2 - v1) * dir)
589                 {
590                         // went too far
591                         trace_fraction = 1;
592                         trace_endpos = v2;
593                         return c;
594                 }
595
596                 tracebox(pos, mi, ma, v2, nomonsters, forent);
597                 ++c;
598
599                 if(c == 50)
600                 {
601                         dprint("HOLY SHIT! When tracing from ", vtos(v1), " to ", vtos(v2), "\n");
602                         dprint("  Nudging gets us nowhere at ", vtos(pos), "\n");
603                         dprint("  trace_endpos is ", vtos(trace_endpos), "\n");
604                         dprint("  trace distance is ", ftos(vlen(pos - trace_endpos)), "\n");
605                 }
606
607                 if(trace_startsolid)
608                 {
609                         // we started inside solid.
610                         // then trace from endpos to pos
611                         t = trace_endpos;
612                         tracebox(t, mi, ma, pos, nomonsters, forent);
613                         ++c;
614                         if(trace_startsolid)
615                         {
616                                 // t is still inside solid? bad
617                                 // force advance, then, and retry
618                                 pos = t + dir * nudge;
619                         }
620                         else
621                         {
622                                 // we actually LEFT solid!
623                                 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
624                                 return c;
625                         }
626                 }
627                 else
628                 {
629                         // pos is outside solid?!? but why?!? never mind, just return it.
630                         trace_endpos = pos;
631                         trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
632                         return c;
633                 }
634         }
635 }
636
637 void traceline_inverted (vector v1, vector v2, float nomonsters, entity forent)
638 {
639 #if 0
640         vector pos, dir, t;
641         float nudge;
642
643         //nudge = 2 * cvar("collision_impactnudge"); // why not?
644         nudge = 0.5;
645
646         dir = normalize(v2 - v1);
647
648         pos = v1 + dir * nudge;
649
650         for(;;)
651         {
652                 if((pos - v1) * dir >= (v2 - v1) * dir)
653                 {
654                         // went too far
655                         trace_fraction = 1;
656                         return;
657                 }
658
659                 traceline(pos, v2, nomonsters, forent);
660
661                 if(trace_startsolid)
662                 {
663                         // we started inside solid.
664                         // then trace from endpos to pos
665                         t = trace_endpos;
666                         traceline(t, pos, nomonsters, forent);
667                         if(trace_startsolid)
668                         {
669                                 // t is inside solid? bad
670                                 // force advance, then
671                                 pos = pos + dir * nudge;
672                         }
673                         else
674                         {
675                                 // we actually LEFT solid!
676                                 trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
677                                 return;
678                         }
679                 }
680                 else
681                 {
682                         // pos is outside solid?!? but why?!? never mind, just return it.
683                         trace_endpos = pos;
684                         trace_fraction = ((trace_endpos - v1) * dir) / ((v2 - v1) * dir);
685                         return;
686                 }
687         }
688 #else
689         tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent);
690 }
691
692 /*
693 ==================
694 findbetterlocation
695
696 Returns a point at least 12 units away from walls
697 (useful for explosion animations, although the blast is performed where it really happened)
698 Ripped from DPMod
699 ==================
700 */
701 vector findbetterlocation (vector org, float mindist)
702 {
703         vector  loc;
704         vector vec;
705         float c, h;
706
707         vec = mindist * '1 0 0';
708         c = 0;
709         while (c < 6)
710         {
711                 traceline (org, org + vec, TRUE, world);
712                 vec = vec * -1;
713                 if (trace_fraction < 1)
714                 {
715                         loc = trace_endpos;
716                         traceline (loc, loc + vec, TRUE, world);
717                         if (trace_fraction >= 1)
718                                 org = loc + vec;
719                 }
720                 if (c & 1)
721                 {
722                         h = vec_y;
723                         vec_y = vec_x;
724                         vec_x = vec_z;
725                         vec_z = h;
726                 }
727                 c = c + 1;
728         }
729
730         return org;
731 }
732
733 /*
734 ==================
735 crandom
736
737 Returns a random number between -1.0 and 1.0
738 ==================
739 */
740 float crandom (void)
741 {
742         return 2 * (random () - 0.5);
743 }
744
745 /*
746 ==================
747 Angc used for animations
748 ==================
749 */
750
751
752 float angc (float a1, float a2)
753 {
754         float   a;
755
756         while (a1 > 180)
757                 a1 = a1 - 360;
758         while (a1 < -179)
759                 a1 = a1 + 360;
760
761         while (a2 > 180)
762                 a2 = a2 - 360;
763         while (a2 < -179)
764                 a2 = a2 + 360;
765
766         a = a1 - a2;
767         while (a > 180)
768                 a = a - 360;
769         while (a < -179)
770                 a = a + 360;
771
772         return a;
773 }
774
775 .string lodtarget1;
776 .string lodtarget2;
777 .string lodmodel1;
778 .string lodmodel2;
779 .float lodmodelindex0;
780 .float lodmodelindex1;
781 .float lodmodelindex2;
782 .float loddistance1;
783 .float loddistance2;
784
785 float LOD_customize()
786 {
787         float d;
788
789         if(autocvar_loddebug)
790         {
791                 d = autocvar_loddebug;
792                 if(d == 1)
793                         self.modelindex = self.lodmodelindex0;
794                 else if(d == 2 || !self.lodmodelindex2)
795                         self.modelindex = self.lodmodelindex1;
796                 else // if(d == 3)
797                         self.modelindex = self.lodmodelindex2;
798                 return TRUE;
799         }
800
801         // TODO csqc network this so it only gets sent once
802         d = vlen(NearestPointOnBox(self, other.origin) - other.origin);
803         if(d < self.loddistance1)
804                 self.modelindex = self.lodmodelindex0;
805         else if(!self.lodmodelindex2 || d < self.loddistance2)
806                 self.modelindex = self.lodmodelindex1;
807         else
808                 self.modelindex = self.lodmodelindex2;
809
810         return TRUE;
811 }
812
813 void LOD_uncustomize()
814 {
815         self.modelindex = self.lodmodelindex0;
816 }
817
818 void LODmodel_attach()
819 {
820         entity e;
821
822         if(!self.loddistance1)
823                 self.loddistance1 = 1000;
824         if(!self.loddistance2)
825                 self.loddistance2 = 2000;
826         self.lodmodelindex0 = self.modelindex;
827
828         if(self.lodtarget1 != "")
829         {
830                 e = find(world, targetname, self.lodtarget1);
831                 if(e)
832                 {
833                         self.lodmodel1 = e.model;
834                         remove(e);
835                 }
836         }
837         if(self.lodtarget2 != "")
838         {
839                 e = find(world, targetname, self.lodtarget2);
840                 if(e)
841                 {
842                         self.lodmodel2 = e.model;
843                         remove(e);
844                 }
845         }
846
847         if(autocvar_loddebug < 0)
848         {
849                 self.lodmodel1 = self.lodmodel2 = ""; // don't even initialize
850         }
851
852         if(self.lodmodel1 != "")
853         {
854                 vector mi, ma;
855                 mi = self.mins;
856                 ma = self.maxs;
857
858                 precache_model(self.lodmodel1);
859                 setmodel(self, self.lodmodel1);
860                 self.lodmodelindex1 = self.modelindex;
861
862                 if(self.lodmodel2 != "")
863                 {
864                         precache_model(self.lodmodel2);
865                         setmodel(self, self.lodmodel2);
866                         self.lodmodelindex2 = self.modelindex;
867                 }
868
869                 self.modelindex = self.lodmodelindex0;
870                 setsize(self, mi, ma);
871         }
872
873         if(self.lodmodelindex1)
874                 if not(self.SendEntity)
875                         SetCustomizer(self, LOD_customize, LOD_uncustomize);
876 }
877
878 void ApplyMinMaxScaleAngles(entity e)
879 {
880         if(e.angles_x != 0 || e.angles_z != 0 || self.avelocity_x != 0 || self.avelocity_z != 0) // "weird" rotation
881         {
882                 e.maxs = '1 1 1' * vlen(
883                         '1 0 0' * max(-e.mins_x, e.maxs_x) +
884                         '0 1 0' * max(-e.mins_y, e.maxs_y) +
885                         '0 0 1' * max(-e.mins_z, e.maxs_z)
886                 );
887                 e.mins = -e.maxs;
888         }
889         else if(e.angles_y != 0 || self.avelocity_y != 0) // yaw only is a bit better
890         {
891                 e.maxs_x = vlen(
892                         '1 0 0' * max(-e.mins_x, e.maxs_x) +
893                         '0 1 0' * max(-e.mins_y, e.maxs_y)
894                 );
895                 e.maxs_y = e.maxs_x;
896                 e.mins_x = -e.maxs_x;
897                 e.mins_y = -e.maxs_x;
898         }
899         if(e.scale)
900                 setsize(e, e.mins * e.scale, e.maxs * e.scale);
901         else
902                 setsize(e, e.mins, e.maxs);
903 }
904
905 void SetBrushEntityModel()
906 {
907         if(self.model != "")
908         {
909                 precache_model(self.model);
910                 setmodel(self, self.model); // no precision needed
911                 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
912         }
913         setorigin(self, self.origin);
914         ApplyMinMaxScaleAngles(self);
915 }
916
917 void SetBrushEntityModelNoLOD()
918 {
919         if(self.model != "")
920         {
921                 precache_model(self.model);
922                 setmodel(self, self.model); // no precision needed
923         }
924         setorigin(self, self.origin);
925         ApplyMinMaxScaleAngles(self);
926 }
927
928 /*
929 ================
930 InitTrigger
931 ================
932 */
933
934 void SetMovedir()
935 {
936         if (self.movedir != '0 0 0')
937                 self.movedir = normalize(self.movedir);
938         else
939         {
940                 makevectors (self.angles);
941                 self.movedir = v_forward;
942         }
943
944         self.angles = '0 0 0';
945 }
946
947 void InitTrigger()
948 {
949 // trigger angles are used for one-way touches.  An angle of 0 is assumed
950 // to mean no restrictions, so use a yaw of 360 instead.
951         SetMovedir ();
952         self.solid = SOLID_TRIGGER;
953         SetBrushEntityModel();
954         self.movetype = MOVETYPE_NONE;
955         self.modelindex = 0;
956         self.model = "";
957 }
958
959 void InitSolidBSPTrigger()
960 {
961 // trigger angles are used for one-way touches.  An angle of 0 is assumed
962 // to mean no restrictions, so use a yaw of 360 instead.
963         SetMovedir ();
964         self.solid = SOLID_BSP;
965         SetBrushEntityModel();
966         self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
967 //      self.modelindex = 0;
968         self.model = "";
969 }
970
971 float InitMovingBrushTrigger()
972 {
973 // trigger angles are used for one-way touches.  An angle of 0 is assumed
974 // to mean no restrictions, so use a yaw of 360 instead.
975         self.solid = SOLID_BSP;
976         SetBrushEntityModel();
977         self.movetype = MOVETYPE_PUSH;
978         if(self.modelindex == 0)
979         {
980                 objerror("InitMovingBrushTrigger: no brushes found!");
981                 return 0;
982         }
983         return 1;
984 }