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