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