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