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