]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/warpzonelib/common.qc
warpzones: fix an endless loop
[xonotic/xonotic-data.pk3dir.git] / qcsrc / warpzonelib / common.qc
1 void WarpZone_Accumulator_Clear(entity acc)
2 {
3         acc.warpzone_transform = '0 0 0';
4         acc.warpzone_shift = '0 0 0';
5 }
6 void WarpZone_Accumulator_AddTransform(entity acc, vector t, vector s)
7 {
8         vector tr, st;
9         tr = AnglesTransform_Multiply(t, acc.warpzone_transform);
10         st = AnglesTransform_Multiply_GetPostShift(t, s, acc.warpzone_transform, acc.warpzone_shift);
11         acc.warpzone_transform = tr;
12         acc.warpzone_shift = st;
13 }
14 void WarpZone_Accumulator_Add(entity acc, entity wz)
15 {
16         vector t, st;
17         t = AnglesTransform_Multiply(wz.warpzone_transform, acc.warpzone_transform);
18         st = AnglesTransform_Multiply_GetPostShift(wz.warpzone_transform, wz.warpzone_shift, acc.warpzone_transform, acc.warpzone_shift);
19         acc.warpzone_transform = t;
20         acc.warpzone_shift = st;
21 }
22
23 .vector(vector, vector) camera_transform;
24 var float autocvar_cl_warpzone_usetrace = 1;
25 vector WarpZone_camera_transform(vector org, vector ang)
26 {
27         vector vf, vr, vu;
28         vf = v_forward;
29         vr = v_right;
30         vu = v_up;
31         org = WarpZone_TransformOrigin(self, org);
32         vf = WarpZone_TransformVelocity(self, vf);
33         vr = WarpZone_TransformVelocity(self, vr);
34         vu = WarpZone_TransformVelocity(self, vu);
35         if(autocvar_cl_warpzone_usetrace)
36                 traceline(self.warpzone_targetorigin, org, MOVE_NOMONSTERS, world);
37         else
38                 trace_endpos = self.warpzone_targetorigin;
39         v_forward = vf;
40         v_right = vr;
41         v_up = vu;
42         return org;
43 }
44
45 void WarpZone_SetUp(entity e, vector my_org, vector my_ang, vector other_org, vector other_ang)
46 {
47         e.warpzone_transform = AnglesTransform_Divide(other_ang, AnglesTransform_TurnDirectionFR(my_ang));
48         e.warpzone_shift = AnglesTransform_PrePostShift_GetPostShift(my_org, e.warpzone_transform, other_org);
49         e.warpzone_origin = my_org;
50         e.warpzone_targetorigin = other_org;
51         e.warpzone_angles = my_ang;
52         e.warpzone_targetangles = other_ang;
53         fixedmakevectors(my_ang); e.warpzone_forward = v_forward;
54         fixedmakevectors(other_ang); e.warpzone_targetforward = v_forward;
55         e.camera_transform = WarpZone_camera_transform;
56 }
57
58 .entity enemy;
59
60 vector WarpZoneLib_BoxTouchesBrush_mins;
61 vector WarpZoneLib_BoxTouchesBrush_maxs;
62 entity WarpZoneLib_BoxTouchesBrush_ent;
63 entity WarpZoneLib_BoxTouchesBrush_ignore;
64 float WarpZoneLib_BoxTouchesBrush_Recurse()
65 {
66         float s;
67         entity se;
68         float f;
69
70         tracebox('0 0 0', WarpZoneLib_BoxTouchesBrush_mins, WarpZoneLib_BoxTouchesBrush_maxs, '0 0 0', MOVE_NOMONSTERS, WarpZoneLib_BoxTouchesBrush_ignore);
71 #ifdef CSQC
72         if (trace_networkentity)
73         {
74                 dprint("hit a network ent, cannot continue WarpZoneLib_BoxTouchesBrush\n");
75                 // we cannot continue, as a player blocks us...
76                 // so, abort
77                 return 0;
78         }
79 #endif
80         if not(trace_ent)
81                 return 0;
82         if (trace_ent == WarpZoneLib_BoxTouchesBrush_ent)
83                 return 1;
84
85         se = trace_ent;
86         s = se.solid;
87         se.solid = SOLID_NOT;
88         f = WarpZoneLib_BoxTouchesBrush_Recurse();
89         se.solid = s;
90
91         return f;
92 }
93
94 float WarpZoneLib_BoxTouchesBrush(vector mi, vector ma, entity e, entity ig)
95 {
96     float f, s;
97
98     if not(e.modelindex)
99         return 1;
100
101     s = e.solid;
102     e.solid = SOLID_BSP;
103     WarpZoneLib_BoxTouchesBrush_mins = mi;
104     WarpZoneLib_BoxTouchesBrush_maxs = ma;
105     WarpZoneLib_BoxTouchesBrush_ent = e;
106     WarpZoneLib_BoxTouchesBrush_ignore = ig;
107     f = WarpZoneLib_BoxTouchesBrush_Recurse();
108     e.solid = s;
109
110     return f;
111 }
112
113 entity WarpZone_Find(vector mi, vector ma)
114 {
115         // if we are near any warpzone planes - MOVE AWAY (work around nearclip)
116         entity e;
117         for(e = world; (e = find(e, classname, "trigger_warpzone")); )
118                 if(WarpZoneLib_BoxTouchesBrush(mi, ma, e, world))
119                         return e;
120         return world;
121 }
122
123 void WarpZone_MakeAllSolid()
124 {
125         entity e;
126         for(e = world; (e = find(e, classname, "trigger_warpzone")); )
127                 e.solid = SOLID_BSP;
128 }
129
130 void WarpZone_MakeAllOther()
131 {
132         entity e;
133         for(e = world; (e = find(e, classname, "trigger_warpzone")); )
134                 e.solid = SOLID_TRIGGER;
135 }
136
137 void WarpZone_Trace_InitTransform()
138 {
139         if(!WarpZone_trace_transform)
140         {
141                 WarpZone_trace_transform = spawn();
142                 WarpZone_trace_transform.classname = "warpzone_trace_transform";
143         }
144         WarpZone_Accumulator_Clear(WarpZone_trace_transform);
145 }
146 void WarpZone_Trace_AddTransform(entity wz)
147 {
148         WarpZone_Accumulator_Add(WarpZone_trace_transform, wz);
149 }
150
151 void WarpZone_TraceBox_ThroughZone(vector org, vector mi, vector ma, vector end, float nomonsters, entity forent, entity zone, WarpZone_trace_callback_t cb)
152 {
153         float frac, sol, i;
154         vector o0, e0;
155         entity wz;
156         vector vf, vr, vu;
157         vf = v_forward;
158         vr = v_right;
159         vu = v_up;
160         o0 = org;
161         e0 = end;
162         WarpZone_Trace_InitTransform();
163         // if starting in warpzone, first transform
164         wz = WarpZone_Find(org + mi, org + ma);
165         if(wz)
166         {
167                 if(zone && wz != zone)
168                 {
169                         // we are in ANOTHER warpzone. This is bad. Make a zero length trace and return.
170                         sol = 1;
171                         trace_fraction = 0;
172                         trace_endpos = org;
173                         goto fail;
174                 }
175                 WarpZone_Trace_AddTransform(wz);
176                 org = WarpZone_TransformOrigin(wz, trace_endpos);
177                 end = WarpZone_TransformOrigin(wz, end);
178         }
179         WarpZone_MakeAllSolid();
180         sol = -1;
181         frac = 0;
182         i = 16;
183         for(;;)
184         {
185                 if(--i < 1)
186                 {
187                         dprint("Too many warpzones in sequence, aborting trace.\n");
188                         break;
189                 }
190                 tracebox(org, mi, ma, end, nomonsters, forent);
191                 if(cb)
192                         cb(org, trace_endpos, end);
193                 if(sol < 0)
194                         sol = trace_startsolid;
195
196                 frac = trace_fraction = frac + (1 - frac) * trace_fraction;
197                 if(trace_fraction >= 1)
198                         break;
199                 if(trace_ent.classname != "trigger_warpzone")
200                         break;
201                 if(trace_ent == wz)
202                 {
203                         dprint("I transformed into the same zone again, wtf, aborting the trace\n");
204                         break;
205                 }
206                 wz = trace_ent;
207                 if(zone && wz != zone)
208                         break;
209                 WarpZone_Trace_AddTransform(wz);
210                 // we hit a warpzone... so, let's perform the trace after the warp again
211                 org = WarpZone_TransformOrigin(wz, trace_endpos);
212                 end = WarpZone_TransformOrigin(wz, end);
213         }
214         WarpZone_MakeAllOther();
215 :fail
216         trace_startsolid = sol;
217         v_forward = vf;
218         v_right = vr;
219         v_up = vu;
220 }
221
222 void WarpZone_TraceBox(vector org, vector mi, vector ma, vector end, float nomonsters, entity forent)
223 {
224         WarpZone_TraceBox_ThroughZone(org, mi, ma, end, nomonsters, forent, world, WarpZone_trace_callback_t_null);
225 }
226
227 void WarpZone_TraceLine(vector org, vector end, float nomonsters, entity forent)
228 {
229         WarpZone_TraceBox(org, '0 0 0', '0 0 0', end, nomonsters, forent);
230 }
231
232 void WarpZone_TraceToss_ThroughZone(entity e, entity forent, entity zone, WarpZone_trace_callback_t cb)
233 {
234         float g, dt, i;
235         vector vf, vr, vu, v0, o0;
236         entity wz;
237
238         vf = v_forward;
239         vr = v_right;
240         vu = v_up;
241         o0 = e.origin;
242         v0 = e.velocity;
243         WarpZone_Trace_InitTransform();
244         // if starting in warpzone, first transform
245         wz = WarpZone_Find(e.origin + e.mins, e.origin + e.maxs);
246         if(wz)
247         {
248                 if(zone && wz != zone)
249                 {
250                         // we are in ANOTHER warpzone. This is bad. Make a zero length trace and return.
251
252                         WarpZone_tracetoss_time = 0;
253                         trace_endpos = o0;
254                         goto fail;
255                 }
256                 WarpZone_Trace_AddTransform(wz);
257                 setorigin(e, WarpZone_TransformOrigin(wz, e.origin));
258                 e.velocity = WarpZone_TransformVelocity(wz, e.velocity);
259         }
260         WarpZone_MakeAllSolid();
261         g = cvar("sv_gravity") * e.gravity;
262         WarpZone_tracetoss_time = 0;
263         i = 16;
264         for(;;)
265         {
266                 if(--i < 1)
267                 {
268                         dprint("Too many warpzones in sequence, aborting trace.\n");
269                         break;
270                 }
271                 tracetoss(e, forent);
272                 if(cb)
273                         cb(e.origin, trace_endpos, trace_endpos);
274                 e.origin = trace_endpos;
275                 e.velocity_z -= WarpZone_tracetoss_time * g;
276                 dt = vlen(e.origin - o0) / vlen(e.velocity);
277                 WarpZone_tracetoss_time += dt;
278                 if(trace_fraction >= 1)
279                         break;
280                 if(trace_ent.classname != "trigger_warpzone")
281                         break;
282                 if(trace_ent == wz)
283                 {
284                         dprint("I transformed into the same zone again, wtf, aborting the trace\n");
285                         break;
286                 }
287                 wz = trace_ent;
288                 if(zone && wz != zone)
289                         break;
290                 WarpZone_Trace_AddTransform(wz);
291                 // we hit a warpzone... so, let's perform the trace after the warp again
292                 e.origin = WarpZone_TransformOrigin(wz, e.origin);
293                 e.velocity = WarpZone_TransformVelocity(wz, e.velocity);
294         }
295         WarpZone_MakeAllOther();
296 :fail
297         WarpZone_tracetoss_velocity = e.velocity;
298         v_forward = vf;
299         v_right = vr;
300         v_up = vu;
301         // restore old entity data (caller just uses trace_endpos, WarpZone_tracetoss_velocity and the transform)
302         e.velocity = v0;
303         e.origin = o0;
304 }
305
306 void WarpZone_TraceToss(entity e, entity forent)
307 {
308         WarpZone_TraceToss_ThroughZone(e, forent, world, WarpZone_trace_callback_t_null);
309 }
310
311 entity WarpZone_TrailParticles_trace_callback_own;
312 float WarpZone_TrailParticles_trace_callback_eff;
313 void WarpZone_TrailParticles_trace_callback(vector from, vector endpos, vector to)
314 {
315         trailparticles(WarpZone_TrailParticles_trace_callback_own, WarpZone_TrailParticles_trace_callback_eff, from, endpos);
316 }
317
318 void WarpZone_TrailParticles(entity own, float eff, vector org, vector end)
319 {
320         WarpZone_TrailParticles_trace_callback_own = own;
321         WarpZone_TrailParticles_trace_callback_eff = eff;
322         WarpZone_TraceBox_ThroughZone(org, '0 0 0', '0 0 0', end, MOVE_NOMONSTERS, world, world, WarpZone_TrailParticles_trace_callback);
323 }
324
325 float WarpZone_PlaneDist(entity wz, vector v)
326 {
327         return (v - wz.warpzone_origin) * wz.warpzone_forward;
328 }
329
330 float WarpZone_TargetPlaneDist(entity wz, vector v)
331 {
332         return (v - wz.warpzone_targetorigin) * wz.warpzone_targetforward;
333 }
334
335 vector WarpZone_TransformOrigin(entity wz, vector v)
336 {
337         return wz.warpzone_shift + AnglesTransform_Apply(wz.warpzone_transform, v);
338 }
339
340 vector WarpZone_TransformVelocity(entity wz, vector v)
341 {
342         return AnglesTransform_Apply(wz.warpzone_transform, v);
343 }
344
345 vector WarpZone_TransformAngles(entity wz, vector v)
346 {
347         return AnglesTransform_ApplyToAngles(wz.warpzone_transform, v);
348 }
349
350 vector WarpZone_TransformVAngles(entity wz, vector ang)
351 {
352 #ifdef KEEP_ROLL
353         float roll;
354         roll = ang_z;
355         ang_z = 0;
356 #endif
357
358         ang = AnglesTransform_ApplyToVAngles(wz.warpzone_transform, ang);
359
360 #ifdef KEEP_ROLL
361         ang = AnglesTransform_Normalize(ang, TRUE);
362         ang = AnglesTransform_CancelRoll(ang);
363         ang_z = roll;
364 #else
365         ang = AnglesTransform_Normalize(ang, FALSE);
366 #endif
367
368         return ang;
369 }
370
371 vector WarpZone_UnTransformOrigin(entity wz, vector v)
372 {
373         return AnglesTransform_Apply(AnglesTransform_Invert(wz.warpzone_transform), v - wz.warpzone_shift);
374 }
375
376 vector WarpZone_UnTransformVelocity(entity wz, vector v)
377 {
378         return AnglesTransform_Apply(AnglesTransform_Invert(wz.warpzone_transform), v);
379 }
380
381 vector WarpZone_UnTransformAngles(entity wz, vector v)
382 {
383         return AnglesTransform_ApplyToAngles(AnglesTransform_Invert(wz.warpzone_transform), v);
384 }
385
386 vector WarpZone_UnTransformVAngles(entity wz, vector ang)
387 {
388         float roll;
389
390         roll = ang_z;
391         ang_z = 0;
392
393         ang = AnglesTransform_ApplyToVAngles(AnglesTransform_Invert(wz.warpzone_transform), ang);
394         ang = AnglesTransform_Normalize(ang, TRUE);
395         ang = AnglesTransform_CancelRoll(ang);
396
397         ang_z = roll;
398         return ang;
399 }
400
401 vector WarpZoneLib_NearestPointOnBox(vector mi, vector ma, vector org)
402 {
403         vector nearest;
404         nearest_x = bound(mi_x, org_x, ma_x);
405         nearest_y = bound(mi_y, org_y, ma_y);
406         nearest_z = bound(mi_z, org_z, ma_z);
407         return nearest;
408 }
409
410 .float WarpZone_findradius_hit;
411 .entity WarpZone_findradius_next;
412 void WarpZone_FindRadius_Recurse(vector org, float rad,        vector org0,               vector transform, vector shift, float needlineofsight)
413 //                               blast origin of current search   original blast origin   how to untransform (victim to blast system)
414 {
415         vector org_new;
416         vector org0_new;
417         vector shift_new, transform_new;
418         vector p;
419         entity e, e0;
420         entity wz;
421         if(rad <= 0)
422                 return;
423         e0 = findradius(org, rad);
424         wz = world;
425
426         for(e = e0; e; e = e.chain)
427         {
428                 p = WarpZoneLib_NearestPointOnBox(e.origin + e.mins, e.origin + e.maxs, org0);
429                 if(needlineofsight)
430                 {
431                         traceline(org, p, MOVE_NOMONSTERS, e);
432                         if(trace_fraction < 1)
433                                 continue;
434                 }
435                 if(!e.WarpZone_findradius_hit || vlen(e.WarpZone_findradius_dist) > vlen(org0 - p))
436                 {
437                         e.WarpZone_findradius_nearest = p;
438                         e.WarpZone_findradius_dist = org0 - p;
439                         e.WarpZone_findradius_findorigin = org;
440                         e.WarpZone_findradius_findradius = rad;
441                         if(e.classname == "warpzone_refsys")
442                         {
443                                 // ignore, especially: do not overwrite the refsys parameters
444                         }
445                         else if(e.classname == "trigger_warpzone")
446                         {
447                                 e.WarpZone_findradius_next = wz;
448                                 wz = e;
449                                 e.WarpZone_findradius_hit = 1;
450                                 e.enemy.WarpZone_findradius_dist = '0 0 0'; // we don't want to go through this zone ever again
451                                 e.enemy.WarpZone_findradius_hit = 1;
452                         }
453                         else
454                         {
455                                 e.warpzone_transform = transform;
456                                 e.warpzone_shift = shift;
457                                 e.WarpZone_findradius_hit = 1;
458                         }
459                 }
460         }
461         for(e = wz; e; e = e.WarpZone_findradius_next)
462         {
463                 org0_new = WarpZone_TransformOrigin(e, org);
464                 traceline(e.warpzone_targetorigin, org0_new, MOVE_NOMONSTERS, e);
465                 org_new = trace_endpos;
466
467                 transform_new = AnglesTransform_Multiply(e.warpzone_transform, transform);
468                 shift_new = AnglesTransform_Multiply_GetPostShift(e.warpzone_transform, e.warpzone_shift, transform, shift);
469                 WarpZone_FindRadius_Recurse(
470                         org_new,
471                         bound(0, rad - vlen(org_new - org0_new), rad - 8),
472                         org0_new,
473                         transform_new, shift_new,
474                         needlineofsight);
475                 e.WarpZone_findradius_hit = 0;
476                 e.enemy.WarpZone_findradius_hit = 0;
477         }
478 }
479 entity WarpZone_FindRadius(vector org, float rad, float needlineofsight)
480 {
481         entity e0, e;
482         WarpZone_FindRadius_Recurse(org, rad, org, '0 0 0', '0 0 0', needlineofsight);
483         e0 = findchainfloat(WarpZone_findradius_hit, 1);
484         for(e = e0; e; e = e.chain)
485                 e.WarpZone_findradius_hit = 0;
486         return e0;
487 }
488
489 .entity WarpZone_refsys;
490 void WarpZone_RefSys_GC()
491 {
492         // garbage collect unused reference systems
493         self.nextthink = time + 1;
494         if(self.owner.WarpZone_refsys != self)
495                 remove(self);
496 }
497 void WarpZone_RefSys_Add(entity me, entity wz)
498 {
499         if(me.WarpZone_refsys.owner != me)
500         {
501                 me.WarpZone_refsys = spawn();
502                 me.WarpZone_refsys.classname = "warpzone_refsys";
503                 me.WarpZone_refsys.owner = me;
504                 me.WarpZone_refsys.think = WarpZone_RefSys_GC;
505                 me.WarpZone_refsys.nextthink = time + 1;
506                 WarpZone_Accumulator_Clear(me.WarpZone_refsys);
507         }
508         if(wz)
509                 WarpZone_Accumulator_Add(me.WarpZone_refsys, wz);
510 }
511 .vector WarpZone_refsys_incremental_shift;
512 .vector WarpZone_refsys_incremental_transform;
513 void WarpZone_RefSys_AddIncrementally(entity me, entity ref)
514 {
515         vector t, s;
516         if(me.WarpZone_refsys_incremental_transform == ref.WarpZone_refsys.warpzone_transform)
517         if(me.WarpZone_refsys_incremental_shift == ref.WarpZone_refsys.warpzone_shift)
518                 return;
519         if(me.WarpZone_refsys.owner != me)
520         {
521                 me.WarpZone_refsys = spawn();
522                 me.WarpZone_refsys.classname = "warpzone_refsys";
523                 me.WarpZone_refsys.owner = me;
524                 me.WarpZone_refsys.think = WarpZone_RefSys_GC;
525                 me.WarpZone_refsys.nextthink = time + 1;
526                 WarpZone_Accumulator_Clear(me.WarpZone_refsys);
527         }
528         t = AnglesTransform_Invert(me.WarpZone_refsys_incremental_transform);
529         s = AnglesTransform_PrePostShift_GetPostShift(me.WarpZone_refsys_incremental_shift, t, '0 0 0');
530         WarpZone_Accumulator_AddTransform(me.WarpZone_refsys, t, s);
531         WarpZone_Accumulator_Add(me.WarpZone_refsys, ref);
532         me.WarpZone_refsys_incremental_shift = ref.WarpZone_refsys.warpzone_shift;
533         me.WarpZone_refsys_incremental_transform = ref.WarpZone_refsys.warpzone_transform;
534 }
535 void WarpZone_RefSys_BeginAddingIncrementally(entity me, entity ref)
536 {
537         me.WarpZone_refsys_incremental_shift = ref.WarpZone_refsys.warpzone_shift;
538         me.WarpZone_refsys_incremental_transform = ref.WarpZone_refsys.warpzone_transform;
539 }
540 vector WarpZone_RefSys_TransformOrigin(entity from, entity to, vector org)
541 {
542         if(from.WarpZone_refsys)
543                 org = WarpZone_UnTransformOrigin(from.WarpZone_refsys, org);
544         if(to.WarpZone_refsys)
545                 org = WarpZone_TransformOrigin(to.WarpZone_refsys, org);
546         return org;
547 }
548 vector WarpZone_RefSys_TransformVelocity(entity from, entity to, vector vel)
549 {
550         if(from.WarpZone_refsys)
551                 vel = WarpZone_UnTransformVelocity(from.WarpZone_refsys, vel);
552         if(to.WarpZone_refsys)
553                 vel = WarpZone_TransformVelocity(to.WarpZone_refsys, vel);
554         return vel;
555 }
556 vector WarpZone_RefSys_TransformAngles(entity from, entity to, vector ang)
557 {
558         if(from.WarpZone_refsys)
559                 ang = WarpZone_UnTransformAngles(from.WarpZone_refsys, ang);
560         if(to.WarpZone_refsys)
561                 ang = WarpZone_TransformAngles(to.WarpZone_refsys, ang);
562         return ang;
563 }
564 vector WarpZone_RefSys_TransformVAngles(entity from, entity to, vector ang)
565 {
566         if(from.WarpZone_refsys)
567                 ang = WarpZone_UnTransformVAngles(from.WarpZone_refsys, ang);
568         if(to.WarpZone_refsys)
569                 ang = WarpZone_TransformVAngles(to.WarpZone_refsys, ang);
570         return ang;
571 }
572 entity WarpZone_RefSys_SpawnSameRefSys(entity me)
573 {
574         entity e;
575         e = spawn();
576         if(me.WarpZone_refsys)
577         {
578                 e.WarpZone_refsys = spawn();
579                 e.WarpZone_refsys.classname = "warpzone_refsys";
580                 e.WarpZone_refsys.owner = e;
581                 e.WarpZone_refsys.think = WarpZone_RefSys_GC;
582                 e.WarpZone_refsys.nextthink = time + 1;
583                 e.WarpZone_refsys.warpzone_shift = me.WarpZone_refsys.warpzone_shift;
584                 e.WarpZone_refsys.warpzone_transform = me.WarpZone_refsys.warpzone_transform;
585         }
586         return e;
587 }