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