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