Merge branch 'master' into martin-t/globals
[xonotic/xonotic-data.pk3dir.git] / qcsrc / lib / warpzone / common.qc
1 #include "common.qh"
2
3 #if defined(CSQC)
4     #include <common/t_items.qh>
5 #elif defined(MENUQC)
6 #elif defined(SVQC)
7     #include <common/weapons/_all.qh>
8 #endif
9
10 void WarpZone_Accumulator_Clear(entity acc)
11 {
12         acc.warpzone_transform = '0 0 0';
13         acc.warpzone_shift = '0 0 0';
14 }
15 void WarpZone_Accumulator_AddTransform(entity acc, vector t, vector s)
16 {
17         vector tr, st;
18         tr = AnglesTransform_Multiply(t, acc.warpzone_transform);
19         st = AnglesTransform_Multiply_GetPostShift(t, s, acc.warpzone_transform, acc.warpzone_shift);
20         acc.warpzone_transform = tr;
21         acc.warpzone_shift = st;
22 }
23 void WarpZone_Accumulator_Add(entity acc, entity wz)
24 {
25         WarpZone_Accumulator_AddTransform(acc, wz.warpzone_transform, wz.warpzone_shift);
26 }
27 void WarpZone_Accumulator_AddInverseTransform(entity acc, vector t, vector s)
28 {
29         vector tt, ss;
30         tt = AnglesTransform_Invert(t);
31         ss = AnglesTransform_PrePostShift_GetPostShift(s, tt, '0 0 0');
32         WarpZone_Accumulator_AddTransform(acc, tt, ss);
33         // yes, this probably can be done simpler... but this way is "obvious" :)
34 }
35 void WarpZone_Accumulator_AddInverse(entity acc, entity wz)
36 {
37         WarpZone_Accumulator_AddInverseTransform(acc, wz.warpzone_transform, wz.warpzone_shift);
38 }
39
40 float autocvar_cl_warpzone_usetrace = 1;
41 vector WarpZone_camera_transform(entity this, vector org, vector ang)
42 {
43         vector vf, vr, vu;
44         if(this.warpzone_fadestart)
45                 if(vdist(org - this.origin - 0.5 * (this.mins + this.maxs), >, this.warpzone_fadeend + 400))
46                         return org;
47                         // don't transform if zone faded out (plus 400qu safety margin for typical speeds and latencies)
48                         // unneeded on client, on server this helps a lot
49         vf = v_forward;
50         vr = v_right;
51         vu = v_up;
52         org = WarpZone_TransformOrigin(this, org);
53         vf = WarpZone_TransformVelocity(this, vf);
54         vr = WarpZone_TransformVelocity(this, vr);
55         vu = WarpZone_TransformVelocity(this, vu);
56         if(autocvar_cl_warpzone_usetrace)
57                 traceline(this.warpzone_targetorigin, org, MOVE_NOMONSTERS, NULL);
58         else
59                 trace_endpos = this.warpzone_targetorigin;
60         v_forward = vf;
61         v_right = vr;
62         v_up = vu;
63         return org;
64 }
65
66 void WarpZone_SetUp(entity e, vector my_org, vector my_ang, vector other_org, vector other_ang)
67 {
68         e.warpzone_transform = AnglesTransform_RightDivide(other_ang, AnglesTransform_TurnDirectionFR(my_ang));
69         e.warpzone_shift = AnglesTransform_PrePostShift_GetPostShift(my_org, e.warpzone_transform, other_org);
70         e.warpzone_origin = my_org;
71         e.warpzone_targetorigin = other_org;
72         e.warpzone_angles = my_ang;
73         e.warpzone_targetangles = other_ang;
74         FIXED_MAKE_VECTORS_NEW(my_ang, forward, right, up);
75         e.warpzone_forward = forward;
76         FIXED_MAKE_VECTORS(other_ang, forward, right, up);
77         e.warpzone_targetforward = forward;
78         setcamera_transform(e, WarpZone_camera_transform);
79 }
80
81 vector WarpZone_Camera_camera_transform(entity this, vector org, vector ang)
82 {
83         // a fixed camera view
84         if(this.warpzone_fadestart)
85                 if(vdist(org - this.origin - 0.5 * (this.mins + this.maxs), >, this.warpzone_fadeend + 400))
86                         return org;
87                         // don't transform if zone faded out (plus 400qu safety margin for typical speeds and latencies)
88                         // unneeded on client, on server this helps a lot
89         trace_endpos = this.warpzone_origin;
90         makevectors(this.warpzone_angles);
91         return this.warpzone_origin;
92 }
93
94 void WarpZone_Camera_SetUp(entity e, vector my_org, vector my_ang) // we assume that e.oldorigin and e.avelocity point to view origin and direction
95 {
96         e.warpzone_origin = my_org;
97         e.warpzone_angles = my_ang;
98         setcamera_transform(e, WarpZone_Camera_camera_transform);
99 }
100
101 .entity enemy;
102
103 vector WarpZoneLib_BoxTouchesBrush_mins;
104 vector WarpZoneLib_BoxTouchesBrush_maxs;
105 entity WarpZoneLib_BoxTouchesBrush_ent;
106 entity WarpZoneLib_BoxTouchesBrush_ignore;
107 float WarpZoneLib_BoxTouchesBrush_Recurse()
108 {
109         float s;
110         entity se;
111         float f;
112
113         tracebox('0 0 0', WarpZoneLib_BoxTouchesBrush_mins, WarpZoneLib_BoxTouchesBrush_maxs, '0 0 0', MOVE_NOMONSTERS, WarpZoneLib_BoxTouchesBrush_ignore);
114 #ifdef CSQC
115         if (trace_networkentity)
116         {
117                 LOG_TRACE("hit a network ent, cannot continue WarpZoneLib_BoxTouchesBrush");
118                 // we cannot continue, as a player blocks us...
119                 // so, abort
120                 return 0;
121         }
122 #endif
123         if (!trace_ent)
124                 return 0;
125         if (trace_ent == WarpZoneLib_BoxTouchesBrush_ent)
126                 return 1;
127
128         se = trace_ent;
129         s = se.solid;
130         se.solid = SOLID_NOT;
131         f = WarpZoneLib_BoxTouchesBrush_Recurse();
132         se.solid = s;
133
134         return f;
135 }
136
137 float WarpZoneLib_BoxTouchesBrush(vector mi, vector ma, entity e, entity ig)
138 {
139     float f, s;
140
141     if(!e.modelindex || e.warpzone_isboxy)
142         return 1;
143
144     s = e.solid;
145     e.solid = SOLID_BSP;
146     WarpZoneLib_BoxTouchesBrush_mins = mi;
147     WarpZoneLib_BoxTouchesBrush_maxs = ma;
148     WarpZoneLib_BoxTouchesBrush_ent = e;
149     WarpZoneLib_BoxTouchesBrush_ignore = ig;
150     f = WarpZoneLib_BoxTouchesBrush_Recurse();
151     e.solid = s;
152
153     return f;
154 }
155
156 entity WarpZone_Find(vector mi, vector ma)
157 {
158         // if we are near any warpzone planes - MOVE AWAY (work around nearclip)
159         if(!warpzone_warpzones_exist)
160                 return NULL;
161         IL_EACH(g_warpzones, WarpZoneLib_BoxTouchesBrush(mi, ma, it, NULL),
162         {
163                 return it;
164         });
165         return NULL;
166 }
167
168 void WarpZone_MakeAllSolid()
169 {
170         if(!warpzone_warpzones_exist)
171                 return;
172         IL_EACH(g_warpzones, true,
173         {
174                 it.solid = SOLID_BSP;
175         });
176 }
177
178 void WarpZone_MakeAllOther()
179 {
180         if(!warpzone_warpzones_exist)
181                 return;
182         IL_EACH(g_warpzones, true,
183         {
184                 it.solid = SOLID_TRIGGER;
185         });
186 }
187
188 void WarpZone_Trace_InitTransform()
189 {
190         if(!WarpZone_trace_transform)
191         {
192                 WarpZone_trace_transform = new_pure(warpzone_trace_transform);
193         }
194         WarpZone_Accumulator_Clear(WarpZone_trace_transform);
195 }
196 void WarpZone_Trace_AddTransform(entity wz)
197 {
198         WarpZone_Accumulator_Add(WarpZone_trace_transform, wz);
199 }
200
201 void WarpZone_TraceBox_ThroughZone(vector org, vector mi, vector ma, vector end, float nomonsters, entity forent, entity zone, WarpZone_trace_callback_t cb)
202 {
203         float nomonsters_adjusted;
204         float frac, sol, i;
205         float contentshack;
206         entity wz;
207         vector vf, vr, vu;
208
209         WarpZone_trace_forent = forent;
210         WarpZone_trace_firstzone = NULL;
211         WarpZone_trace_lastzone = NULL;
212         WarpZone_Trace_InitTransform();
213         if(!warpzone_warpzones_exist)
214         {
215                 if(nomonsters == MOVE_NOTHING)
216                 {
217                         trace_endpos = end;
218                         trace_fraction = 1;
219                         if(cb)
220                                 cb(org, trace_endpos, end);
221                         return;
222                 }
223                 else
224                 {
225                         tracebox(org, mi, ma, end, nomonsters, WarpZone_trace_forent);
226                         if(cb)
227                                 cb(org, trace_endpos, end);
228                         return;
229                 }
230         }
231
232         vf = v_forward;
233         vr = v_right;
234         vu = v_up;
235
236         switch(nomonsters)
237         {
238                 case MOVE_WORLDONLY:
239                 case MOVE_NOTHING:
240                         nomonsters_adjusted = MOVE_NOMONSTERS;
241                         break;
242                 default:
243                         nomonsters_adjusted = nomonsters;
244                         break;
245         }
246         if((contentshack = (WarpZone_trace_forent.dphitcontentsmask && !(WarpZone_trace_forent.dphitcontentsmask & DPCONTENTS_SOLID))))
247                 BITSET_ASSIGN(WarpZone_trace_forent.dphitcontentsmask, DPCONTENTS_SOLID);
248
249         // if starting in warpzone, first transform
250         wz = WarpZone_Find(org + mi, org + ma);
251         if(wz)
252         {
253                 WarpZone_trace_firstzone = wz;
254                 WarpZone_trace_lastzone = wz;
255                 if(zone && wz != zone)
256                 {
257                         // we are in ANOTHER warpzone. This is bad. Make a zero length trace and return.
258                         sol = 1;
259                         trace_fraction = 0;
260                         trace_endpos = org;
261                         goto fail;
262                 }
263                 WarpZone_Trace_AddTransform(wz);
264                 org = WarpZone_TransformOrigin(wz, org);
265                 end = WarpZone_TransformOrigin(wz, end);
266         }
267         WarpZone_MakeAllSolid();
268         sol = -1;
269         frac = 0;
270         i = 16;
271         for (;;)
272         {
273                 if(--i < 1)
274                 {
275                         LOG_TRACE("Too many warpzones in sequence, aborting trace.");
276                         trace_ent = NULL;
277                         break;
278                 }
279                 tracebox(org, mi, ma, end, nomonsters_adjusted, WarpZone_trace_forent);
280                 if(cb)
281                         cb(org, trace_endpos, end);
282                 if(sol < 0)
283                         sol = trace_startsolid;
284
285                 frac = trace_fraction = frac + (1 - frac) * trace_fraction;
286                 if(trace_fraction >= 1)
287                         break;
288                 if(trace_ent.classname != "trigger_warpzone")
289                 {
290                         if((nomonsters == MOVE_NOTHING) || ((nomonsters == MOVE_WORLDONLY) && trace_ent) || (contentshack && (trace_dphitcontents & WarpZone_trace_forent.dphitcontentsmask) == DPCONTENTS_SOLID))
291                         {
292                                 // continue the trace, ignoring this hit (we only care for warpzones)
293                                 org = trace_endpos + normalize(end - org);
294                                 continue;
295                                 // we cannot do an inverted trace here, as we do care for further warpzones inside that "solid" to be found
296                                 // otherwise, players could block entrances that way
297                         }
298                         break;
299                 }
300                 /*if(trace_ent == wz)
301                 {
302                         // FIXME can this check be removed? Do we really need it?
303                         LOG_TRACE("I transformed into the same zone again, wtf, aborting the trace");
304                         trace_ent = NULL;
305                         break;
306                 }*/
307                 wz = trace_ent;
308                 if(!WarpZone_trace_firstzone)
309                         WarpZone_trace_firstzone = wz;
310                 WarpZone_trace_lastzone = wz;
311                 if(zone && wz != zone)
312                         break;
313                 WarpZone_Trace_AddTransform(wz);
314                 // we hit a warpzone... so, let's perform the trace after the warp again
315                 org = WarpZone_TransformOrigin(wz, trace_endpos);
316                 end = WarpZone_TransformOrigin(wz, end);
317
318                 // we got warped, so let's step back a bit
319                 tracebox(org, mi, ma, org + normalize(org - end) * 32, nomonsters_adjusted, WarpZone_trace_forent);
320                 org = trace_endpos;
321         }
322         WarpZone_MakeAllOther();
323 LABEL(fail)
324         if(contentshack)
325                 BITCLR_ASSIGN(WarpZone_trace_forent.dphitcontentsmask, DPCONTENTS_SOLID);
326         trace_startsolid = sol;
327         v_forward = vf;
328         v_right = vr;
329         v_up = vu;
330 }
331
332 void WarpZone_TraceBox(vector org, vector mi, vector ma, vector end, float nomonsters, entity forent)
333 {
334         WarpZone_TraceBox_ThroughZone(org, mi, ma, end, nomonsters, forent, NULL, WarpZone_trace_callback_t_null);
335 }
336
337 void WarpZone_TraceLine(vector org, vector end, float nomonsters, entity forent)
338 {
339         WarpZone_TraceBox(org, '0 0 0', '0 0 0', end, nomonsters, forent);
340 }
341
342 void WarpZone_TraceToss_ThroughZone(entity e, entity forent, entity zone, WarpZone_trace_callback_t cb)
343 {
344         float g, dt, i;
345         vector vf, vr, vu, v0, o0;
346         entity wz;
347
348         o0 = e.origin;
349         v0 = e.velocity;
350         g = cvar("sv_gravity") * e.gravity;
351
352         WarpZone_trace_forent = forent;
353         WarpZone_trace_firstzone = NULL;
354         WarpZone_trace_lastzone = NULL;
355         WarpZone_Trace_InitTransform();
356         WarpZone_tracetoss_time = 0;
357         if(!warpzone_warpzones_exist)
358         {
359                 tracetoss(e, WarpZone_trace_forent);
360                 if(cb)
361                         cb(e.origin, trace_endpos, trace_endpos);
362                 dt = vlen(e.origin - o0) / vlen(e.velocity);
363                 WarpZone_tracetoss_time += dt;
364                 e.velocity_z -= dt * g;
365                 WarpZone_tracetoss_velocity = e.velocity;
366                 e.velocity = v0;
367                 return;
368         }
369
370         vf = v_forward;
371         vr = v_right;
372         vu = v_up;
373
374         // if starting in warpzone, first transform
375         wz = WarpZone_Find(e.origin + e.mins, e.origin + e.maxs);
376         if(wz)
377         {
378                 WarpZone_trace_firstzone = wz;
379                 WarpZone_trace_lastzone = wz;
380                 if(zone && wz != zone)
381                 {
382                         // we are in ANOTHER warpzone. This is bad. Make a zero length trace and return.
383
384                         WarpZone_tracetoss_time = 0;
385                         trace_endpos = o0;
386                         goto fail;
387                 }
388                 WarpZone_Trace_AddTransform(wz);
389                 vector org = WarpZone_TransformOrigin(wz, e.origin);
390                 setorigin(e, org);
391                 e.velocity = WarpZone_TransformVelocity(wz, e.velocity);
392         }
393         WarpZone_MakeAllSolid();
394         i = 16;
395         for (;;)
396         {
397                 if(--i < 1)
398                 {
399                         LOG_TRACE("Too many warpzones in sequence, aborting trace.");
400                         trace_ent = NULL;
401                         break;
402                 }
403                 tracetoss(e, WarpZone_trace_forent);
404                 if(cb)
405                         cb(e.origin, trace_endpos, trace_endpos);
406                 dt = vlen(trace_endpos - e.origin) / vlen(e.velocity);
407                 WarpZone_tracetoss_time += dt;
408                 e.origin = trace_endpos;
409                 e.velocity_z -= dt * g;
410                 if(trace_fraction >= 1)
411                         break;
412                 if(trace_ent.classname != "trigger_warpzone")
413                         break;
414                 if(trace_ent == wz)
415                 {
416                         // FIXME can this check be removed? Do we really need it?
417                         LOG_TRACE("I transformed into the same zone again, wtf, aborting the trace");
418                         trace_ent = NULL;
419                         break;
420                 }
421                 wz = trace_ent;
422                 if(!WarpZone_trace_firstzone)
423                         WarpZone_trace_firstzone = wz;
424                 WarpZone_trace_lastzone = wz;
425                 if(zone && wz != zone)
426                         break;
427                 WarpZone_Trace_AddTransform(wz);
428                 // we hit a warpzone... so, let's perform the trace after the warp again
429                 e.origin = WarpZone_TransformOrigin(wz, e.origin);
430                 e.velocity = WarpZone_TransformVelocity(wz, e.velocity);
431
432                 // we got warped, so let's step back a bit
433                 e.velocity = -e.velocity;
434                 tracetoss(e, WarpZone_trace_forent);
435                 dt = vlen(trace_endpos - e.origin) / vlen(e.velocity);
436                 WarpZone_tracetoss_time -= dt;
437                 e.origin = trace_endpos;
438                 e.velocity = -e.velocity;
439         }
440         WarpZone_MakeAllOther();
441 LABEL(fail)
442         WarpZone_tracetoss_velocity = e.velocity;
443         v_forward = vf;
444         v_right = vr;
445         v_up = vu;
446         // restore old entity data (caller just uses trace_endpos, WarpZone_tracetoss_velocity and the transform)
447         e.velocity = v0;
448         e.origin = o0;
449 }
450
451 void WarpZone_TraceToss(entity e, entity forent)
452 {
453         WarpZone_TraceToss_ThroughZone(e, forent, NULL, WarpZone_trace_callback_t_null);
454 }
455
456 entity WarpZone_TrailParticles_trace_callback_own;
457 float WarpZone_TrailParticles_trace_callback_eff;
458 void WarpZone_TrailParticles_trace_callback(vector from, vector endpos, vector to)
459 {
460         __trailparticles(WarpZone_TrailParticles_trace_callback_own, WarpZone_TrailParticles_trace_callback_eff, from, endpos);
461 }
462
463 void WarpZone_TrailParticles(entity own, float eff, vector org, vector end)
464 {
465         WarpZone_TrailParticles_trace_callback_own = own;
466         WarpZone_TrailParticles_trace_callback_eff = eff;
467         WarpZone_TraceBox_ThroughZone(org, '0 0 0', '0 0 0', end, MOVE_NOMONSTERS, NULL, NULL, WarpZone_TrailParticles_trace_callback);
468 }
469
470 #ifdef CSQC
471 float WarpZone_TrailParticles_trace_callback_f;
472 float WarpZone_TrailParticles_trace_callback_flags;
473 void WarpZone_TrailParticles_WithMultiplier_trace_callback(vector from, vector endpos, vector to)
474 {
475         boxparticles(WarpZone_TrailParticles_trace_callback_eff, WarpZone_TrailParticles_trace_callback_own, from, endpos, WarpZone_TrailParticles_trace_callback_own.velocity, WarpZone_TrailParticles_trace_callback_own.velocity, WarpZone_TrailParticles_trace_callback_f, WarpZone_TrailParticles_trace_callback_flags);
476 }
477
478 void WarpZone_TrailParticles_WithMultiplier(entity own, float eff, vector org, vector end, float f, int boxflags)
479 {
480         WarpZone_TrailParticles_trace_callback_own = own;
481         WarpZone_TrailParticles_trace_callback_eff = eff;
482         WarpZone_TrailParticles_trace_callback_f = f;
483         WarpZone_TrailParticles_trace_callback_flags = boxflags | PARTICLES_DRAWASTRAIL;
484         WarpZone_TraceBox_ThroughZone(org, '0 0 0', '0 0 0', end, MOVE_NOMONSTERS, NULL, NULL, WarpZone_TrailParticles_WithMultiplier_trace_callback);
485 }
486 #endif
487
488 float WarpZone_PlaneDist(entity wz, vector v)
489 {
490         return (v - wz.warpzone_origin) * wz.warpzone_forward;
491 }
492
493 float WarpZone_TargetPlaneDist(entity wz, vector v)
494 {
495         return (v - wz.warpzone_targetorigin) * wz.warpzone_targetforward;
496 }
497
498 vector WarpZone_TransformOrigin(entity wz, vector v)
499 {
500         return wz.warpzone_shift + AnglesTransform_Apply(wz.warpzone_transform, v);
501 }
502
503 vector WarpZone_TransformVelocity(entity wz, vector v)
504 {
505         return AnglesTransform_Apply(wz.warpzone_transform, v);
506 }
507
508 vector WarpZone_TransformAngles(entity wz, vector v)
509 {
510         return AnglesTransform_ApplyToAngles(wz.warpzone_transform, v);
511 }
512
513 vector WarpZone_TransformVAngles(entity wz, vector ang)
514 {
515 #ifdef KEEP_ROLL
516         float roll;
517         roll = ang.z;
518         ang.z = 0;
519 #endif
520
521         ang = AnglesTransform_ApplyToVAngles(wz.warpzone_transform, ang);
522
523 #ifdef KEEP_ROLL
524         ang = AnglesTransform_Normalize(ang, true);
525         ang = AnglesTransform_CancelRoll(ang);
526         ang.z = roll;
527 #else
528         ang = AnglesTransform_Normalize(ang, false);
529 #endif
530
531         return ang;
532 }
533
534 vector WarpZone_UnTransformOrigin(entity wz, vector v)
535 {
536         return AnglesTransform_Apply(AnglesTransform_Invert(wz.warpzone_transform), v - wz.warpzone_shift);
537 }
538
539 vector WarpZone_UnTransformVelocity(entity wz, vector v)
540 {
541         return AnglesTransform_Apply(AnglesTransform_Invert(wz.warpzone_transform), v);
542 }
543
544 vector WarpZone_UnTransformAngles(entity wz, vector v)
545 {
546         return AnglesTransform_ApplyToAngles(AnglesTransform_Invert(wz.warpzone_transform), v);
547 }
548
549 vector WarpZone_UnTransformVAngles(entity wz, vector ang)
550 {
551         float roll;
552
553         roll = ang.z;
554         ang.z = 0;
555
556         ang = AnglesTransform_ApplyToVAngles(AnglesTransform_Invert(wz.warpzone_transform), ang);
557         ang = AnglesTransform_Normalize(ang, true);
558         ang = AnglesTransform_CancelRoll(ang);
559
560         ang.z = roll;
561         return ang;
562 }
563
564 vector WarpZoneLib_NearestPointOnBox(vector mi, vector ma, vector org)
565 {
566         vector nearest;
567         nearest.x = bound(mi.x, org.x, ma.x);
568         nearest.y = bound(mi.y, org.y, ma.y);
569         nearest.z = bound(mi.z, org.z, ma.z);
570         return nearest;
571 }
572
573 bool WarpZoneLib_BadEntity(entity e)
574 {
575         if (is_pure(e)) return true;
576         string s = e.classname;
577
578         //if (s == "net_linked") return true; // actually some real entities are linked without classname, fail
579         if (s == "") return true;
580
581         if (startsWith(s, "target_")) return true;
582
583         if (startsWith(s, "info_")) return true;
584
585         return false;
586 }
587
588 .float WarpZone_findradius_hit;
589 .entity WarpZone_findradius_next;
590 void WarpZone_FindRadius_Recurse(
591     /** blast origin of current search */
592     vector org,
593     float rad,
594     /** original blast origin */
595     vector org0,
596     /** how to untransform (victim to blast system) */
597     vector transform,
598     vector shift,
599     bool needlineofsight)
600 {
601         if (rad <= 0) return;
602         entity wz = NULL;
603         FOREACH_ENTITY_RADIUS(org, rad, !WarpZoneLib_BadEntity(it), {
604                 vector p = WarpZoneLib_NearestPointOnBox(it.origin + it.mins, it.origin + it.maxs, org0);
605                 if (needlineofsight)
606                 {
607                         traceline(org, p, MOVE_NOMONSTERS, it);
608                         if (trace_fraction < 1) continue;
609                 }
610                 if (!it.WarpZone_findradius_hit || vlen2(it.WarpZone_findradius_dist) > vlen2(org0 - p))
611                 {
612                         it.WarpZone_findradius_nearest = p;
613                         it.WarpZone_findradius_dist = org0 - p;
614                         it.WarpZone_findradius_findorigin = org;
615                         it.WarpZone_findradius_findradius = rad;
616                         if (it.classname == "warpzone_refsys")
617                         {
618                                 // ignore, especially: do not overwrite the refsys parameters
619                         }
620                         else if (it.classname == "trigger_warpzone")
621                         {
622                                 it.WarpZone_findradius_next = wz;
623                                 wz = it;
624                                 it.WarpZone_findradius_hit = 1;
625                                 it.enemy.WarpZone_findradius_dist = '0 0 0'; // we don't want to go through this zone ever again
626                                 it.enemy.WarpZone_findradius_hit = 1;
627                         }
628                         else
629                         {
630                                 it.warpzone_transform = transform;
631                                 it.warpzone_shift = shift;
632                                 it.WarpZone_findradius_hit = 1;
633                         }
634                 }
635     });
636         for(entity e = wz; e; e = e.WarpZone_findradius_next)
637         {
638                 if (WarpZoneLib_BadEntity(e)) continue;
639
640                 vector org0_new = WarpZone_TransformOrigin(e, org);
641                 traceline(e.warpzone_targetorigin, org0_new, MOVE_NOMONSTERS, e);
642                 vector org_new = trace_endpos;
643
644                 vector transform_new = AnglesTransform_Multiply(e.warpzone_transform, transform);
645                 vector shift_new = AnglesTransform_Multiply_GetPostShift(e.warpzone_transform, e.warpzone_shift, transform, shift);
646                 WarpZone_FindRadius_Recurse(
647                         org_new,
648                         bound(0, rad - vlen(org_new - org0_new), rad - 8),
649                         org0_new,
650                         transform_new, shift_new,
651                         needlineofsight);
652                 e.WarpZone_findradius_hit = 0;
653                 e.enemy.WarpZone_findradius_hit = 0;
654         }
655 }
656 entity WarpZone_FindRadius(vector org, float rad, bool needlineofsight)
657 {
658     // FIXME: why can't we do this? (sometimes finds nothing, breaking explosions)
659     // if (!warpzone_warpzones_exist && !needlineofsight) return findradius(org, rad);
660         WarpZone_FindRadius_Recurse(org, rad, org, '0 0 0', '0 0 0', needlineofsight);
661         entity list_first = findchainfloat(WarpZone_findradius_hit, 1);
662         FOREACH_LIST(list, chain, true, it.WarpZone_findradius_hit = 0);
663         return list_first;
664 }
665
666 .entity WarpZone_refsys;
667 void WarpZone_RefSys_GC(entity this)
668 {
669         // garbage collect unused reference systems
670         this.nextthink = time + 1;
671         if(this.owner.WarpZone_refsys != this)
672                 delete(this);
673 }
674 void WarpZone_RefSys_CheckCreate(entity me)
675 {
676         if(me.WarpZone_refsys.owner != me)
677         {
678                 me.WarpZone_refsys = new(warpzone_refsys);
679                 me.WarpZone_refsys.owner = me;
680                 setthink(me.WarpZone_refsys, WarpZone_RefSys_GC);
681                 me.WarpZone_refsys.nextthink = time + 1;
682                 WarpZone_Accumulator_Clear(me.WarpZone_refsys);
683         }
684 }
685 void WarpZone_RefSys_Clear(entity me)
686 {
687         if(me.WarpZone_refsys)
688         {
689                 delete(me.WarpZone_refsys);
690                 me.WarpZone_refsys = NULL;
691         }
692 }
693 void WarpZone_RefSys_AddTransform(entity me, vector t, vector s)
694 {
695         if(t != '0 0 0' || s != '0 0 0')
696         {
697                 WarpZone_RefSys_CheckCreate(me);
698                 WarpZone_Accumulator_AddTransform(me.WarpZone_refsys, t, s);
699         }
700 }
701 void WarpZone_RefSys_Add(entity me, entity wz)
702 {
703         WarpZone_RefSys_AddTransform(me, wz.warpzone_transform, wz.warpzone_shift);
704 }
705 void WarpZone_RefSys_AddInverseTransform(entity me, vector t, vector s)
706 {
707         if(t != '0 0 0' || s != '0 0 0')
708         {
709                 WarpZone_RefSys_CheckCreate(me);
710                 WarpZone_Accumulator_AddInverseTransform(me.WarpZone_refsys, t, s);
711         }
712 }
713 void WarpZone_RefSys_AddInverse(entity me, entity wz)
714 {
715         WarpZone_RefSys_AddInverseTransform(me, wz.warpzone_transform, wz.warpzone_shift);
716 }
717 .vector WarpZone_refsys_incremental_shift;
718 .vector WarpZone_refsys_incremental_transform;
719 void WarpZone_RefSys_AddIncrementally(entity me, entity ref)
720 {
721         //vector t, s;
722         if(me.WarpZone_refsys_incremental_transform == ref.WarpZone_refsys.warpzone_transform)
723         if(me.WarpZone_refsys_incremental_shift == ref.WarpZone_refsys.warpzone_shift)
724                 return;
725         WarpZone_Accumulator_AddInverseTransform(me.WarpZone_refsys, me.WarpZone_refsys_incremental_transform, me.WarpZone_refsys_incremental_shift);
726         WarpZone_Accumulator_Add(me.WarpZone_refsys, ref.WarpZone_refsys);
727         me.WarpZone_refsys_incremental_shift = ref.WarpZone_refsys.warpzone_shift;
728         me.WarpZone_refsys_incremental_transform = ref.WarpZone_refsys.warpzone_transform;
729 }
730 void WarpZone_RefSys_BeginAddingIncrementally(entity me, entity ref)
731 {
732         me.WarpZone_refsys_incremental_shift = ref.WarpZone_refsys.warpzone_shift;
733         me.WarpZone_refsys_incremental_transform = ref.WarpZone_refsys.warpzone_transform;
734 }
735 vector WarpZone_RefSys_TransformOrigin(entity from, entity to, vector org)
736 {
737         if(from.WarpZone_refsys)
738                 org = WarpZone_UnTransformOrigin(from.WarpZone_refsys, org);
739         if(to.WarpZone_refsys)
740                 org = WarpZone_TransformOrigin(to.WarpZone_refsys, org);
741         return org;
742 }
743 vector WarpZone_RefSys_TransformVelocity(entity from, entity to, vector vel)
744 {
745         if(from.WarpZone_refsys)
746                 vel = WarpZone_UnTransformVelocity(from.WarpZone_refsys, vel);
747         if(to.WarpZone_refsys)
748                 vel = WarpZone_TransformVelocity(to.WarpZone_refsys, vel);
749         return vel;
750 }
751 vector WarpZone_RefSys_TransformAngles(entity from, entity to, vector ang)
752 {
753         if(from.WarpZone_refsys)
754                 ang = WarpZone_UnTransformAngles(from.WarpZone_refsys, ang);
755         if(to.WarpZone_refsys)
756                 ang = WarpZone_TransformAngles(to.WarpZone_refsys, ang);
757         return ang;
758 }
759 vector WarpZone_RefSys_TransformVAngles(entity from, entity to, vector ang)
760 {
761         if(from.WarpZone_refsys)
762                 ang = WarpZone_UnTransformVAngles(from.WarpZone_refsys, ang);
763         if(to.WarpZone_refsys)
764                 ang = WarpZone_TransformVAngles(to.WarpZone_refsys, ang);
765         return ang;
766 }
767 void WarpZone_RefSys_Copy(entity me, entity from)
768 {
769         if(from.WarpZone_refsys)
770         {
771                 WarpZone_RefSys_CheckCreate(me);
772                 me.WarpZone_refsys.warpzone_shift = from.WarpZone_refsys.warpzone_shift;
773                 me.WarpZone_refsys.warpzone_transform = from.WarpZone_refsys.warpzone_transform;
774         }
775         else
776                 WarpZone_RefSys_Clear(me);
777 }
778 entity WarpZone_RefSys_SpawnSameRefSys(entity me)
779 {
780         entity e;
781         e = spawn();
782         WarpZone_RefSys_Copy(e, me);
783         return e;
784 }
785
786 bool WarpZoneLib_ExactTrigger_Touch(entity this, entity toucher)
787 {
788         return !WarpZoneLib_BoxTouchesBrush(toucher.absmin, toucher.absmax, this, toucher);
789 }
790
791
792 void WarpZoneLib_MoveOutOfSolid_Expand(entity e, vector by)
793 {
794         float eps = 0.0625;
795         tracebox(e.origin, e.mins - '1 1 1' * eps, e.maxs + '1 1 1' * eps, e.origin + by, MOVE_WORLDONLY, e);
796         if (trace_startsolid)
797                 return;
798         if (trace_fraction < 1)
799         {
800                 // hit something
801                 // adjust origin in the other direction...
802                 setorigin(e, e.origin - by * (1 - trace_fraction));
803         }
804 }
805
806 bool WarpZoneLib_MoveOutOfSolid(entity e)
807 {
808         vector o = e.origin;
809         traceline(o, o, MOVE_WORLDONLY, e);
810         if (trace_startsolid)
811                 return false;
812
813         tracebox(o, e.mins, e.maxs, o, MOVE_WORLDONLY, e);
814         if (!trace_startsolid)
815                 return true;
816
817         vector m0 = e.mins;
818         vector m1 = e.maxs;
819         e.mins = '0 0 0';
820         e.maxs = '0 0 0';
821         WarpZoneLib_MoveOutOfSolid_Expand(e, eX * m0.x); e.mins_x = m0.x;
822         WarpZoneLib_MoveOutOfSolid_Expand(e, eX * m1.x); e.maxs_x = m1.x;
823         WarpZoneLib_MoveOutOfSolid_Expand(e, eY * m0.y); e.mins_y = m0.y;
824         WarpZoneLib_MoveOutOfSolid_Expand(e, eY * m1.y); e.maxs_y = m1.y;
825         WarpZoneLib_MoveOutOfSolid_Expand(e, eZ * m0.z); e.mins_z = m0.z;
826         WarpZoneLib_MoveOutOfSolid_Expand(e, eZ * m1.z); e.maxs_z = m1.z;
827         setorigin(e, e.origin);
828
829         tracebox(e.origin, e.mins, e.maxs, e.origin, MOVE_WORLDONLY, e);
830         if (trace_startsolid)
831         {
832                 setorigin(e, o);
833                 return false;
834         }
835
836         return true;
837 }