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