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