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