]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/warpzonelib/common.qc
More fun stuff
[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 (!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 | PARTICLES_DRAWASTRAIL;
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 WarpZoneLib_BadClassname(string myclassname)
568 {
569         switch(myclassname)
570         {
571                 case "weapon_info":
572                 case "monster_info":
573                 case "deathtype":
574                 case "callback":
575                 case "callbackchain":
576                 case "weaponentity":
577                 case "exteriorweaponentity":
578                 case "csqc_score_team":
579                 case "pingplreport":
580                 case "ent_client_scoreinfo":
581                 case "saved_cvar_value":
582                 case "accuracy":
583                 case "entcs_sender_v2":
584                 case "entcs_receiver_v2":
585                 case "clientinit":
586                 case "sprite_waypoint":
587                 case "waypoint":
588                 case "gibsplash":
589                 //case "net_linked": // actually some real entities are linked without classname, fail
590                 case "":
591                         return TRUE;
592         }
593
594         if(startsWith(myclassname, "msg_"))
595                 return TRUE;
596
597         if(startsWith(myclassname, "target_"))
598                 return TRUE;
599
600         if(startsWith(myclassname, "info_"))
601                 return TRUE;
602
603         return FALSE;
604 }
605
606 .float WarpZone_findradius_hit;
607 .entity WarpZone_findradius_next;
608 void WarpZone_FindRadius_Recurse(vector org, float rad,        vector org0,               vector transform, vector shift, float needlineofsight)
609 //                               blast origin of current search   original blast origin   how to untransform (victim to blast system)
610 {
611         vector org_new;
612         vector org0_new;
613         vector shift_new, transform_new;
614         vector p;
615         entity e, e0;
616         entity wz;
617         if(rad <= 0)
618                 return;
619         e0 = findradius(org, rad);
620         wz = world;
621
622         for(e = e0; e; e = e.chain)
623         {
624                 if(WarpZoneLib_BadClassname(e.classname))
625                         continue;
626                 print(e.classname, ", first check\n");
627                 p = WarpZoneLib_NearestPointOnBox(e.origin + e.mins, e.origin + e.maxs, org0);
628                 if(needlineofsight)
629                 {
630                         traceline(org, p, MOVE_NOMONSTERS, e);
631                         if(trace_fraction < 1)
632                                 continue;
633                 }
634                 if(!e.WarpZone_findradius_hit || vlen(e.WarpZone_findradius_dist) > vlen(org0 - p))
635                 {
636                         e.WarpZone_findradius_nearest = p;
637                         e.WarpZone_findradius_dist = org0 - p;
638                         e.WarpZone_findradius_findorigin = org;
639                         e.WarpZone_findradius_findradius = rad;
640                         if(e.classname == "warpzone_refsys")
641                         {
642                                 // ignore, especially: do not overwrite the refsys parameters
643                         }
644                         else if(e.classname == "trigger_warpzone")
645                         {
646                                 e.WarpZone_findradius_next = wz;
647                                 wz = e;
648                                 e.WarpZone_findradius_hit = 1;
649                                 e.enemy.WarpZone_findradius_dist = '0 0 0'; // we don't want to go through this zone ever again
650                                 e.enemy.WarpZone_findradius_hit = 1;
651                         }
652                         else
653                         {
654                                 e.warpzone_transform = transform;
655                                 e.warpzone_shift = shift;
656                                 e.WarpZone_findradius_hit = 1;
657                         }
658                 }
659         }
660         for(e = wz; e; e = e.WarpZone_findradius_next)
661         {
662                 if(WarpZoneLib_BadClassname(e.classname))
663                         continue;
664
665                 print(e.classname, ", second check\n");
666
667                 org0_new = WarpZone_TransformOrigin(e, org);
668                 traceline(e.warpzone_targetorigin, org0_new, MOVE_NOMONSTERS, e);
669                 org_new = trace_endpos;
670
671                 transform_new = AnglesTransform_Multiply(e.warpzone_transform, transform);
672                 shift_new = AnglesTransform_Multiply_GetPostShift(e.warpzone_transform, e.warpzone_shift, transform, shift);
673                 WarpZone_FindRadius_Recurse(
674                         org_new,
675                         bound(0, rad - vlen(org_new - org0_new), rad - 8),
676                         org0_new,
677                         transform_new, shift_new,
678                         needlineofsight);
679                 e.WarpZone_findradius_hit = 0;
680                 e.enemy.WarpZone_findradius_hit = 0;
681         }
682 }
683 entity WarpZone_FindRadius(vector org, float rad, float needlineofsight)
684 {
685         entity e0, e;
686         WarpZone_FindRadius_Recurse(org, rad, org, '0 0 0', '0 0 0', needlineofsight);
687         e0 = findchainfloat(WarpZone_findradius_hit, 1);
688         for(e = e0; e; e = e.chain)
689                 e.WarpZone_findradius_hit = 0;
690         return e0;
691 }
692
693 .entity WarpZone_refsys;
694 void WarpZone_RefSys_GC()
695 {
696         // garbage collect unused reference systems
697         self.nextthink = time + 1;
698         if(self.owner.WarpZone_refsys != self)
699                 remove(self);
700 }
701 void WarpZone_RefSys_CheckCreate(entity me)
702 {
703         if(me.WarpZone_refsys.owner != me)
704         {
705                 me.WarpZone_refsys = spawn();
706                 me.WarpZone_refsys.classname = "warpzone_refsys";
707                 me.WarpZone_refsys.owner = me;
708                 me.WarpZone_refsys.think = WarpZone_RefSys_GC;
709                 me.WarpZone_refsys.nextthink = time + 1;
710                 WarpZone_Accumulator_Clear(me.WarpZone_refsys);
711         }
712 }
713 void WarpZone_RefSys_Clear(entity me)
714 {
715         if(me.WarpZone_refsys)
716         {
717                 remove(me.WarpZone_refsys);
718                 me.WarpZone_refsys = world;
719         }
720 }
721 void WarpZone_RefSys_AddTransform(entity me, vector t, vector s)
722 {
723         if(t != '0 0 0' || s != '0 0 0')
724         {
725                 WarpZone_RefSys_CheckCreate(me);
726                 WarpZone_Accumulator_AddTransform(me.WarpZone_refsys, t, s);
727         }
728 }
729 void WarpZone_RefSys_Add(entity me, entity wz)
730 {
731         WarpZone_RefSys_AddTransform(me, wz.warpzone_transform, wz.warpzone_shift);
732 }
733 void WarpZone_RefSys_AddInverseTransform(entity me, vector t, vector s)
734 {
735         if(t != '0 0 0' || s != '0 0 0')
736         {
737                 WarpZone_RefSys_CheckCreate(me);
738                 WarpZone_Accumulator_AddInverseTransform(me.WarpZone_refsys, t, s);
739         }
740 }
741 void WarpZone_RefSys_AddInverse(entity me, entity wz)
742 {
743         WarpZone_RefSys_AddInverseTransform(me, wz.warpzone_transform, wz.warpzone_shift);
744 }
745 .vector WarpZone_refsys_incremental_shift;
746 .vector WarpZone_refsys_incremental_transform;
747 void WarpZone_RefSys_AddIncrementally(entity me, entity ref)
748 {
749         //vector t, s;
750         if(me.WarpZone_refsys_incremental_transform == ref.WarpZone_refsys.warpzone_transform)
751         if(me.WarpZone_refsys_incremental_shift == ref.WarpZone_refsys.warpzone_shift)
752                 return;
753         WarpZone_Accumulator_AddInverseTransform(me.WarpZone_refsys, me.WarpZone_refsys_incremental_transform, me.WarpZone_refsys_incremental_shift);
754         WarpZone_Accumulator_Add(me.WarpZone_refsys, ref.WarpZone_refsys);
755         me.WarpZone_refsys_incremental_shift = ref.WarpZone_refsys.warpzone_shift;
756         me.WarpZone_refsys_incremental_transform = ref.WarpZone_refsys.warpzone_transform;
757 }
758 void WarpZone_RefSys_BeginAddingIncrementally(entity me, entity ref)
759 {
760         me.WarpZone_refsys_incremental_shift = ref.WarpZone_refsys.warpzone_shift;
761         me.WarpZone_refsys_incremental_transform = ref.WarpZone_refsys.warpzone_transform;
762 }
763 vector WarpZone_RefSys_TransformOrigin(entity from, entity to, vector org)
764 {
765         if(from.WarpZone_refsys)
766                 org = WarpZone_UnTransformOrigin(from.WarpZone_refsys, org);
767         if(to.WarpZone_refsys)
768                 org = WarpZone_TransformOrigin(to.WarpZone_refsys, org);
769         return org;
770 }
771 vector WarpZone_RefSys_TransformVelocity(entity from, entity to, vector vel)
772 {
773         if(from.WarpZone_refsys)
774                 vel = WarpZone_UnTransformVelocity(from.WarpZone_refsys, vel);
775         if(to.WarpZone_refsys)
776                 vel = WarpZone_TransformVelocity(to.WarpZone_refsys, vel);
777         return vel;
778 }
779 vector WarpZone_RefSys_TransformAngles(entity from, entity to, vector ang)
780 {
781         if(from.WarpZone_refsys)
782                 ang = WarpZone_UnTransformAngles(from.WarpZone_refsys, ang);
783         if(to.WarpZone_refsys)
784                 ang = WarpZone_TransformAngles(to.WarpZone_refsys, ang);
785         return ang;
786 }
787 vector WarpZone_RefSys_TransformVAngles(entity from, entity to, vector ang)
788 {
789         if(from.WarpZone_refsys)
790                 ang = WarpZone_UnTransformVAngles(from.WarpZone_refsys, ang);
791         if(to.WarpZone_refsys)
792                 ang = WarpZone_TransformVAngles(to.WarpZone_refsys, ang);
793         return ang;
794 }
795 void WarpZone_RefSys_Copy(entity me, entity from)
796 {
797         if(from.WarpZone_refsys)
798         {
799                 WarpZone_RefSys_CheckCreate(me);
800                 me.WarpZone_refsys.warpzone_shift = from.WarpZone_refsys.warpzone_shift;
801                 me.WarpZone_refsys.warpzone_transform = from.WarpZone_refsys.warpzone_transform;
802         }
803         else
804                 WarpZone_RefSys_Clear(me);
805 }
806 entity WarpZone_RefSys_SpawnSameRefSys(entity me)
807 {
808         entity e;
809         e = spawn();
810         WarpZone_RefSys_Copy(e, me);
811         return e;
812 }
813
814 float WarpZoneLib_ExactTrigger_Touch()
815 {
816         return !WarpZoneLib_BoxTouchesBrush(other.absmin, other.absmax, self, other);
817 }
818
819
820 void WarpZoneLib_MoveOutOfSolid_Expand(entity e, vector by)
821 {
822         float eps = 0.0625;
823         tracebox(e.origin, e.mins - '1 1 1' * eps, e.maxs + '1 1 1' * eps, e.origin + by, MOVE_WORLDONLY, e);
824         if (trace_startsolid)
825                 return;
826         if (trace_fraction < 1)
827         {
828                 // hit something
829                 // adjust origin in the other direction...
830                 setorigin(e,e.origin - by * (1 - trace_fraction));
831         }
832 }
833
834 float WarpZoneLib_MoveOutOfSolid(entity e)
835 {
836         vector o, m0, m1;
837
838         o = e.origin;
839         traceline(o, o, MOVE_WORLDONLY, e);
840         if (trace_startsolid)
841                 return FALSE;
842
843         tracebox(o, e.mins, e.maxs, o, MOVE_WORLDONLY, e);
844         if (!trace_startsolid)
845                 return TRUE;
846
847         m0 = e.mins;
848         m1 = e.maxs;
849         e.mins = '0 0 0';
850         e.maxs = '0 0 0';
851         WarpZoneLib_MoveOutOfSolid_Expand(e, '1 0 0' * m0_x);
852         e.mins_x = m0_x;
853         WarpZoneLib_MoveOutOfSolid_Expand(e, '1 0 0' * m1_x);
854         e.maxs_x = m1_x;
855         WarpZoneLib_MoveOutOfSolid_Expand(e, '0 1 0' * m0_y);
856         e.mins_y = m0_y;
857         WarpZoneLib_MoveOutOfSolid_Expand(e, '0 1 0' * m1_y);
858         e.maxs_y = m1_y;
859         WarpZoneLib_MoveOutOfSolid_Expand(e, '0 0 1' * m0_z);
860         e.mins_z = m0_z;
861         WarpZoneLib_MoveOutOfSolid_Expand(e, '0 0 1' * m1_z);
862         e.maxs_z = m1_z;
863         setorigin(e, e.origin);
864
865         tracebox(e.origin, e.mins, e.maxs, e.origin, MOVE_WORLDONLY, e);
866         if (trace_startsolid)
867         {
868                 setorigin(e, o);
869                 return FALSE;
870         }
871
872         return TRUE;
873 }