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