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