]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/warpzonelib/server.qc
Get rid of if not, step 1.
[xonotic/xonotic-data.pk3dir.git] / qcsrc / warpzonelib / server.qc
1 #ifdef WARPZONELIB_KEEPDEBUG
2 #define WARPZONELIB_REMOVEHACK
3 #endif
4
5 // for think function
6 .vector warpzone_save_origin;
7 .vector warpzone_save_angles;
8 .vector warpzone_save_eorigin;
9 .vector warpzone_save_eangles;
10
11 // for all entities
12 .vector warpzone_oldorigin, warpzone_oldvelocity, warpzone_oldangles;
13 .float warpzone_teleport_time;
14 .float warpzone_teleport_finishtime;
15 .entity warpzone_teleport_zone;
16
17 void WarpZone_StoreProjectileData(entity e)
18 {
19         e.warpzone_oldorigin = e.origin;
20         e.warpzone_oldvelocity = e.velocity;
21         e.warpzone_oldangles = e.angles;
22 }
23
24 void WarpZone_TeleportPlayer(entity teleporter, entity player, vector to, vector to_angles, vector to_velocity)
25 {
26         setorigin (player, to); // NOTE: this also aborts the move, when this is called by touch
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(IS_PLAYER(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 wz, entity player, float f0, float f1)
50 {
51         vector o0, a0, v0, o1, a1, v1, o10;
52
53         o0 = player.origin + player.view_ofs;
54         v0 = player.velocity;
55         a0 = player.angles;
56
57         o10 = o1 = WarpZone_TransformOrigin(wz, o0);
58         v1 = WarpZone_TransformVelocity(wz, v0);
59         if (!IS_NOT_A_CLIENT(player))
60                 a1 = WarpZone_TransformVAngles(wz, player.v_angle);
61         else
62                 a1 = WarpZone_TransformAngles(wz, a0);
63
64         if(f0 != 0 || f1 != 0)
65         {
66                 // retry last move but behind the warpzone!
67                 // we must first go back as far as we can, then forward again, to not cause double touch events!
68
69                 tracebox(o1 - player.view_ofs + v1 * frametime * f1, player.mins, player.maxs, o1 - player.view_ofs + v1 * frametime * f0, MOVE_WORLDONLY, player);
70                 {
71                         entity own;
72                         own = player.owner;
73                         player.owner = world;
74                         tracebox(trace_endpos, player.mins, player.maxs, o1 - player.view_ofs + v1 * frametime * f1, MOVE_NORMAL, player); // this should get us through the warpzone
75                         player.owner = own;
76                 }
77                 o1 = trace_endpos + player.view_ofs;
78
79                 float d, dv, md;
80                 md = max(vlen(player.mins), vlen(player.maxs));
81                 d = WarpZone_TargetPlaneDist(wz, o1);
82                 dv = WarpZone_TargetPlaneDist(wz, v1);
83                 if(d < 0)
84                         o1 = o1 - v1 * (d / dv);
85         }
86
87         // put him out of solid
88         tracebox(o1 - player.view_ofs, player.mins, player.maxs, o1 - player.view_ofs, MOVE_NOMONSTERS, player);
89         if(trace_startsolid)
90         {
91                 setorigin(player, o1 - player.view_ofs);
92                 if(WarpZoneLib_MoveOutOfSolid(player))
93                 {
94                         o1 = player.origin + player.view_ofs;
95                         setorigin(player, o0 - player.view_ofs);
96                 }
97                 else
98                 {
99                         print("would have to put player in solid, won't do that\n");
100                         setorigin(player, o0 - player.view_ofs);
101                         return 0;
102                 }
103         }
104
105         // do the teleport
106         WarpZone_RefSys_Add(player, wz);
107         WarpZone_TeleportPlayer(wz, player, o1 - player.view_ofs, a1, v1);
108         WarpZone_StoreProjectileData(player);
109         player.warpzone_teleport_time = time;
110         player.warpzone_teleport_finishtime = time;
111         player.warpzone_teleport_zone = wz;
112
113         // prevent further teleports back
114         float dt = (o1 - o10) * v1 * (1 / (v1 * v1));
115         if(dt < sys_frametime)
116                 player.warpzone_teleport_finishtime += sys_frametime - dt;
117
118 #ifndef WARPZONE_USE_FIXANGLE
119         if(IS_PLAYER(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(time <= other.warpzone_teleport_finishtime) // 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 || other.tag_entity)
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         float f;
163         // number of frames we need to go back:
164         //   dist = 16*sqrt(2) qu
165         //   dist ~ 24 qu
166         //   24 qu = v*t
167         //   24 qu = v*frametime*n
168         //       n = 24 qu/(v*frametime)
169         // for clients go only one frame though, may be too irritating otherwise
170         // but max 0.25 sec = 0.25/frametime frames
171         //       24/(0.25/frametime)
172         //       96*frametime
173         float d;
174         d = 24 + max(vlen(other.mins), vlen(other.maxs));
175         if(IS_NOT_A_CLIENT(other))
176                 f = -d / bound(frametime * d * 1, frametime * vlen(other.velocity), d);
177         else
178                 f = -1;
179         if(WarpZone_Teleport(self, other, f, 0))
180         {
181                 string save1, save2;
182                 activator = other;
183
184                 save1 = self.target; self.target = string_null;
185                 save2 = self.target3; self.target3 = string_null;
186                 SUB_UseTargets();
187                 if (!self.target) self.target = save1;
188                 if (!self.target3) self.target3 = save2;
189
190                 oldself = self;
191                 self = self.enemy;
192                 save1 = self.target; self.target = string_null;
193                 save2 = self.target2; self.target2 = string_null;
194                 SUB_UseTargets();
195                 if (!self.target) self.target = save1;
196                 if (!self.target2) self.target2 = save2;
197                 self = oldself;
198         }
199         else
200         {
201                 dprint("WARPZONE FAIL AHAHAHAHAH))\n");
202         }
203 }
204
205 float WarpZone_Send(entity to, float sendflags)
206 {
207         float f;
208         WriteByte(MSG_ENTITY, ENT_CLIENT_WARPZONE);
209
210         // we must send this flag for clientside to match properly too
211         f = 0;
212         if(self.warpzone_isboxy)
213                 BITSET_ASSIGN(f, 1);
214         if(self.warpzone_fadestart)
215                 BITSET_ASSIGN(f, 2);
216         if(self.origin != '0 0 0')
217                 BITSET_ASSIGN(f, 4);
218         WriteByte(MSG_ENTITY, f);
219
220         // we need THESE to render the warpzone (and cull properly)...
221         if(f & 4)
222         {
223                 WriteCoord(MSG_ENTITY, self.origin_x);
224                 WriteCoord(MSG_ENTITY, self.origin_y);
225                 WriteCoord(MSG_ENTITY, self.origin_z);
226         }
227
228         WriteShort(MSG_ENTITY, self.modelindex);
229         WriteCoord(MSG_ENTITY, self.mins_x);
230         WriteCoord(MSG_ENTITY, self.mins_y);
231         WriteCoord(MSG_ENTITY, self.mins_z);
232         WriteCoord(MSG_ENTITY, self.maxs_x);
233         WriteCoord(MSG_ENTITY, self.maxs_y);
234         WriteCoord(MSG_ENTITY, self.maxs_z);
235         WriteByte(MSG_ENTITY, bound(1, self.scale * 16, 255));
236
237         // we need THESE to calculate the proper transform
238         WriteCoord(MSG_ENTITY, self.warpzone_origin_x);
239         WriteCoord(MSG_ENTITY, self.warpzone_origin_y);
240         WriteCoord(MSG_ENTITY, self.warpzone_origin_z);
241         WriteCoord(MSG_ENTITY, self.warpzone_angles_x);
242         WriteCoord(MSG_ENTITY, self.warpzone_angles_y);
243         WriteCoord(MSG_ENTITY, self.warpzone_angles_z);
244         WriteCoord(MSG_ENTITY, self.warpzone_targetorigin_x);
245         WriteCoord(MSG_ENTITY, self.warpzone_targetorigin_y);
246         WriteCoord(MSG_ENTITY, self.warpzone_targetorigin_z);
247         WriteCoord(MSG_ENTITY, self.warpzone_targetangles_x);
248         WriteCoord(MSG_ENTITY, self.warpzone_targetangles_y);
249         WriteCoord(MSG_ENTITY, self.warpzone_targetangles_z);
250
251         if(f & 2)
252         {
253                 WriteShort(MSG_ENTITY, self.warpzone_fadestart);
254                 WriteShort(MSG_ENTITY, self.warpzone_fadeend);
255         }
256
257         return TRUE;
258 }
259
260 float WarpZone_Camera_Send(entity to, float sendflags)
261 {
262         float f = 0;
263         WriteByte(MSG_ENTITY, ENT_CLIENT_WARPZONE_CAMERA);
264
265         if(self.warpzone_fadestart)
266                 BITSET_ASSIGN(f, 2);
267         if(self.origin != '0 0 0')
268                 BITSET_ASSIGN(f, 4);
269         WriteByte(MSG_ENTITY, f);
270
271         // we need THESE to render the warpzone (and cull properly)...
272         if(f & 4)
273         {
274                 WriteCoord(MSG_ENTITY, self.origin_x);
275                 WriteCoord(MSG_ENTITY, self.origin_y);
276                 WriteCoord(MSG_ENTITY, self.origin_z);
277         }
278
279         WriteShort(MSG_ENTITY, self.modelindex);
280         WriteCoord(MSG_ENTITY, self.mins_x);
281         WriteCoord(MSG_ENTITY, self.mins_y);
282         WriteCoord(MSG_ENTITY, self.mins_z);
283         WriteCoord(MSG_ENTITY, self.maxs_x);
284         WriteCoord(MSG_ENTITY, self.maxs_y);
285         WriteCoord(MSG_ENTITY, self.maxs_z);
286         WriteByte(MSG_ENTITY, bound(1, self.scale * 16, 255));
287
288         // we need THESE to calculate the proper transform
289         WriteCoord(MSG_ENTITY, self.enemy.origin_x);
290         WriteCoord(MSG_ENTITY, self.enemy.origin_y);
291         WriteCoord(MSG_ENTITY, self.enemy.origin_z);
292         WriteCoord(MSG_ENTITY, self.enemy.angles_x);
293         WriteCoord(MSG_ENTITY, self.enemy.angles_y);
294         WriteCoord(MSG_ENTITY, self.enemy.angles_z);
295
296         if(f & 2)
297         {
298                 WriteShort(MSG_ENTITY, self.warpzone_fadestart);
299                 WriteShort(MSG_ENTITY, self.warpzone_fadeend);
300         }
301
302         return TRUE;
303 }
304
305 #ifdef WARPZONELIB_KEEPDEBUG
306 float WarpZone_CheckProjectileImpact(entity player)
307 {
308         vector o0, v0;
309
310         o0 = player.origin + player.view_ofs;
311         v0 = player.velocity;
312
313         // if we teleported shortly before, abort
314         if(time <= player.warpzone_teleport_finishtime + 0.1)
315                 return 0;
316
317         // if player hit a warpzone, abort
318         entity wz;
319         wz = WarpZone_Find(o0 + player.mins, o0 + player.maxs);
320         if(!wz)
321                 return 0;
322
323 #ifdef WARPZONELIB_REMOVEHACK
324         print("impactfilter found something - and it no longer gets handled correctly - please tell divVerent whether anything behaves broken now\n");
325 #else
326         print("impactfilter found something - and it even gets handled correctly - please tell divVerent that this code apparently gets triggered again\n");
327 #endif
328         print("Entity type: ", player.classname, "\n");
329         print("Origin: ", vtos(player.origin), "\n");
330         print("Velocity: ", vtos(player.velocity), "\n");
331
332 #ifdef WARPZONELIB_REMOVEHACK
333         return 0;
334 #else
335         // retry previous move
336         setorigin(player, player.warpzone_oldorigin);
337         player.velocity = player.warpzone_oldvelocity;
338         if(WarpZone_Teleport(wz, player, 0, 1))
339         {
340                 entity oldself;
341                 string save1, save2;
342
343                 oldself = self;
344                 self = wz;
345                 other = player;
346                 activator = player;
347
348                 save1 = self.target; self.target = string_null;
349                 save2 = self.target3; self.target3 = string_null;
350                 SUB_UseTargets();
351                 if (!self.target) self.target = save1;
352                 if (!self.target3) self.target3 = save2;
353
354                 self = self.enemy;
355                 save1 = self.target; self.target = string_null;
356                 save2 = self.target2; self.target2 = string_null;
357                 SUB_UseTargets();
358                 if (!self.target) self.target = save1;
359                 if (!self.target2) self.target2 = save2;
360                 self = oldself;
361         }
362         else
363         {
364                 setorigin(player, o0 - player.view_ofs);
365                 player.velocity = v0;
366         }
367
368         return +1;
369 #endif
370 }
371 #endif
372
373 float WarpZone_Projectile_Touch()
374 {
375         if(other.classname == "trigger_warpzone")
376                 return TRUE;
377
378         // no further impacts if we teleported this frame!
379         // this is because even if we did teleport, the engine still may raise
380         // touch events for the previous location
381         // engine now aborts moves on teleport, so this SHOULD not happen any more
382         // but if this is called from TouchAreaGrid of the projectile moving,
383         // then this won't do
384         if(time == self.warpzone_teleport_time)
385                 return TRUE;
386
387 #ifdef WARPZONELIB_KEEPDEBUG
388         // this SEEMS to not happen at the moment, but if it did, it would be more reliable
389         {
390                 float save_dpstartcontents;
391                 float save_dphitcontents;
392                 float save_dphitq3surfaceflags;
393                 string save_dphittexturename;
394                 float save_allsolid;
395                 float save_startsolid;
396                 float save_fraction;
397                 vector save_endpos;
398                 vector save_plane_normal;
399                 float save_plane_dist;
400                 entity save_ent;
401                 float save_inopen;
402                 float save_inwater;
403                 save_dpstartcontents = trace_dpstartcontents;
404                 save_dphitcontents = trace_dphitcontents;
405                 save_dphitq3surfaceflags = trace_dphitq3surfaceflags;
406                 save_dphittexturename = trace_dphittexturename;
407                 save_allsolid = trace_allsolid;
408                 save_startsolid = trace_startsolid;
409                 save_fraction = trace_fraction;
410                 save_endpos = trace_endpos;
411                 save_plane_normal = trace_plane_normal;
412                 save_plane_dist = trace_plane_dist;
413                 save_ent = trace_ent;
414                 save_inopen = trace_inopen;
415                 save_inwater = trace_inwater;
416                 float f;
417                 if((f = WarpZone_CheckProjectileImpact(self)) != 0)
418                         return (f > 0);
419                 trace_dpstartcontents = save_dpstartcontents;
420                 trace_dphitcontents = save_dphitcontents;
421                 trace_dphitq3surfaceflags = save_dphitq3surfaceflags;
422                 trace_dphittexturename = save_dphittexturename;
423                 trace_allsolid = save_allsolid;
424                 trace_startsolid = save_startsolid;
425                 trace_fraction = save_fraction;
426                 trace_endpos = save_endpos;
427                 trace_plane_normal = save_plane_normal;
428                 trace_plane_dist = save_plane_dist;
429                 trace_ent = save_ent;
430                 trace_inopen = save_inopen;
431                 trace_inwater = save_inwater;
432         }
433 #endif
434
435         if(WarpZone_Projectile_Touch_ImpactFilter_Callback())
436                 return TRUE;
437
438         return FALSE;
439 }
440
441 void WarpZone_InitStep_FindOriginTarget()
442 {
443         if(self.killtarget != "")
444         {
445                 self.aiment = find(world, targetname, self.killtarget);
446                 if(self.aiment == world)
447                 {
448                         error("Warp zone with nonexisting killtarget");
449                         return;
450                 }
451                 self.killtarget = string_null;
452         }
453 }
454
455 void WarpZonePosition_InitStep_FindTarget()
456 {
457         if(self.target == "")
458         {
459                 error("Warp zone position with no target");
460                 return;
461         }
462         self.enemy = find(world, targetname, self.target);
463         if(self.enemy == world)
464         {
465                 error("Warp zone position with nonexisting target");
466                 return;
467         }
468         if(self.enemy.aiment)
469         {
470                 // already is positioned
471                 error("Warp zone position targeting already oriented warpzone");
472                 return;
473         }
474         self.enemy.aiment = self;
475 }
476
477 void WarpZoneCamera_Think(void)
478 {
479         if(self.warpzone_save_origin != self.origin
480         || self.warpzone_save_angles != self.angles
481         || self.warpzone_save_eorigin != self.enemy.origin
482         || self.warpzone_save_eangles != self.enemy.angles)
483         {
484                 WarpZone_Camera_SetUp(self, self.enemy.origin, self.enemy.angles);
485                 self.warpzone_save_origin = self.origin;
486                 self.warpzone_save_angles = self.angles;
487                 self.warpzone_save_eorigin = self.enemy.origin;
488                 self.warpzone_save_eangles = self.enemy.angles;
489         }
490         self.nextthink = time;
491 }
492
493 void WarpZoneCamera_InitStep_FindTarget()
494 {
495         entity e;
496         float i;
497         if(self.target == "")
498         {
499                 error("Camera with no target");
500                 return;
501         }
502         self.enemy = world;
503         for(e = world, i = 0; (e = find(e, targetname, self.target)); )
504                 if(random() * ++i < 1)
505                         self.enemy = e;
506         if(self.enemy == world)
507         {
508                 error("Camera with nonexisting target");
509                 return;
510         }
511         warpzone_cameras_exist = 1;
512         WarpZone_Camera_SetUp(self, self.enemy.origin, self.enemy.angles);
513         self.SendFlags = 0xFFFFFF;
514         if(self.spawnflags & 1)
515         {
516                 self.think = WarpZoneCamera_Think;
517                 self.nextthink = time;
518         }
519         else
520                 self.nextthink = 0;
521 }
522
523 void WarpZone_InitStep_UpdateTransform()
524 {
525         vector org, ang, norm, point;
526         float area;
527         vector tri, a, b, c, p, q, n;
528         float i_s, i_t, n_t;
529         string tex;
530
531         org = self.origin;
532         if(org == '0 0 0')
533                 org = 0.5 * (self.mins + self.maxs);
534
535         norm = point = '0 0 0';
536         area = 0;
537         for(i_s = 0; ; ++i_s)
538         {
539                 tex = getsurfacetexture(self, i_s);
540                 if (!tex)
541                         break; // this is beyond the last one
542                 if(tex == "textures/common/trigger" || tex == "trigger")
543                         continue;
544                 n_t = getsurfacenumtriangles(self, i_s);
545                 for(i_t = 0; i_t < n_t; ++i_t)
546                 {
547                         tri = getsurfacetriangle(self, i_s, i_t);
548                         a = getsurfacepoint(self, i_s, tri_x);
549                         b = getsurfacepoint(self, i_s, tri_y);
550                         c = getsurfacepoint(self, i_s, tri_z);
551                         p = b - a;
552                         q = c - a;
553                         n =     '1 0 0' * (q_y * p_z - q_z * p_y)
554                         +       '0 1 0' * (q_z * p_x - q_x * p_z)
555                         +       '0 0 1' * (q_x * p_y - q_y * p_x);
556                         area = area + vlen(n);
557                         norm = norm + n;
558                         point = point + vlen(n) * (a + b + c);
559                 }
560         }
561         if(area > 0)
562         {
563                 norm = norm * (1 / area);
564                 point = point * (1 / (3 * area));
565                 if(vlen(norm) < 0.99)
566                 {
567                         print("trigger_warpzone near ", vtos(self.aiment.origin), " is nonplanar. BEWARE.\n");
568                         area = 0; // no autofixing in this case
569                 }
570                 norm = normalize(norm);
571         }
572
573 #ifdef GMQCC
574         ang = '0 0 0';
575 #endif
576         if(self.aiment)
577         {
578                 org = self.aiment.origin;
579                 ang = self.aiment.angles;
580                 if(area > 0)
581                 {
582                         org = org - ((org - point) * norm) * norm; // project to plane
583                         makevectors(ang);
584                         if(norm * v_forward < 0)
585                         {
586                                 print("Position target of trigger_warpzone near ", vtos(self.aiment.origin), " points into trigger_warpzone. BEWARE.\n");
587                                 norm = -1 * norm;
588                         }
589                         ang = vectoangles2(norm, v_up); // keep rotation, but turn exactly against plane
590                         ang_x = -ang_x;
591                         if(norm * v_forward < 0.99)
592                                 print("trigger_warpzone near ", vtos(self.aiment.origin), " has been turned to match plane orientation (", vtos(self.aiment.angles), " -> ", vtos(ang), "\n");
593                         if(vlen(org - self.aiment.origin) > 0.5)
594                                 print("trigger_warpzone near ", vtos(self.aiment.origin), " has been moved to match the plane (", vtos(self.aiment.origin), " -> ", vtos(org), ").\n");
595                 }
596         }
597         else if(area > 0)
598         {
599                 org = point;
600                 ang = vectoangles(norm);
601                 ang_x = -ang_x;
602         }
603         else
604                 error("cannot infer origin/angles for this warpzone, please use a killtarget or a trigger_warpzone_position");
605
606         self.warpzone_origin = org;
607         self.warpzone_angles = ang;
608 }
609
610 void WarpZone_InitStep_ClearTarget()
611 {
612         if(self.enemy)
613                 self.enemy.enemy = world;
614         self.enemy = world;
615 }
616
617 entity warpzone_first; .entity warpzone_next;
618 void WarpZone_InitStep_FindTarget()
619 {
620         float i;
621         entity e, e2;
622
623         if(self.enemy)
624                 return;
625
626         // this way only one of the two ents needs to target
627         if(self.target != "")
628         {
629                 self.enemy = self; // so the if(!e.enemy) check also skips self, saves one IF
630
631                 e2 = world;
632                 for(e = world, i = 0; (e = find(e, targetname, self.target)); )
633                         if(!e.enemy)
634                                 if(e.classname == self.classname) // possibly non-warpzones may use the same targetname!
635                                         if(random() * ++i < 1)
636                                                 e2 = e;
637                 if(!e2)
638                 {
639                         self.enemy = world;
640                         error("Warpzone with non-existing target");
641                         return;
642                 }
643                 self.enemy = e2;
644                 e2.enemy = self;
645         }
646 }
647
648 void WarpZone_Think();
649 void WarpZone_InitStep_FinalizeTransform()
650 {
651         if(!self.enemy || self.enemy.enemy != self)
652         {
653                 error("Invalid warp zone detected. Killed.");
654                 return;
655         }
656
657         warpzone_warpzones_exist = 1;
658         WarpZone_SetUp(self, self.warpzone_origin, self.warpzone_angles, self.enemy.warpzone_origin, self.enemy.warpzone_angles);
659         self.touch = WarpZone_Touch;
660         self.SendFlags = 0xFFFFFF;
661         if(self.spawnflags & 1)
662         {
663                 self.think = WarpZone_Think;
664                 self.nextthink = time;
665         }
666         else
667                 self.nextthink = 0;
668 }
669
670 float warpzone_initialized;
671 entity warpzone_first;
672 entity warpzone_position_first;
673 entity warpzone_camera_first;
674 .entity warpzone_next;
675 void spawnfunc_misc_warpzone_position(void)
676 {
677         // "target", "angles", "origin"
678         self.warpzone_next = warpzone_position_first;
679         warpzone_position_first = self;
680 }
681 void spawnfunc_trigger_warpzone_position(void)
682 {
683         spawnfunc_misc_warpzone_position();
684 }
685 void spawnfunc_trigger_warpzone(void)
686 {
687         // warp zone entities must have:
688         // "killtarget" pointing to a target_position with a direction arrow
689         //              that points AWAY from the warp zone, and that is inside
690         //              the warp zone trigger
691         // "target"     pointing to an identical warp zone at another place in
692         //              the map, with another killtarget to designate its
693         //              orientation
694
695 #ifndef WARPZONE_USE_FIXANGLE
696         // used when teleporting
697         precache_model("null");
698 #endif
699
700         if(!self.scale)
701                 self.scale = self.modelscale;
702         if(!self.scale)
703                 self.scale = 1;
704         string m;
705         m = self.model;
706         WarpZoneLib_ExactTrigger_Init();
707         if(m != "")
708         {
709                 precache_model(m);
710                 setmodel(self, m); // no precision needed
711         }
712         setorigin(self, self.origin);
713         if(self.scale)
714                 setsize(self, self.mins * self.scale, self.maxs * self.scale);
715         else
716                 setsize(self, self.mins, self.maxs);
717         self.SendEntity = WarpZone_Send;
718         self.SendFlags = 0xFFFFFF;
719         BITSET_ASSIGN(self.effects, EF_NODEPTHTEST);
720         self.warpzone_next = warpzone_first;
721         warpzone_first = self;
722 }
723 void spawnfunc_func_camera(void)
724 {
725         if(!self.scale)
726                 self.scale = self.modelscale;
727         if(!self.scale)
728                 self.scale = 1;
729         if(self.model != "")
730         {
731                 precache_model(self.model);
732                 setmodel(self, self.model); // no precision needed
733         }
734         setorigin(self, self.origin);
735         if(self.scale)
736                 setsize(self, self.mins * self.scale, self.maxs * self.scale);
737         else
738                 setsize(self, self.mins, self.maxs);
739         if(!self.solid)
740                 self.solid = SOLID_BSP;
741         else if(self.solid < 0)
742                 self.solid = SOLID_NOT;
743         self.SendEntity = WarpZone_Camera_Send;
744         self.SendFlags = 0xFFFFFF;
745         self.warpzone_next = warpzone_camera_first;
746         warpzone_camera_first = self;
747 }
748 void WarpZones_Reconnect()
749 {
750         entity e;
751         e = self;
752         for(self = warpzone_first; self; self = self.warpzone_next)
753                 WarpZone_InitStep_ClearTarget();
754         for(self = warpzone_first; self; self = self.warpzone_next)
755                 WarpZone_InitStep_FindTarget();
756         for(self = warpzone_camera_first; self; self = self.warpzone_next)
757                 WarpZoneCamera_InitStep_FindTarget();
758         for(self = warpzone_first; self; self = self.warpzone_next)
759                 WarpZone_InitStep_FinalizeTransform();
760         self = e;
761 }
762
763 void WarpZone_Think()
764 {
765         if(self.warpzone_save_origin != self.origin
766         || self.warpzone_save_angles != self.angles
767         || self.warpzone_save_eorigin != self.enemy.origin
768         || self.warpzone_save_eangles != self.enemy.angles)
769         {
770                 entity oldself;
771                 oldself = self;
772                 WarpZone_InitStep_UpdateTransform();
773                 self = self.enemy;
774                 WarpZone_InitStep_UpdateTransform();
775                 self = oldself;
776                 WarpZone_InitStep_FinalizeTransform();
777                 self = self.enemy;
778                 WarpZone_InitStep_FinalizeTransform();
779                 self = oldself;
780                 self.warpzone_save_origin = self.origin;
781                 self.warpzone_save_angles = self.angles;
782                 self.warpzone_save_eorigin = self.enemy.origin;
783                 self.warpzone_save_eangles = self.enemy.angles;
784         }
785         self.nextthink = time;
786 }
787
788 void WarpZone_StartFrame()
789 {
790         entity e;
791         if(warpzone_initialized == 0)
792         {
793                 warpzone_initialized = 1;
794                 e = self;
795                 for(self = warpzone_first; self; self = self.warpzone_next)
796                         WarpZone_InitStep_FindOriginTarget();
797                 for(self = warpzone_position_first; self; self = self.warpzone_next)
798                         WarpZonePosition_InitStep_FindTarget();
799                 for(self = warpzone_first; self; self = self.warpzone_next)
800                         WarpZone_InitStep_UpdateTransform();
801                 self = e;
802                 WarpZones_Reconnect();
803                 WarpZone_PostInitialize_Callback();
804         }
805
806         entity oldself, oldother;
807         oldself = self;
808         oldother = other;
809         for(e = world; (e = nextent(e)); )
810         {
811                 if(warpzone_warpzones_exist) { WarpZone_StoreProjectileData(e); }
812                 
813                 if(IS_REAL_CLIENT(e))
814                 {
815                         if(e.solid == SOLID_NOT) // not spectating?
816                         if(e.movetype == MOVETYPE_NOCLIP || e.movetype == MOVETYPE_FLY || e.movetype == MOVETYPE_FLY_WORLDONLY) // not spectating? (this is to catch observers)
817                         {
818                                 other = e; // player
819                         
820                                 // warpzones
821                                 if(warpzone_warpzones_exist) { 
822                                 self = WarpZone_Find(e.origin + e.mins, e.origin + e.maxs); 
823                                 if(self)
824                                 if(!WarpZoneLib_ExactTrigger_Touch())
825                                         if(WarpZone_PlaneDist(self, e.origin + e.view_ofs) <= 0)
826                                                 WarpZone_Teleport(self, e, -1, 0); } // NOT triggering targets by this!
827                         
828                                 // teleporters
829                                 self = Teleport_Find(e.origin + e.mins, e.origin + e.maxs);
830                                 if(self)
831                                 if(!WarpZoneLib_ExactTrigger_Touch())
832                                         Simple_TeleportPlayer(self, other); // NOT triggering targets by this!
833                         }
834                 }
835                 
836                 if(IS_NOT_A_CLIENT(e))
837                 {
838                         if(warpzone_warpzones_exist)
839                                 for(; (e = nextent(e)); )
840                                         WarpZone_StoreProjectileData(e);
841                         break;
842                 }
843         }
844         self = oldself;
845         other = oldother;
846 }
847
848 .float warpzone_reconnecting;
849 float visible_to_some_client(entity ent)
850 {
851         entity e;
852         for(e = nextent(world); !IS_NOT_A_CLIENT(e); e = nextent(e))
853                 if(IS_PLAYER(e) && IS_REAL_CLIENT(e))
854                         if(checkpvs(e.origin + e.view_ofs, ent))
855                                 return 1;
856         return 0;
857 }
858 void trigger_warpzone_reconnect_use()
859 {
860         entity e;
861         e = self;
862         // NOTE: this matches for target, not targetname, but of course
863         // targetname must be set too on the other entities
864         for(self = warpzone_first; self; self = self.warpzone_next)
865                 self.warpzone_reconnecting = ((e.target == "" || self.target == e.target) && !((e.spawnflags & 1) && (visible_to_some_client(self) || visible_to_some_client(self.enemy))));
866         for(self = warpzone_camera_first; self; self = self.warpzone_next)
867                 self.warpzone_reconnecting = ((e.target == "" || self.target == e.target) && !((e.spawnflags & 1) && visible_to_some_client(self)));
868         for(self = warpzone_first; self; self = self.warpzone_next)
869                 if(self.warpzone_reconnecting)
870                         WarpZone_InitStep_ClearTarget();
871         for(self = warpzone_first; self; self = self.warpzone_next)
872                 if(self.warpzone_reconnecting)
873                         WarpZone_InitStep_FindTarget();
874         for(self = warpzone_camera_first; self; self = self.warpzone_next)
875                 if(self.warpzone_reconnecting)
876                         WarpZoneCamera_InitStep_FindTarget();
877         for(self = warpzone_first; self; self = self.warpzone_next)
878                 if(self.warpzone_reconnecting || self.enemy.warpzone_reconnecting)
879                         WarpZone_InitStep_FinalizeTransform();
880         self = e;
881 }
882
883 void spawnfunc_trigger_warpzone_reconnect()
884 {
885         self.use = trigger_warpzone_reconnect_use;
886 }
887
888 void spawnfunc_target_warpzone_reconnect()
889 {
890         spawnfunc_trigger_warpzone_reconnect(); // both names make sense here :(
891 }
892
893 void WarpZone_PlayerPhysics_FixVAngle(void)
894 {
895 #ifndef WARPZONE_DONT_FIX_VANGLE
896         if(IS_REAL_CLIENT(self))
897         if(self.v_angle_z <= 360) // if not already adjusted
898         if(time - self.ping * 0.001 < self.warpzone_teleport_time)
899         {
900                 self.v_angle = WarpZone_TransformVAngles(self.warpzone_teleport_zone, self.v_angle);
901                 self.v_angle_z += 720; // mark as adjusted
902         }
903 #endif
904 }