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