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