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