]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/warpzonelib/server.qc
use spawnflag 1 instead
[xonotic/xonotic-data.pk3dir.git] / qcsrc / warpzonelib / server.qc
1 // for think function
2 .vector warpzone_save_origin;
3 .vector warpzone_save_angles;
4 .vector warpzone_save_eorigin;
5 .vector warpzone_save_eangles;
6
7 // for all entities
8 .vector warpzone_oldorigin, warpzone_oldvelocity, warpzone_oldangles;
9 .float warpzone_teleport_time;
10 .entity warpzone_teleport_zone;
11
12 void WarpZone_StoreProjectileData(entity e)
13 {
14         e.warpzone_oldorigin = e.origin;
15         e.warpzone_oldvelocity = e.velocity;
16         e.warpzone_oldangles = e.angles;
17 }
18
19 void WarpZone_TeleportPlayer(entity teleporter, entity player, vector to, vector to_angles, vector to_velocity)
20 {
21         vector from;
22
23         makevectors (to_angles);
24
25         from = player.origin;
26         setorigin (player, to);
27         player.oldorigin = to; // for DP's unsticking
28         player.angles = to_angles;
29         player.fixangle = TRUE;
30         player.velocity = to_velocity;
31
32         BITXOR_ASSIGN(player.effects, EF_TELEPORT_BIT);
33
34         if(player.classname == "player")
35                 BITCLR_ASSIGN(player.flags, FL_ONGROUND);
36
37         WarpZone_PostTeleportPlayer_Callback(player);
38 }
39
40 float WarpZone_Teleported_Send(entity to, float sf)
41 {
42         WriteByte(MSG_ENTITY, ENT_CLIENT_WARPZONE_TELEPORTED);
43         WriteCoord(MSG_ENTITY, self.angles_x);
44         WriteCoord(MSG_ENTITY, self.angles_y);
45         WriteCoord(MSG_ENTITY, self.angles_z);
46         return TRUE;
47 }
48
49 float WarpZone_Teleport(entity player)
50 {
51         vector o0, a0, v0, o1, a1, v1;
52
53         o0 = player.origin + player.view_ofs;
54         v0 = player.velocity;
55         a0 = player.angles;
56
57         if(WarpZone_PlaneDist(self, o0) >= 0) // wrong side of the trigger_warpzone
58                 return 2;
59         // no failure, we simply don't want to teleport yet; TODO in
60         // this situation we may want to create a temporary clone
61         // entity of the player to fix graphics glitch
62
63         o1 = WarpZone_TransformOrigin(self, o0);
64         v1 = WarpZone_TransformVelocity(self, v0);
65         if(clienttype(player) != CLIENTTYPE_NOTACLIENT)
66                 a1 = WarpZone_TransformVAngles(self, player.v_angle);
67         else
68                 a1 = WarpZone_TransformAngles(self, a0);
69
70         // put him inside solid
71         tracebox(o1 - player.view_ofs, player.mins, player.maxs, o1 - player.view_ofs, MOVE_NOMONSTERS, player);
72         if(trace_startsolid)
73         {
74                 vector mi, ma;
75                 mi = player.mins;
76                 ma = player.maxs;
77                 setsize(player, mi - player.view_ofs, ma - player.view_ofs);
78                 setorigin(player, o1);
79                 if(WarpZoneLib_MoveOutOfSolid(player))
80                 {
81                         o1 = player.origin;
82                         setsize(player, mi, ma);
83                         setorigin(player, o0);
84                 }
85                 else
86                 {
87                         print("would have to put player in solid, won't do that\n");
88                         setsize(player, mi, ma);
89                         setorigin(player, o0 - player.view_ofs);
90                         return 0; // cannot fix
91                 }
92         }
93
94         if(WarpZone_TargetPlaneDist(self, o1) <= 0)
95         {
96                 print("inconsistent warp zones or evil roundoff error\n");
97                 return 0;
98         }
99
100         //print(sprintf("warpzone: %f %f %f -> %f %f %f\n", o0_x, o0_y, o0_z, o1_x, o1_y, o1_z));
101
102         //o1 = trace_endpos;
103         WarpZone_RefSys_Add(player, self);
104         WarpZone_TeleportPlayer(self, player, o1 - player.view_ofs, a1, v1);
105         WarpZone_StoreProjectileData(player);
106         player.warpzone_teleport_time = time;
107         player.warpzone_teleport_zone = self;
108 #ifndef WARPZONE_USE_FIXANGLE
109         // instead of fixangle, send the transform to the client for smoother operation
110         player.fixangle = FALSE;
111
112         entity ts = spawn();
113         setmodel(ts, "null");
114         ts.SendEntity = WarpZone_Teleported_Send;
115         ts.SendFlags = 0xFFFFFF;
116         ts.drawonlytoclient = player;
117         ts.think = SUB_Remove;
118         ts.nextthink = time + 1;
119         ts.owner = player;
120         ts.enemy = self;
121         ts.effects = EF_NODEPTHTEST;
122         ts.classname = "warpzone_teleported";
123         ts.angles = self.warpzone_transform;
124 #endif
125
126         return 1;
127 }
128
129 void WarpZone_Touch (void)
130 {
131         entity oldself, e;
132
133         if(other.classname == "trigger_warpzone")
134                 return;
135
136         // FIXME needs a better check to know what is safe to teleport and what not
137         if(other.movetype == MOVETYPE_NONE)
138                 return;
139
140         if(WarpZoneLib_ExactTrigger_Touch())
141                 return;
142
143         e = self.enemy;
144         if(WarpZone_Teleport(other))
145         {
146                 string save1, save2;
147                 activator = other;
148
149                 save1 = self.target; self.target = string_null;
150                 save2 = self.target3; self.target3 = string_null;
151                 SUB_UseTargets();
152                 if not(self.target) self.target = save1;
153                 if not(self.target3) self.target3 = save2;
154
155                 oldself = self;
156                 self = self.enemy;
157                 save1 = self.target; self.target = string_null;
158                 save2 = self.target2; self.target2 = string_null;
159                 SUB_UseTargets();
160                 if not(self.target) self.target = save1;
161                 if not(self.target2) self.target2 = save2;
162                 self = oldself;
163         }
164         else
165         {
166                 dprint("WARPZONE FAIL AHAHAHAHAH))\n");
167         }
168 }
169
170 float WarpZone_Send(entity to, float sendflags)
171 {
172         float f;
173         WriteByte(MSG_ENTITY, ENT_CLIENT_WARPZONE);
174
175         // we must send this flag for clientside to match properly too
176         f = 0;
177         if(self.warpzone_isboxy)
178                 BITSET_ASSIGN(f, 1);
179         if(self.warpzone_fadestart)
180                 BITSET_ASSIGN(f, 2);
181         if(self.origin != '0 0 0')
182                 BITSET_ASSIGN(f, 4);
183         WriteByte(MSG_ENTITY, f);
184
185         // we need THESE to render the warpzone (and cull properly)...
186         if(f & 4)
187         {
188                 WriteCoord(MSG_ENTITY, self.origin_x);
189                 WriteCoord(MSG_ENTITY, self.origin_y);
190                 WriteCoord(MSG_ENTITY, self.origin_z);
191         }
192
193         WriteShort(MSG_ENTITY, self.modelindex);
194         WriteCoord(MSG_ENTITY, self.mins_x);
195         WriteCoord(MSG_ENTITY, self.mins_y);
196         WriteCoord(MSG_ENTITY, self.mins_z);
197         WriteCoord(MSG_ENTITY, self.maxs_x);
198         WriteCoord(MSG_ENTITY, self.maxs_y);
199         WriteCoord(MSG_ENTITY, self.maxs_z);
200         WriteByte(MSG_ENTITY, bound(1, self.scale * 16, 255));
201
202         // we need THESE to calculate the proper transform
203         WriteCoord(MSG_ENTITY, self.warpzone_origin_x);
204         WriteCoord(MSG_ENTITY, self.warpzone_origin_y);
205         WriteCoord(MSG_ENTITY, self.warpzone_origin_z);
206         WriteCoord(MSG_ENTITY, self.warpzone_angles_x);
207         WriteCoord(MSG_ENTITY, self.warpzone_angles_y);
208         WriteCoord(MSG_ENTITY, self.warpzone_angles_z);
209         WriteCoord(MSG_ENTITY, self.warpzone_targetorigin_x);
210         WriteCoord(MSG_ENTITY, self.warpzone_targetorigin_y);
211         WriteCoord(MSG_ENTITY, self.warpzone_targetorigin_z);
212         WriteCoord(MSG_ENTITY, self.warpzone_targetangles_x);
213         WriteCoord(MSG_ENTITY, self.warpzone_targetangles_y);
214         WriteCoord(MSG_ENTITY, self.warpzone_targetangles_z);
215
216         if(f & 2)
217         {
218                 WriteShort(MSG_ENTITY, self.warpzone_fadestart);
219                 WriteShort(MSG_ENTITY, self.warpzone_fadeend);
220         }
221
222         return TRUE;
223 }
224
225 float WarpZone_Camera_Send(entity to, float sendflags)
226 {
227         float f;
228         WriteByte(MSG_ENTITY, ENT_CLIENT_WARPZONE_CAMERA);
229
230         if(self.warpzone_fadestart)
231                 BITSET_ASSIGN(f, 2);
232         if(self.origin != '0 0 0')
233                 BITSET_ASSIGN(f, 4);
234         WriteByte(MSG_ENTITY, f);
235
236         // we need THESE to render the warpzone (and cull properly)...
237         if(f & 4)
238         {
239                 WriteCoord(MSG_ENTITY, self.origin_x);
240                 WriteCoord(MSG_ENTITY, self.origin_y);
241                 WriteCoord(MSG_ENTITY, self.origin_z);
242         }
243
244         WriteShort(MSG_ENTITY, self.modelindex);
245         WriteCoord(MSG_ENTITY, self.mins_x);
246         WriteCoord(MSG_ENTITY, self.mins_y);
247         WriteCoord(MSG_ENTITY, self.mins_z);
248         WriteCoord(MSG_ENTITY, self.maxs_x);
249         WriteCoord(MSG_ENTITY, self.maxs_y);
250         WriteCoord(MSG_ENTITY, self.maxs_z);
251         WriteByte(MSG_ENTITY, bound(1, self.scale * 16, 255));
252
253         // we need THESE to calculate the proper transform
254         WriteCoord(MSG_ENTITY, self.enemy.origin_x);
255         WriteCoord(MSG_ENTITY, self.enemy.origin_y);
256         WriteCoord(MSG_ENTITY, self.enemy.origin_z);
257         WriteCoord(MSG_ENTITY, self.enemy.angles_x);
258         WriteCoord(MSG_ENTITY, self.enemy.angles_y);
259         WriteCoord(MSG_ENTITY, self.enemy.angles_z);
260
261         if(f & 2)
262         {
263                 WriteShort(MSG_ENTITY, self.warpzone_fadestart);
264                 WriteShort(MSG_ENTITY, self.warpzone_fadeend);
265         }
266
267         return TRUE;
268 }
269
270 float WarpZone_CheckProjectileImpact()
271 {
272         // if self hit a warpzone, abort
273         vector o0, v0, a0;
274         float mpd, pd, dpd;
275         entity wz;
276         wz = WarpZone_Find(self.origin + self.mins, self.origin + self.maxs);
277         if(!wz)
278                 return 0;
279         if(self.warpzone_teleport_time == time)
280         {
281                 // just ignore if we got teleported this frame already and now hit a wall and are in a warpzone again (this will cause a detonation)
282                 // print("2 warps 1 frame\n");
283                 return -1;
284         }
285         o0 = self.origin;
286         v0 = self.velocity;
287         a0 = self.angles;
288
289         // this approach transports the projectile at its full speed, but does
290         // not properly retain the projectile trail (but we can't retain it
291         // easily anyway without delaying the projectile by two frames, so who
292         // cares)
293         WarpZone_TraceBox_ThroughZone(self.warpzone_oldorigin, self.mins, self.maxs, self.warpzone_oldorigin + self.warpzone_oldvelocity * frametime, MOVE_NORMAL, self, wz, WarpZone_trace_callback_t_null); // this will get us through the warpzone
294         setorigin(self, trace_endpos);
295         self.angles = WarpZone_TransformAngles(WarpZone_trace_transform, self.angles);
296         self.velocity = WarpZone_TransformVelocity(WarpZone_trace_transform, self.warpzone_oldvelocity);
297         
298         // in case we are in our warp zone post-teleport, shift the projectile forward a bit
299         mpd = max(vlen(self.mins), vlen(self.maxs));
300         pd = WarpZone_TargetPlaneDist(wz, self.origin);
301         if(pd < mpd)
302         {
303                 dpd = normalize(self.velocity) * wz.warpzone_targetforward;
304                 setorigin(self, self.origin + normalize(self.velocity) * ((mpd - pd) / dpd));
305                 if(!WarpZoneLib_MoveOutOfSolid(self))
306                 {
307                         setorigin(self, o0);
308                         self.angles = a0;
309                         self.velocity = v0;
310                         return 0;
311                 }
312         }
313         WarpZone_RefSys_Add(self, wz);
314         WarpZone_StoreProjectileData(self);
315         self.warpzone_teleport_time = time;
316
317         return +1;
318 }
319 float WarpZone_Projectile_Touch()
320 {
321         float f;
322         if(other.classname == "trigger_warpzone")
323                 return TRUE;
324         if(WarpZone_Projectile_Touch_ImpactFilter_Callback())
325                 return TRUE;
326         if((f = WarpZone_CheckProjectileImpact()) != 0)
327                 return (f > 0);
328         if(self.warpzone_teleport_time == time)
329         {
330                 // sequence: hit warpzone, get teleported, hit wall
331                 // print("2 hits 1 frame\n");
332                 setorigin(self, self.warpzone_oldorigin);
333                 self.velocity = self.warpzone_oldvelocity;
334                 self.angles = self.warpzone_oldangles;
335                 return TRUE;
336         }
337         return FALSE;
338 }
339
340 void WarpZone_InitStep_FindOriginTarget()
341 {
342         if(self.killtarget != "")
343         {
344                 self.aiment = find(world, targetname, self.killtarget);
345                 if(self.aiment == world)
346                 {
347                         error("Warp zone with nonexisting killtarget");
348                         return;
349                 }
350                 self.killtarget = string_null;
351         }
352 }
353
354 void WarpZonePosition_InitStep_FindTarget()
355 {
356         if(self.target == "")
357         {
358                 error("Warp zone position with no target");
359                 return;
360         }
361         self.enemy = find(world, targetname, self.target);
362         if(self.enemy == world)
363         {
364                 error("Warp zone position with nonexisting target");
365                 return;
366         }
367         if(self.enemy.aiment)
368         {
369                 // already is positioned
370                 error("Warp zone position targeting already oriented warpzone");
371                 return;
372         }
373         self.enemy.aiment = self;
374 }
375
376 void WarpZoneCamera_Think(void)
377 {
378         if(self.warpzone_save_origin != self.origin
379         || self.warpzone_save_angles != self.angles
380         || self.warpzone_save_eorigin != self.enemy.origin
381         || self.warpzone_save_eangles != self.enemy.angles)
382         {
383                 WarpZone_Camera_SetUp(self, self.enemy.origin, self.enemy.angles);
384                 self.warpzone_save_origin = self.origin;
385                 self.warpzone_save_angles = self.angles;
386                 self.warpzone_save_eorigin = self.enemy.origin;
387                 self.warpzone_save_eangles = self.enemy.angles;
388         }
389         self.nextthink = time;
390 }
391
392 void WarpZoneCamera_InitStep_FindTarget()
393 {
394         entity e;
395         float i;
396         if(self.target == "")
397         {
398                 error("Camera with no target");
399                 return;
400         }
401         self.enemy = world;
402         for(e = world, i = 0; (e = find(e, targetname, self.target)); )
403                 if(random() * ++i < 1)
404                         self.enemy = e;
405         if(self.enemy == world)
406         {
407                 error("Camera with nonexisting target");
408                 return;
409         }
410         warpzone_cameras_exist = 1;
411         WarpZone_Camera_SetUp(self, self.enemy.origin, self.enemy.angles);
412         self.SendFlags = 0xFFFFFF;
413         if(self.spawnflags & 1)
414         {
415                 self.think = WarpZoneCamera_Think;
416                 self.nextthink = time;
417         }
418         else
419                 self.nextthink = 0;
420 }
421
422 void WarpZone_InitStep_UpdateTransform()
423 {
424         vector org, ang, norm, point;
425         float area;
426         vector tri, a, b, c, p, q, n;
427         float i_s, i_t, n_t;
428         string tex;
429
430         org = self.origin;
431         if(org == '0 0 0')
432                 org = 0.5 * (self.mins + self.maxs);
433
434         norm = point = '0 0 0';
435         area = 0;
436         for(i_s = 0; ; ++i_s)
437         {
438                 tex = getsurfacetexture(self, i_s);
439                 if not(tex)
440                         break; // this is beyond the last one
441                 if(tex == "textures/common/trigger" || tex == "trigger")
442                         continue;
443                 n_t = getsurfacenumtriangles(self, i_s);
444                 for(i_t = 0; i_t < n_t; ++i_t)
445                 {
446                         tri = getsurfacetriangle(self, i_s, i_t);
447                         a = getsurfacepoint(self, i_s, tri_x);
448                         b = getsurfacepoint(self, i_s, tri_y);
449                         c = getsurfacepoint(self, i_s, tri_z);
450                         p = b - a;
451                         q = c - a;
452                         n =     '1 0 0' * (q_y * p_z - q_z * p_y)
453                         +       '0 1 0' * (q_z * p_x - q_x * p_z)
454                         +       '0 0 1' * (q_x * p_y - q_y * p_x);
455                         area = area + vlen(n);
456                         norm = norm + n;
457                         point = point + vlen(n) * (a + b + c);
458                 }
459         }
460         if(area > 0)
461         {
462                 norm = norm * (1 / area);
463                 point = point * (1 / (3 * area));
464                 if(vlen(norm) < 0.99)
465                 {
466                         print("trigger_warpzone near ", vtos(self.aiment.origin), " is nonplanar. BEWARE.\n");
467                         area = 0; // no autofixing in this case
468                 }
469                 norm = normalize(norm);
470         }
471
472         if(self.aiment)
473         {
474                 org = self.aiment.origin;
475                 ang = self.aiment.angles;
476                 if(area > 0)
477                 {
478                         org = org - ((org - point) * norm) * norm; // project to plane
479                         makevectors(ang);
480                         if(norm * v_forward < 0)
481                         {
482                                 print("Position target of trigger_warpzone near ", vtos(self.aiment.origin), " points into trigger_warpzone. BEWARE.\n");
483                                 norm = -1 * norm;
484                         }
485                         ang = vectoangles(norm, v_up); // keep rotation, but turn exactly against plane
486                         ang_x = -ang_x;
487                         if(norm * v_forward < 0.99)
488                                 print("trigger_warpzone near ", vtos(self.aiment.origin), " has been turned to match plane orientation (", vtos(self.aiment.angles), " -> ", vtos(ang), "\n");
489                         if(vlen(org - self.aiment.origin) > 0.5)
490                                 print("trigger_warpzone near ", vtos(self.aiment.origin), " has been moved to match the plane (", vtos(self.aiment.origin), " -> ", vtos(org), ").\n");
491                 }
492         }
493         else if(area > 0)
494         {
495                 org = point;
496                 ang = vectoangles(norm);
497                 ang_x = -ang_x;
498         }
499         else
500                 error("cannot infer origin/angles for this warpzone, please use a killtarget or a trigger_warpzone_position");
501
502         self.warpzone_origin = org;
503         self.warpzone_angles = ang;
504 }
505
506 void WarpZone_InitStep_ClearTarget()
507 {
508         if(self.enemy)
509                 self.enemy.enemy = world;
510         self.enemy = world;
511 }
512
513 entity warpzone_first; .entity warpzone_next;
514 void WarpZone_InitStep_FindTarget()
515 {
516         float i;
517         entity e, e2;
518
519         if(self.enemy)
520                 return;
521
522         // this way only one of the two ents needs to target
523         if(self.target != "")
524         {
525                 self.enemy = self; // so the if(!e.enemy) check also skips self, saves one IF
526
527                 e2 = world;
528                 for(e = world, i = 0; (e = find(e, targetname, self.target)); )
529                         if(!e.enemy)
530                                 if(e.classname == self.classname) // possibly non-warpzones may use the same targetname!
531                                         if(random() * ++i < 1)
532                                                 e2 = e;
533                 if(!e2)
534                 {
535                         self.enemy = world;
536                         error("Warpzone with non-existing target");
537                         return;
538                 }
539                 self.enemy = e2;
540                 e2.enemy = self;
541         }
542 }
543
544 void WarpZone_Think();
545 void WarpZone_InitStep_FinalizeTransform()
546 {
547         if(!self.enemy || self.enemy.enemy != self)
548         {
549                 error("Invalid warp zone detected. Killed.");
550                 return;
551         }
552
553         warpzone_warpzones_exist = 1;
554         WarpZone_SetUp(self, self.warpzone_origin, self.warpzone_angles, self.enemy.warpzone_origin, self.enemy.warpzone_angles);
555         self.touch = WarpZone_Touch;
556         self.SendFlags = 0xFFFFFF;
557         if(self.spawnflags & 1)
558         {
559                 self.think = WarpZone_Think;
560                 self.nextthink = time;
561         }
562         else
563                 self.nextthink = 0;
564 }
565
566 float warpzone_initialized;
567 entity warpzone_first;
568 entity warpzone_position_first;
569 entity warpzone_camera_first;
570 .entity warpzone_next;
571 void spawnfunc_misc_warpzone_position(void)
572 {
573         // "target", "angles", "origin"
574         self.warpzone_next = warpzone_position_first;
575         warpzone_position_first = self;
576 }
577 void spawnfunc_trigger_warpzone_position(void)
578 {
579         spawnfunc_misc_warpzone_position();
580 }
581 void spawnfunc_trigger_warpzone(void)
582 {
583         // warp zone entities must have:
584         // "killtarget" pointing to a target_position with a direction arrow
585         //              that points AWAY from the warp zone, and that is inside
586         //              the warp zone trigger
587         // "target"     pointing to an identical warp zone at another place in
588         //              the map, with another killtarget to designate its
589         //              orientation
590
591         if(!self.scale)
592                 self.scale = self.modelscale;
593         if(!self.scale)
594                 self.scale = 1;
595         string m;
596         m = self.model;
597         WarpZoneLib_ExactTrigger_Init();
598         if(m != "")
599         {
600                 precache_model(m);
601                 setmodel(self, m); // no precision needed
602         }
603         setorigin(self, self.origin);
604         if(self.scale)
605                 setsize(self, self.mins * self.scale, self.maxs * self.scale);
606         else
607                 setsize(self, self.mins, self.maxs);
608         self.SendEntity = WarpZone_Send;
609         self.SendFlags = 0xFFFFFF;
610         BITSET_ASSIGN(self.effects, EF_NODEPTHTEST);
611         self.warpzone_next = warpzone_first;
612         warpzone_first = self;
613 }
614 void spawnfunc_func_camera(void)
615 {
616         if(!self.scale)
617                 self.scale = self.modelscale;
618         if(!self.scale)
619                 self.scale = 1;
620         if(self.model != "")
621         {
622                 precache_model(self.model);
623                 setmodel(self, self.model); // no precision needed
624         }
625         setorigin(self, self.origin);
626         if(self.scale)
627                 setsize(self, self.mins * self.scale, self.maxs * self.scale);
628         else
629                 setsize(self, self.mins, self.maxs);
630         if(!self.solid)
631                 self.solid = SOLID_BSP;
632         else if(self.solid < 0)
633                 self.solid = SOLID_NOT;
634         self.SendEntity = WarpZone_Camera_Send;
635         self.SendFlags = 0xFFFFFF;
636         self.warpzone_next = warpzone_camera_first;
637         warpzone_camera_first = self;
638 }
639 void WarpZones_Reconnect()
640 {
641         entity e;
642         e = self;
643         for(self = warpzone_first; self; self = self.warpzone_next)
644                 WarpZone_InitStep_ClearTarget();
645         for(self = warpzone_first; self; self = self.warpzone_next)
646                 WarpZone_InitStep_FindTarget();
647         for(self = warpzone_camera_first; self; self = self.warpzone_next)
648                 WarpZoneCamera_InitStep_FindTarget();
649         for(self = warpzone_first; self; self = self.warpzone_next)
650                 WarpZone_InitStep_FinalizeTransform();
651         self = e;
652 }
653
654 void WarpZone_Think()
655 {
656         if(self.warpzone_save_origin != self.origin
657         || self.warpzone_save_angles != self.angles
658         || self.warpzone_save_eorigin != self.enemy.origin
659         || self.warpzone_save_eangles != self.enemy.angles)
660         {
661                 entity oldself;
662                 oldself = self;
663                 WarpZone_InitStep_UpdateTransform();
664                 self = self.enemy;
665                 WarpZone_InitStep_UpdateTransform();
666                 self = oldself;
667                 WarpZone_InitStep_FinalizeTransform();
668                 self = self.enemy;
669                 WarpZone_InitStep_FinalizeTransform();
670                 self = oldself;
671                 self.warpzone_save_origin = self.origin;
672                 self.warpzone_save_angles = self.angles;
673                 self.warpzone_save_eorigin = self.enemy.origin;
674                 self.warpzone_save_eangles = self.enemy.angles;
675         }
676         self.nextthink = time;
677 }
678
679 void WarpZone_StartFrame()
680 {
681         entity e;
682         if(warpzone_initialized == 0)
683         {
684                 warpzone_initialized = 1;
685                 e = self;
686                 for(self = warpzone_first; self; self = self.warpzone_next)
687                         WarpZone_InitStep_FindOriginTarget();
688                 for(self = warpzone_position_first; self; self = self.warpzone_next)
689                         WarpZonePosition_InitStep_FindTarget();
690                 for(self = warpzone_first; self; self = self.warpzone_next)
691                         WarpZone_InitStep_UpdateTransform();
692                 self = e;
693                 WarpZones_Reconnect();
694         }
695
696         if(warpzone_warpzones_exist)
697         {
698                 entity oldself, oldother;
699                 oldself = self;
700                 oldother = other;
701                 for(e = world; (e = nextent(e)); )
702                 {
703                         WarpZone_StoreProjectileData(e);
704                         float f;
705                         f = clienttype(e);
706                         if(f == CLIENTTYPE_REAL)
707                         {
708                                 if(e.solid != SOLID_NOT) // not spectating?
709                                         continue;
710                                 if(e.movetype != MOVETYPE_NOCLIP && e.movetype != MOVETYPE_FLY) // not spectating? (this is to catch observers)
711                                         continue;
712                                 self = WarpZone_Find(e.origin + e.mins, e.origin + e.maxs);
713                                 if(!self)
714                                         continue;
715                                 other = e;
716                                 if(WarpZoneLib_ExactTrigger_Touch())
717                                         continue;
718                                 WarpZone_Teleport(e); // NOT triggering targets by this!
719                         }
720                         if(f == CLIENTTYPE_NOTACLIENT)
721                         {
722                                 for(; (e = nextent(e)); )
723                                         WarpZone_StoreProjectileData(e);
724                                 break;
725                         }
726                 }
727                 self = oldself;
728                 other = oldother;
729         }
730 }
731
732 .float warpzone_reconnecting;
733 float visible_to_some_client(entity ent)
734 {
735         entity e;
736         for(e = nextent(world); clienttype(e) != CLIENTTYPE_NOTACLIENT; e = nextent(e))
737                 if(e.classname == "player" && clienttype(e) == CLIENTTYPE_REAL)
738                         if(checkpvs(e.origin + e.view_ofs, ent))
739                                 return 1;
740         return 0;
741 }
742 void trigger_warpzone_reconnect_use()
743 {
744         entity e;
745         e = self;
746         // NOTE: this matches for target, not targetname, but of course
747         // targetname must be set too on the other entities
748         for(self = warpzone_first; self; self = self.warpzone_next)
749                 self.warpzone_reconnecting = ((e.target == "" || self.target == e.target) && !((e.spawnflags & 1) && (visible_to_some_client(self) || visible_to_some_client(self.enemy))));
750         for(self = warpzone_camera_first; self; self = self.warpzone_next)
751                 self.warpzone_reconnecting = ((e.target == "" || self.target == e.target) && !((e.spawnflags & 1) && visible_to_some_client(self)));
752         for(self = warpzone_first; self; self = self.warpzone_next)
753                 if(self.warpzone_reconnecting)
754                         WarpZone_InitStep_ClearTarget();
755         for(self = warpzone_first; self; self = self.warpzone_next)
756                 if(self.warpzone_reconnecting)
757                         WarpZone_InitStep_FindTarget();
758         for(self = warpzone_camera_first; self; self = self.warpzone_next)
759                 if(self.warpzone_reconnecting)
760                         WarpZoneCamera_InitStep_FindTarget();
761         for(self = warpzone_first; self; self = self.warpzone_next)
762                 if(self.warpzone_reconnecting || self.enemy.warpzone_reconnecting)
763                         WarpZone_InitStep_FinalizeTransform();
764         self = e;
765 }
766
767 void spawnfunc_trigger_warpzone_reconnect()
768 {
769         self.use = trigger_warpzone_reconnect_use;
770 }
771
772 void spawnfunc_target_warpzone_reconnect()
773 {
774         spawnfunc_trigger_warpzone_reconnect(); // both names make sense here :(
775 }
776
777 void WarpZone_PlayerPhysics_FixVAngle(void)
778 {
779 #ifndef WARPZONE_DONT_FIX_VANGLE
780         if(clienttype(self) == CLIENTTYPE_REAL)
781         if(self.v_angle_z <= 360) // if not already adjusted
782         if(time - self.ping * 0.001 < self.warpzone_teleport_time)
783         {
784                 self.v_angle = WarpZone_TransformVAngles(self.warpzone_teleport_zone, self.v_angle);
785                 self.v_angle_z += 720; // mark as adjusted
786         }
787 #endif
788 }