]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/warpzonelib/common.qc
Get rid of unrefs. again..
[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 void WarpZone_TrailParticles_WithMultiplier(entity own, float eff, vector org, vector end, float f, float boxflags)
473 {
474         WarpZone_TrailParticles_trace_callback_own = own;
475         WarpZone_TrailParticles_trace_callback_eff = eff;
476         WarpZone_TrailParticles_trace_callback_f = f;
477         WarpZone_TrailParticles_trace_callback_flags = boxflags;
478         WarpZone_TraceBox_ThroughZone(org, '0 0 0', '0 0 0', end, MOVE_NOMONSTERS, world, world, WarpZone_TrailParticles_WithMultiplier_trace_callback);
479 }
480 #endif
481
482 float WarpZone_PlaneDist(entity wz, vector v)
483 {
484         return (v - wz.warpzone_origin) * wz.warpzone_forward;
485 }
486
487 float WarpZone_TargetPlaneDist(entity wz, vector v)
488 {
489         return (v - wz.warpzone_targetorigin) * wz.warpzone_targetforward;
490 }
491
492 vector WarpZone_TransformOrigin(entity wz, vector v)
493 {
494         return wz.warpzone_shift + AnglesTransform_Apply(wz.warpzone_transform, v);
495 }
496
497 vector WarpZone_TransformVelocity(entity wz, vector v)
498 {
499         return AnglesTransform_Apply(wz.warpzone_transform, v);
500 }
501
502 vector WarpZone_TransformAngles(entity wz, vector v)
503 {
504         return AnglesTransform_ApplyToAngles(wz.warpzone_transform, v);
505 }
506
507 vector WarpZone_TransformVAngles(entity wz, vector ang)
508 {
509 #ifdef KEEP_ROLL
510         float roll;
511         roll = ang_z;
512         ang_z = 0;
513 #endif
514
515         ang = AnglesTransform_ApplyToVAngles(wz.warpzone_transform, ang);
516
517 #ifdef KEEP_ROLL
518         ang = AnglesTransform_Normalize(ang, TRUE);
519         ang = AnglesTransform_CancelRoll(ang);
520         ang_z = roll;
521 #else
522         ang = AnglesTransform_Normalize(ang, FALSE);
523 #endif
524
525         return ang;
526 }
527
528 vector WarpZone_UnTransformOrigin(entity wz, vector v)
529 {
530         return AnglesTransform_Apply(AnglesTransform_Invert(wz.warpzone_transform), v - wz.warpzone_shift);
531 }
532
533 vector WarpZone_UnTransformVelocity(entity wz, vector v)
534 {
535         return AnglesTransform_Apply(AnglesTransform_Invert(wz.warpzone_transform), v);
536 }
537
538 vector WarpZone_UnTransformAngles(entity wz, vector v)
539 {
540         return AnglesTransform_ApplyToAngles(AnglesTransform_Invert(wz.warpzone_transform), v);
541 }
542
543 vector WarpZone_UnTransformVAngles(entity wz, vector ang)
544 {
545         float roll;
546
547         roll = ang_z;
548         ang_z = 0;
549
550         ang = AnglesTransform_ApplyToVAngles(AnglesTransform_Invert(wz.warpzone_transform), ang);
551         ang = AnglesTransform_Normalize(ang, TRUE);
552         ang = AnglesTransform_CancelRoll(ang);
553
554         ang_z = roll;
555         return ang;
556 }
557
558 vector WarpZoneLib_NearestPointOnBox(vector mi, vector ma, vector org)
559 {
560         vector nearest;
561         nearest_x = bound(mi_x, org_x, ma_x);
562         nearest_y = bound(mi_y, org_y, ma_y);
563         nearest_z = bound(mi_z, org_z, ma_z);
564         return nearest;
565 }
566
567 .float WarpZone_findradius_hit;
568 .entity WarpZone_findradius_next;
569 void WarpZone_FindRadius_Recurse(vector org, float rad,        vector org0,               vector transform, vector shift, float needlineofsight)
570 //                               blast origin of current search   original blast origin   how to untransform (victim to blast system)
571 {
572         vector org_new;
573         vector org0_new;
574         vector shift_new, transform_new;
575         vector p;
576         entity e, e0;
577         entity wz;
578         if(rad <= 0)
579                 return;
580         e0 = findradius(org, rad);
581         wz = world;
582
583         for(e = e0; e; e = e.chain)
584         {
585                 p = WarpZoneLib_NearestPointOnBox(e.origin + e.mins, e.origin + e.maxs, org0);
586                 if(needlineofsight)
587                 {
588                         traceline(org, p, MOVE_NOMONSTERS, e);
589                         if(trace_fraction < 1)
590                                 continue;
591                 }
592                 if(!e.WarpZone_findradius_hit || vlen(e.WarpZone_findradius_dist) > vlen(org0 - p))
593                 {
594                         e.WarpZone_findradius_nearest = p;
595                         e.WarpZone_findradius_dist = org0 - p;
596                         e.WarpZone_findradius_findorigin = org;
597                         e.WarpZone_findradius_findradius = rad;
598                         if(e.classname == "warpzone_refsys")
599                         {
600                                 // ignore, especially: do not overwrite the refsys parameters
601                         }
602                         else if(e.classname == "trigger_warpzone")
603                         {
604                                 e.WarpZone_findradius_next = wz;
605                                 wz = e;
606                                 e.WarpZone_findradius_hit = 1;
607                                 e.enemy.WarpZone_findradius_dist = '0 0 0'; // we don't want to go through this zone ever again
608                                 e.enemy.WarpZone_findradius_hit = 1;
609                         }
610                         else
611                         {
612                                 e.warpzone_transform = transform;
613                                 e.warpzone_shift = shift;
614                                 e.WarpZone_findradius_hit = 1;
615                         }
616                 }
617         }
618         for(e = wz; e; e = e.WarpZone_findradius_next)
619         {
620                 org0_new = WarpZone_TransformOrigin(e, org);
621                 traceline(e.warpzone_targetorigin, org0_new, MOVE_NOMONSTERS, e);
622                 org_new = trace_endpos;
623
624                 transform_new = AnglesTransform_Multiply(e.warpzone_transform, transform);
625                 shift_new = AnglesTransform_Multiply_GetPostShift(e.warpzone_transform, e.warpzone_shift, transform, shift);
626                 WarpZone_FindRadius_Recurse(
627                         org_new,
628                         bound(0, rad - vlen(org_new - org0_new), rad - 8),
629                         org0_new,
630                         transform_new, shift_new,
631                         needlineofsight);
632                 e.WarpZone_findradius_hit = 0;
633                 e.enemy.WarpZone_findradius_hit = 0;
634         }
635 }
636 entity WarpZone_FindRadius(vector org, float rad, float needlineofsight)
637 {
638         entity e0, e;
639         WarpZone_FindRadius_Recurse(org, rad, org, '0 0 0', '0 0 0', needlineofsight);
640         e0 = findchainfloat(WarpZone_findradius_hit, 1);
641         for(e = e0; e; e = e.chain)
642                 e.WarpZone_findradius_hit = 0;
643         return e0;
644 }
645
646 .entity WarpZone_refsys;
647 void WarpZone_RefSys_GC()
648 {
649         // garbage collect unused reference systems
650         self.nextthink = time + 1;
651         if(self.owner.WarpZone_refsys != self)
652                 remove(self);
653 }
654 void WarpZone_RefSys_CheckCreate(entity me)
655 {
656         if(me.WarpZone_refsys.owner != me)
657         {
658                 me.WarpZone_refsys = spawn();
659                 me.WarpZone_refsys.classname = "warpzone_refsys";
660                 me.WarpZone_refsys.owner = me;
661                 me.WarpZone_refsys.think = WarpZone_RefSys_GC;
662                 me.WarpZone_refsys.nextthink = time + 1;
663                 WarpZone_Accumulator_Clear(me.WarpZone_refsys);
664         }
665 }
666 void WarpZone_RefSys_Clear(entity me)
667 {
668         if(me.WarpZone_refsys)
669         {
670                 remove(me.WarpZone_refsys);
671                 me.WarpZone_refsys = world;
672         }
673 }
674 void WarpZone_RefSys_AddTransform(entity me, vector t, vector s)
675 {
676         if(t != '0 0 0' || s != '0 0 0')
677         {
678                 WarpZone_RefSys_CheckCreate(me);
679                 WarpZone_Accumulator_AddTransform(me.WarpZone_refsys, t, s);
680         }
681 }
682 void WarpZone_RefSys_Add(entity me, entity wz)
683 {
684         WarpZone_RefSys_AddTransform(me, wz.warpzone_transform, wz.warpzone_shift);
685 }
686 void WarpZone_RefSys_AddInverseTransform(entity me, vector t, vector s)
687 {
688         if(t != '0 0 0' || s != '0 0 0')
689         {
690                 WarpZone_RefSys_CheckCreate(me);
691                 WarpZone_Accumulator_AddInverseTransform(me.WarpZone_refsys, t, s);
692         }
693 }
694 void WarpZone_RefSys_AddInverse(entity me, entity wz)
695 {
696         WarpZone_RefSys_AddInverseTransform(me, wz.warpzone_transform, wz.warpzone_shift);
697 }
698 .vector WarpZone_refsys_incremental_shift;
699 .vector WarpZone_refsys_incremental_transform;
700 void WarpZone_RefSys_AddIncrementally(entity me, entity ref)
701 {
702         //vector t, s;
703         if(me.WarpZone_refsys_incremental_transform == ref.WarpZone_refsys.warpzone_transform)
704         if(me.WarpZone_refsys_incremental_shift == ref.WarpZone_refsys.warpzone_shift)
705                 return;
706         WarpZone_Accumulator_AddInverseTransform(me.WarpZone_refsys, me.WarpZone_refsys_incremental_transform, me.WarpZone_refsys_incremental_shift);
707         WarpZone_Accumulator_Add(me.WarpZone_refsys, ref.WarpZone_refsys);
708         me.WarpZone_refsys_incremental_shift = ref.WarpZone_refsys.warpzone_shift;
709         me.WarpZone_refsys_incremental_transform = ref.WarpZone_refsys.warpzone_transform;
710 }
711 void WarpZone_RefSys_BeginAddingIncrementally(entity me, entity ref)
712 {
713         me.WarpZone_refsys_incremental_shift = ref.WarpZone_refsys.warpzone_shift;
714         me.WarpZone_refsys_incremental_transform = ref.WarpZone_refsys.warpzone_transform;
715 }
716 vector WarpZone_RefSys_TransformOrigin(entity from, entity to, vector org)
717 {
718         if(from.WarpZone_refsys)
719                 org = WarpZone_UnTransformOrigin(from.WarpZone_refsys, org);
720         if(to.WarpZone_refsys)
721                 org = WarpZone_TransformOrigin(to.WarpZone_refsys, org);
722         return org;
723 }
724 vector WarpZone_RefSys_TransformVelocity(entity from, entity to, vector vel)
725 {
726         if(from.WarpZone_refsys)
727                 vel = WarpZone_UnTransformVelocity(from.WarpZone_refsys, vel);
728         if(to.WarpZone_refsys)
729                 vel = WarpZone_TransformVelocity(to.WarpZone_refsys, vel);
730         return vel;
731 }
732 vector WarpZone_RefSys_TransformAngles(entity from, entity to, vector ang)
733 {
734         if(from.WarpZone_refsys)
735                 ang = WarpZone_UnTransformAngles(from.WarpZone_refsys, ang);
736         if(to.WarpZone_refsys)
737                 ang = WarpZone_TransformAngles(to.WarpZone_refsys, ang);
738         return ang;
739 }
740 vector WarpZone_RefSys_TransformVAngles(entity from, entity to, vector ang)
741 {
742         if(from.WarpZone_refsys)
743                 ang = WarpZone_UnTransformVAngles(from.WarpZone_refsys, ang);
744         if(to.WarpZone_refsys)
745                 ang = WarpZone_TransformVAngles(to.WarpZone_refsys, ang);
746         return ang;
747 }
748 void WarpZone_RefSys_Copy(entity me, entity from)
749 {
750         if(from.WarpZone_refsys)
751         {
752                 WarpZone_RefSys_CheckCreate(me);
753                 me.WarpZone_refsys.warpzone_shift = from.WarpZone_refsys.warpzone_shift;
754                 me.WarpZone_refsys.warpzone_transform = from.WarpZone_refsys.warpzone_transform;
755         }
756         else
757                 WarpZone_RefSys_Clear(me);
758 }
759 entity WarpZone_RefSys_SpawnSameRefSys(entity me)
760 {
761         entity e;
762         e = spawn();
763         WarpZone_RefSys_Copy(e, me);
764         return e;
765 }