Remove `-Wno-double-declaration`
[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         ang = '0 0 0';
574         if(self.aiment)
575         {
576                 org = self.aiment.origin;
577                 ang = self.aiment.angles;
578                 if(area > 0)
579                 {
580                         org = org - ((org - point) * norm) * norm; // project to plane
581                         makevectors(ang);
582                         if(norm * v_forward < 0)
583                         {
584                                 print("Position target of trigger_warpzone near ", vtos(self.aiment.origin), " points into trigger_warpzone. BEWARE.\n");
585                                 norm = -1 * norm;
586                         }
587                         ang = vectoangles2(norm, v_up); // keep rotation, but turn exactly against plane
588                         ang_x = -ang.x;
589                         if(norm * v_forward < 0.99)
590                                 print("trigger_warpzone near ", vtos(self.aiment.origin), " has been turned to match plane orientation (", vtos(self.aiment.angles), " -> ", vtos(ang), "\n");
591                         if(vlen(org - self.aiment.origin) > 0.5)
592                                 print("trigger_warpzone near ", vtos(self.aiment.origin), " has been moved to match the plane (", vtos(self.aiment.origin), " -> ", vtos(org), ").\n");
593                 }
594         }
595         else if(area > 0)
596         {
597                 org = point;
598                 ang = vectoangles(norm);
599                 ang_x = -ang.x;
600         }
601         else
602                 error("cannot infer origin/angles for this warpzone, please use a killtarget or a trigger_warpzone_position");
603
604         self.warpzone_origin = org;
605         self.warpzone_angles = ang;
606 }
607
608 void WarpZone_InitStep_ClearTarget()
609 {
610         if(self.enemy)
611                 self.enemy.enemy = world;
612         self.enemy = world;
613 }
614
615 entity warpzone_first; .entity warpzone_next;
616 void WarpZone_InitStep_FindTarget()
617 {
618         float i;
619         entity e, e2;
620
621         if(self.enemy)
622                 return;
623
624         // this way only one of the two ents needs to target
625         if(self.target != "")
626         {
627                 self.enemy = self; // so the if(!e.enemy) check also skips self, saves one IF
628
629                 e2 = world;
630                 for(e = world, i = 0; (e = find(e, targetname, self.target)); )
631                         if(!e.enemy)
632                                 if(e.classname == self.classname) // possibly non-warpzones may use the same targetname!
633                                         if(random() * ++i < 1)
634                                                 e2 = e;
635                 if(!e2)
636                 {
637                         self.enemy = world;
638                         error("Warpzone with non-existing target");
639                         return;
640                 }
641                 self.enemy = e2;
642                 e2.enemy = self;
643         }
644 }
645
646 void WarpZone_Think();
647 void WarpZone_InitStep_FinalizeTransform()
648 {
649         if(!self.enemy || self.enemy.enemy != self)
650         {
651                 error("Invalid warp zone detected. Killed.");
652                 return;
653         }
654
655         warpzone_warpzones_exist = 1;
656         WarpZone_SetUp(self, self.warpzone_origin, self.warpzone_angles, self.enemy.warpzone_origin, self.enemy.warpzone_angles);
657         self.touch = WarpZone_Touch;
658         self.SendFlags = 0xFFFFFF;
659         if(self.spawnflags & 1)
660         {
661                 self.think = WarpZone_Think;
662                 self.nextthink = time;
663         }
664         else
665                 self.nextthink = 0;
666 }
667
668 float warpzone_initialized;
669 //entity warpzone_first;
670 entity warpzone_position_first;
671 entity warpzone_camera_first;
672 .entity warpzone_next;
673 void spawnfunc_misc_warpzone_position(void)
674 {
675         // "target", "angles", "origin"
676         self.warpzone_next = warpzone_position_first;
677         warpzone_position_first = self;
678 }
679 void spawnfunc_trigger_warpzone_position(void)
680 {
681         spawnfunc_misc_warpzone_position();
682 }
683 void spawnfunc_trigger_warpzone(void)
684 {
685         // warp zone entities must have:
686         // "killtarget" pointing to a target_position with a direction arrow
687         //              that points AWAY from the warp zone, and that is inside
688         //              the warp zone trigger
689         // "target"     pointing to an identical warp zone at another place in
690         //              the map, with another killtarget to designate its
691         //              orientation
692
693 #ifndef WARPZONE_USE_FIXANGLE
694         // used when teleporting
695         precache_model("null");
696 #endif
697
698         if(!self.scale)
699                 self.scale = self.modelscale;
700         if(!self.scale)
701                 self.scale = 1;
702         string m;
703         m = self.model;
704         WarpZoneLib_ExactTrigger_Init();
705         if(m != "")
706         {
707                 precache_model(m);
708                 setmodel(self, m); // no precision needed
709         }
710         setorigin(self, self.origin);
711         if(self.scale)
712                 setsize(self, self.mins * self.scale, self.maxs * self.scale);
713         else
714                 setsize(self, self.mins, self.maxs);
715         self.SendEntity = WarpZone_Send;
716         self.SendFlags = 0xFFFFFF;
717         BITSET_ASSIGN(self.effects, EF_NODEPTHTEST);
718         self.warpzone_next = warpzone_first;
719         warpzone_first = self;
720 }
721 void spawnfunc_func_camera(void)
722 {
723         if(!self.scale)
724                 self.scale = self.modelscale;
725         if(!self.scale)
726                 self.scale = 1;
727         if(self.model != "")
728         {
729                 precache_model(self.model);
730                 setmodel(self, self.model); // no precision needed
731         }
732         setorigin(self, self.origin);
733         if(self.scale)
734                 setsize(self, self.mins * self.scale, self.maxs * self.scale);
735         else
736                 setsize(self, self.mins, self.maxs);
737         if(!self.solid)
738                 self.solid = SOLID_BSP;
739         else if(self.solid < 0)
740                 self.solid = SOLID_NOT;
741         self.SendEntity = WarpZone_Camera_Send;
742         self.SendFlags = 0xFFFFFF;
743         self.warpzone_next = warpzone_camera_first;
744         warpzone_camera_first = self;
745 }
746 void WarpZones_Reconnect()
747 {
748         entity e;
749         e = self;
750         for(self = warpzone_first; self; self = self.warpzone_next)
751                 WarpZone_InitStep_ClearTarget();
752         for(self = warpzone_first; self; self = self.warpzone_next)
753                 WarpZone_InitStep_FindTarget();
754         for(self = warpzone_camera_first; self; self = self.warpzone_next)
755                 WarpZoneCamera_InitStep_FindTarget();
756         for(self = warpzone_first; self; self = self.warpzone_next)
757                 WarpZone_InitStep_FinalizeTransform();
758         self = e;
759 }
760
761 void WarpZone_Think()
762 {
763         if(self.warpzone_save_origin != self.origin
764         || self.warpzone_save_angles != self.angles
765         || self.warpzone_save_eorigin != self.enemy.origin
766         || self.warpzone_save_eangles != self.enemy.angles)
767         {
768                 entity oldself;
769                 oldself = self;
770                 WarpZone_InitStep_UpdateTransform();
771                 self = self.enemy;
772                 WarpZone_InitStep_UpdateTransform();
773                 self = oldself;
774                 WarpZone_InitStep_FinalizeTransform();
775                 self = self.enemy;
776                 WarpZone_InitStep_FinalizeTransform();
777                 self = oldself;
778                 self.warpzone_save_origin = self.origin;
779                 self.warpzone_save_angles = self.angles;
780                 self.warpzone_save_eorigin = self.enemy.origin;
781                 self.warpzone_save_eangles = self.enemy.angles;
782         }
783         self.nextthink = time;
784 }
785
786 void WarpZone_StartFrame()
787 {
788         entity e;
789         if(warpzone_initialized == 0)
790         {
791                 warpzone_initialized = 1;
792                 e = self;
793                 for(self = warpzone_first; self; self = self.warpzone_next)
794                         WarpZone_InitStep_FindOriginTarget();
795                 for(self = warpzone_position_first; self; self = self.warpzone_next)
796                         WarpZonePosition_InitStep_FindTarget();
797                 for(self = warpzone_first; self; self = self.warpzone_next)
798                         WarpZone_InitStep_UpdateTransform();
799                 self = e;
800                 WarpZones_Reconnect();
801                 WarpZone_PostInitialize_Callback();
802         }
803
804         entity oldself, oldother;
805         oldself = self;
806         oldother = other;
807         for(e = world; (e = nextent(e)); )
808         {
809                 if(warpzone_warpzones_exist) { WarpZone_StoreProjectileData(e); }
810
811                 if(IS_REAL_CLIENT(e))
812                 {
813                         if(e.solid == SOLID_NOT) // not spectating?
814                         if(e.movetype == MOVETYPE_NOCLIP || e.movetype == MOVETYPE_FLY || e.movetype == MOVETYPE_FLY_WORLDONLY) // not spectating? (this is to catch observers)
815                         {
816                                 other = e; // player
817
818                                 // warpzones
819                                 if(warpzone_warpzones_exist) {
820                                 self = WarpZone_Find(e.origin + e.mins, e.origin + e.maxs);
821                                 if(self)
822                                 if(!WarpZoneLib_ExactTrigger_Touch())
823                                         if(WarpZone_PlaneDist(self, e.origin + e.view_ofs) <= 0)
824                                                 WarpZone_Teleport(self, e, -1, 0); } // NOT triggering targets by this!
825
826                                 // teleporters
827                                 self = Teleport_Find(e.origin + e.mins, e.origin + e.maxs);
828                                 if(self)
829                                 if(!WarpZoneLib_ExactTrigger_Touch())
830                                         Simple_TeleportPlayer(self, other); // NOT triggering targets by this!
831                         }
832                 }
833
834                 if(IS_NOT_A_CLIENT(e))
835                 {
836                         if(warpzone_warpzones_exist)
837                                 for(0; (e = nextent(e)); )
838                                         WarpZone_StoreProjectileData(e);
839                         break;
840                 }
841         }
842         self = oldself;
843         other = oldother;
844 }
845
846 .float warpzone_reconnecting;
847 float visible_to_some_client(entity ent)
848 {
849         entity e;
850         for(e = nextent(world); !IS_NOT_A_CLIENT(e); e = nextent(e))
851                 if(IS_PLAYER(e) && IS_REAL_CLIENT(e))
852                         if(checkpvs(e.origin + e.view_ofs, ent))
853                                 return 1;
854         return 0;
855 }
856 void trigger_warpzone_reconnect_use()
857 {
858         entity e;
859         e = self;
860         // NOTE: this matches for target, not targetname, but of course
861         // targetname must be set too on the other entities
862         for(self = warpzone_first; self; self = self.warpzone_next)
863                 self.warpzone_reconnecting = ((e.target == "" || self.target == e.target) && !((e.spawnflags & 1) && (visible_to_some_client(self) || visible_to_some_client(self.enemy))));
864         for(self = warpzone_camera_first; self; self = self.warpzone_next)
865                 self.warpzone_reconnecting = ((e.target == "" || self.target == e.target) && !((e.spawnflags & 1) && visible_to_some_client(self)));
866         for(self = warpzone_first; self; self = self.warpzone_next)
867                 if(self.warpzone_reconnecting)
868                         WarpZone_InitStep_ClearTarget();
869         for(self = warpzone_first; self; self = self.warpzone_next)
870                 if(self.warpzone_reconnecting)
871                         WarpZone_InitStep_FindTarget();
872         for(self = warpzone_camera_first; self; self = self.warpzone_next)
873                 if(self.warpzone_reconnecting)
874                         WarpZoneCamera_InitStep_FindTarget();
875         for(self = warpzone_first; self; self = self.warpzone_next)
876                 if(self.warpzone_reconnecting || self.enemy.warpzone_reconnecting)
877                         WarpZone_InitStep_FinalizeTransform();
878         self = e;
879 }
880
881 void spawnfunc_trigger_warpzone_reconnect()
882 {
883         self.use = trigger_warpzone_reconnect_use;
884 }
885
886 void spawnfunc_target_warpzone_reconnect()
887 {
888         spawnfunc_trigger_warpzone_reconnect(); // both names make sense here :(
889 }
890
891 void WarpZone_PlayerPhysics_FixVAngle(void)
892 {
893 #ifndef WARPZONE_DONT_FIX_VANGLE
894         if(IS_REAL_CLIENT(self))
895         if(self.v_angle.z <= 360) // if not already adjusted
896         if(time - self.ping * 0.001 < self.warpzone_teleport_time)
897         {
898                 self.v_angle = WarpZone_TransformVAngles(self.warpzone_teleport_zone, self.v_angle);
899                 self.v_angle_z += 720; // mark as adjusted
900         }
901 #endif
902 }