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