]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/server/g_subs.qc
Rename defs to qh
[xonotic/xonotic-data.pk3dir.git] / qcsrc / server / g_subs.qc
1 #if defined(CSQC)
2 #elif defined(MENUQC)
3 #elif defined(SVQC)
4         #include "../dpdefs/progsdefs.qh"
5     #include "../dpdefs/dpextensions.qh"
6     #include "../warpzonelib/common.qh"
7     #include "../common/util.qh"
8     #include "autocvars.qh"
9     #include "defs.qh"
10     #include "command/common.qh"
11     #include "../csqcmodellib/sv_model.qh"
12     #include "antilag.qh"
13 #endif
14
15 void SUB_NullThink(void) { }
16
17 void()  SUB_CalcMoveDone;
18 void() SUB_CalcAngleMoveDone;
19 //void() SUB_UseTargets;
20 void() SUB_Remove;
21
22 void spawnfunc_info_null (void)
23 {
24         remove(self);
25         // if anything breaks, tell the mapper to fix his map! info_null is meant to remove itself immediately.
26 }
27
28 void setanim(entity e, vector anim, float looping, float override, float restart)
29 {
30         if (!anim)
31                 return; // no animation was given to us! We can't use this.
32
33         if (anim_x == e.animstate_startframe)
34         if (anim_y == e.animstate_numframes)
35         if (anim_z == e.animstate_framerate)
36         {
37                 if(restart)
38                 {
39                         if(restart > 0)
40                         if(anim_y == 1) // ZYM animation
41                                 BITXOR_ASSIGN(e.effects, EF_RESTARTANIM_BIT);
42                 }
43                 else
44                         return;
45         }
46         e.animstate_startframe = anim.x;
47         e.animstate_numframes = anim.y;
48         e.animstate_framerate = anim.z;
49         e.animstate_starttime = servertime - 0.1 * serverframetime; // shift it a little bit into the past to prevent float inaccuracy hiccups
50         e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
51         e.animstate_looping = looping;
52         e.animstate_override = override;
53         e.frame = e.animstate_startframe;
54         e.frame1time = servertime;
55 }
56
57 void updateanim(entity e)
58 {
59         if (time >= e.animstate_endtime)
60         {
61                 if (e.animstate_looping)
62                 {
63                         e.animstate_starttime = e.animstate_endtime;
64                         e.animstate_endtime = e.animstate_starttime + e.animstate_numframes / e.animstate_framerate;
65                 }
66                 e.animstate_override = false;
67         }
68         e.frame = e.animstate_startframe + bound(0, (time - e.animstate_starttime) * e.animstate_framerate, e.animstate_numframes - 1);
69         //print(ftos(time), " -> ", ftos(e.frame), "\n");
70 }
71
72 /*
73 ==================
74 SUB_Remove
75
76 Remove self
77 ==================
78 */
79 void SUB_Remove (void)
80 {
81         remove (self);
82 }
83
84 /*
85 ==================
86 SUB_Friction
87
88 Applies some friction to self
89 ==================
90 */
91 .float friction;
92 void SUB_Friction (void)
93 {
94         self.nextthink = time;
95         if(self.flags & FL_ONGROUND)
96                 self.velocity = self.velocity * (1 - frametime * self.friction);
97 }
98
99 /*
100 ==================
101 SUB_VanishOrRemove
102
103 Makes client invisible or removes non-client
104 ==================
105 */
106 void SUB_VanishOrRemove (entity ent)
107 {
108         if (IS_CLIENT(ent))
109         {
110                 // vanish
111                 ent.alpha = -1;
112                 ent.effects = 0;
113                 ent.glow_size = 0;
114                 ent.pflags = 0;
115         }
116         else
117         {
118                 // remove
119                 remove (ent);
120         }
121 }
122
123 void SUB_SetFade_Think (void)
124 {
125         if(self.alpha == 0)
126                 self.alpha = 1;
127         self.think = SUB_SetFade_Think;
128         self.nextthink = time;
129         self.alpha -= frametime * self.fade_rate;
130         if (self.alpha < 0.01)
131                 SUB_VanishOrRemove(self);
132         else
133                 self.nextthink = time;
134 }
135
136 /*
137 ==================
138 SUB_SetFade
139
140 Fade 'ent' out when time >= 'when'
141 ==================
142 */
143 void SUB_SetFade (entity ent, float when, float fadetime)
144 {
145         ent.fade_rate = 1/fadetime;
146         ent.think = SUB_SetFade_Think;
147         ent.nextthink = when;
148 }
149
150 /*
151 =============
152 SUB_CalcMove
153
154 calculate self.velocity and self.nextthink to reach dest from
155 self.origin traveling at speed
156 ===============
157 */
158 void SUB_CalcMoveDone (void)
159 {
160         // After moving, set origin to exact final destination
161
162         setorigin (self, self.finaldest);
163         self.velocity = '0 0 0';
164         self.nextthink = -1;
165         if (self.think1)
166                 self.think1 ();
167 }
168
169 .float platmovetype_turn;
170 void SUB_CalcMove_controller_think (void)
171 {
172         entity oldself;
173         float traveltime;
174         float phasepos;
175         float nexttick;
176         vector delta;
177         vector delta2;
178         vector veloc;
179         vector angloc;
180         vector nextpos;
181         delta = self.destvec;
182         delta2 = self.destvec2;
183         if(time < self.animstate_endtime) {
184                 nexttick = time + sys_frametime;
185
186                 traveltime = self.animstate_endtime - self.animstate_starttime;
187                 phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
188                 phasepos = cubic_speedfunc(self.platmovetype_start, self.platmovetype_end, phasepos);
189                 nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
190                 // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
191
192                 if(self.owner.platmovetype_turn)
193                 {
194                         vector destangle;
195                         destangle = delta + 2 * delta2 * phasepos;
196                         destangle = vectoangles(destangle);
197                         destangle_x = -destangle.x; // flip up / down orientation
198
199                         // take the shortest distance for the angles
200                         self.owner.angles_x -= 360 * floor((self.owner.angles.x - destangle.x) / 360 + 0.5);
201                         self.owner.angles_y -= 360 * floor((self.owner.angles.y - destangle.y) / 360 + 0.5);
202                         self.owner.angles_z -= 360 * floor((self.owner.angles.z - destangle.z) / 360 + 0.5);
203                         angloc = destangle - self.owner.angles;
204                         angloc = angloc * (1 / sys_frametime); // so it arrives for the next frame
205                         self.owner.avelocity = angloc;
206                 }
207                 if(nexttick < self.animstate_endtime)
208                         veloc = nextpos - self.owner.origin;
209                 else
210                         veloc = self.finaldest - self.owner.origin;
211                 veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
212
213                 self.owner.velocity = veloc;
214                 self.nextthink = nexttick;
215         } else {
216                 // derivative: delta + 2 * delta2 (e.g. for angle positioning)
217                 oldself = self;
218                 self.owner.think = self.think1;
219                 self = self.owner;
220                 remove(oldself);
221                 self.think();
222         }
223 }
224
225 void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest)
226 {
227         // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
228         // 2 * control * t - 2 * control * t * t + dest * t * t
229         // 2 * control * t + (dest - 2 * control) * t * t
230
231         controller.origin = org; // starting point
232         control -= org;
233         dest -= org;
234
235         controller.destvec = 2 * control; // control point
236         controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
237         // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (dest - control)
238 }
239
240 void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest)
241 {
242         // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
243         // 2 * control * t - 2 * control * t * t + dest * t * t
244         // 2 * control * t + (dest - 2 * control) * t * t
245
246         controller.origin = org; // starting point
247         dest -= org;
248
249         controller.destvec = dest; // end point
250         controller.destvec2 = '0 0 0';
251 }
252
253 float TSPEED_TIME = -1;
254 float TSPEED_LINEAR = 0;
255 float TSPEED_START = 1;
256 float TSPEED_END = 2;
257 // TODO average too?
258
259 void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeedtype, float tspeed, void() func)
260 {
261         float   traveltime;
262         entity controller;
263
264         if (!tspeed)
265                 objerror ("No speed is defined!");
266
267         self.think1 = func;
268         self.finaldest = tdest;
269         self.think = SUB_CalcMoveDone;
270
271         switch(tspeedtype)
272         {
273                 default:
274                 case TSPEED_START:
275                         traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
276                         break;
277                 case TSPEED_END:
278                         traveltime = 2 * vlen(tcontrol - tdest)       / tspeed;
279                         break;
280                 case TSPEED_LINEAR:
281                         traveltime = vlen(tdest - self.origin)        / tspeed;
282                         break;
283                 case TSPEED_TIME:
284                         traveltime = tspeed;
285                         break;
286         }
287
288         if (traveltime < 0.1) // useless anim
289         {
290                 self.velocity = '0 0 0';
291                 self.nextthink = self.ltime + 0.1;
292                 return;
293         }
294
295         controller = spawn();
296         controller.classname = "SUB_CalcMove_controller";
297         controller.owner = self;
298         controller.platmovetype = self.platmovetype;
299         controller.platmovetype_start = self.platmovetype_start;
300         controller.platmovetype_end = self.platmovetype_end;
301         SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
302         controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
303         controller.animstate_starttime = time;
304         controller.animstate_endtime = time + traveltime;
305         controller.think = SUB_CalcMove_controller_think;
306         controller.think1 = self.think;
307
308         // the thinking is now done by the controller
309         self.think = SUB_NullThink; // for PushMove
310         self.nextthink = self.ltime + traveltime;
311
312         // invoke controller
313         self = controller;
314         self.think();
315         self = self.owner;
316 }
317
318 void SUB_CalcMove (vector tdest, float tspeedtype, float tspeed, void() func)
319 {
320         vector  delta;
321         float   traveltime;
322
323         if (!tspeed)
324                 objerror ("No speed is defined!");
325
326         self.think1 = func;
327         self.finaldest = tdest;
328         self.think = SUB_CalcMoveDone;
329
330         if (tdest == self.origin)
331         {
332                 self.velocity = '0 0 0';
333                 self.nextthink = self.ltime + 0.1;
334                 return;
335         }
336
337         delta = tdest - self.origin;
338
339         switch(tspeedtype)
340         {
341                 default:
342                 case TSPEED_START:
343                 case TSPEED_END:
344                 case TSPEED_LINEAR:
345                         traveltime = vlen (delta) / tspeed;
346                         break;
347                 case TSPEED_TIME:
348                         traveltime = tspeed;
349                         break;
350         }
351
352         // Very short animations don't really show off the effect
353         // of controlled animation, so let's just use linear movement.
354         // Alternatively entities can choose to specify non-controlled movement.
355         // The only currently implemented alternative movement is linear (value 1)
356         if (traveltime < 0.15 || (self.platmovetype_start == 1 && self.platmovetype_end == 1)) // is this correct?
357         {
358                 self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
359                 self.nextthink = self.ltime + traveltime;
360                 return;
361         }
362
363         // now just run like a bezier curve...
364         SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
365 }
366
367 void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void() func)
368 {
369         entity  oldself;
370
371         oldself = self;
372         self = ent;
373
374         SUB_CalcMove (tdest, tspeedtype, tspeed, func);
375
376         self = oldself;
377 }
378
379 /*
380 =============
381 SUB_CalcAngleMove
382
383 calculate self.avelocity and self.nextthink to reach destangle from
384 self.angles rotating
385
386 The calling function should make sure self.think is valid
387 ===============
388 */
389 void SUB_CalcAngleMoveDone (void)
390 {
391         // After rotating, set angle to exact final angle
392         self.angles = self.finalangle;
393         self.avelocity = '0 0 0';
394         self.nextthink = -1;
395         if (self.think1)
396                 self.think1 ();
397 }
398
399 // FIXME: I fixed this function only for rotation around the main axes
400 void SUB_CalcAngleMove (vector destangle, float tspeedtype, float tspeed, void() func)
401 {
402         vector  delta;
403         float   traveltime;
404
405         if (!tspeed)
406                 objerror ("No speed is defined!");
407
408         // take the shortest distance for the angles
409         self.angles_x -= 360 * floor((self.angles.x - destangle.x) / 360 + 0.5);
410         self.angles_y -= 360 * floor((self.angles.y - destangle.y) / 360 + 0.5);
411         self.angles_z -= 360 * floor((self.angles.z - destangle.z) / 360 + 0.5);
412         delta = destangle - self.angles;
413
414         switch(tspeedtype)
415         {
416                 default:
417                 case TSPEED_START:
418                 case TSPEED_END:
419                 case TSPEED_LINEAR:
420                         traveltime = vlen (delta) / tspeed;
421                         break;
422                 case TSPEED_TIME:
423                         traveltime = tspeed;
424                         break;
425         }
426
427         self.think1 = func;
428         self.finalangle = destangle;
429         self.think = SUB_CalcAngleMoveDone;
430
431         if (traveltime < 0.1)
432         {
433                 self.avelocity = '0 0 0';
434                 self.nextthink = self.ltime + 0.1;
435                 return;
436         }
437
438         self.avelocity = delta * (1 / traveltime);
439         self.nextthink = self.ltime + traveltime;
440 }
441
442 void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void() func)
443 {
444         entity  oldself;
445
446         oldself = self;
447         self = ent;
448
449         SUB_CalcAngleMove (destangle, tspeedtype, tspeed, func);
450
451         self = oldself;
452 }
453
454 /*
455 ==================
456 main
457
458 unused but required by the engine
459 ==================
460 */
461 void main (void)
462 {
463
464 }
465
466 // Misc
467
468 /*
469 ==================
470 traceline_antilag
471
472 A version of traceline that must be used by SOLID_SLIDEBOX things that want to hit SOLID_CORPSE things with a trace attack
473 Additionally it moves players back into the past before the trace and restores them afterward.
474 ==================
475 */
476 void tracebox_antilag_force_wz (entity source, vector v1, vector mi, vector ma, vector v2, float nomonst, entity forent, float lag, float wz)
477 {
478         entity player;
479         float oldsolid;
480
481         // check whether antilagged traces are enabled
482         if (lag < 0.001)
483                 lag = 0;
484         if (!IS_REAL_CLIENT(forent))
485                 lag = 0; // only antilag for clients
486
487         // change shooter to SOLID_BBOX so the shot can hit corpses
488         oldsolid = source.dphitcontentsmask;
489         if(source)
490                 source.dphitcontentsmask = DPCONTENTS_SOLID | DPCONTENTS_BODY | DPCONTENTS_CORPSE;
491
492         if (lag)
493         {
494                 // take players back into the past
495                 FOR_EACH_PLAYER(player)
496                         if(player != forent)
497                                 antilag_takeback(player, time - lag);
498                 FOR_EACH_MONSTER(player)
499                         antilag_takeback(player, time - lag);
500         }
501
502         // do the trace
503         if(wz)
504                 WarpZone_TraceBox (v1, mi, ma, v2, nomonst, forent);
505         else
506                 tracebox (v1, mi, ma, v2, nomonst, forent);
507
508         // restore players to current positions
509         if (lag)
510         {
511                 FOR_EACH_PLAYER(player)
512                         if(player != forent)
513                                 antilag_restore(player);
514                 FOR_EACH_MONSTER(player)
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, entity ignorestopatentity) // 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(0;;)
572         {
573                 if(pos * dir >= v2 * 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 && stopentity != ignorestopatentity)
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, entity ignorestopatentity)
634 {
635         tracebox_inverted(v1, '0 0 0', '0 0 0', v2, nomonsters, forent, stopatentity, ignorestopatentity);
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 (!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                 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
857                 {
858                         vector mi = self.mins;
859                         vector ma = self.maxs;
860                         setmodel(self, self.model); // no precision needed
861                         setsize(self, mi, ma);
862                 }
863                 else
864                         setmodel(self, self.model); // no precision needed
865                 InitializeEntity(self, LODmodel_attach, INITPRIO_FINDTARGET);
866         }
867         setorigin(self, self.origin);
868         ApplyMinMaxScaleAngles(self);
869 }
870
871 void SetBrushEntityModelNoLOD()
872 {
873         if(self.model != "")
874         {
875                 precache_model(self.model);
876                 if(self.mins != '0 0 0' || self.maxs != '0 0 0')
877                 {
878                         vector mi = self.mins;
879                         vector ma = self.maxs;
880                         setmodel(self, self.model); // no precision needed
881                         setsize(self, mi, ma);
882                 }
883                 else
884                         setmodel(self, self.model); // no precision needed
885         }
886         setorigin(self, self.origin);
887         ApplyMinMaxScaleAngles(self);
888 }
889
890 /*
891 ================
892 InitTrigger
893 ================
894 */
895
896 void SetMovedir()
897 {
898         if (self.movedir != '0 0 0')
899                 self.movedir = normalize(self.movedir);
900         else
901         {
902                 makevectors (self.angles);
903                 self.movedir = v_forward;
904         }
905
906         self.angles = '0 0 0';
907 }
908
909 void InitTrigger()
910 {
911 // trigger angles are used for one-way touches.  An angle of 0 is assumed
912 // to mean no restrictions, so use a yaw of 360 instead.
913         SetMovedir ();
914         self.solid = SOLID_TRIGGER;
915         SetBrushEntityModel();
916         self.movetype = MOVETYPE_NONE;
917         self.modelindex = 0;
918         self.model = "";
919 }
920
921 void InitSolidBSPTrigger()
922 {
923 // trigger angles are used for one-way touches.  An angle of 0 is assumed
924 // to mean no restrictions, so use a yaw of 360 instead.
925         SetMovedir ();
926         self.solid = SOLID_BSP;
927         SetBrushEntityModel();
928         self.movetype = MOVETYPE_NONE; // why was this PUSH? -div0
929 //      self.modelindex = 0;
930         self.model = "";
931 }
932
933 float InitMovingBrushTrigger()
934 {
935 // trigger angles are used for one-way touches.  An angle of 0 is assumed
936 // to mean no restrictions, so use a yaw of 360 instead.
937         self.solid = SOLID_BSP;
938         SetBrushEntityModel();
939         self.movetype = MOVETYPE_PUSH;
940         if(self.modelindex == 0)
941         {
942                 objerror("InitMovingBrushTrigger: no brushes found!");
943                 return 0;
944         }
945         return 1;
946 }