]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/warpzonelib/server.qc
Merge remote-tracking branch 'origin/divVerent/new-laser-by-morphed'
[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(player.classname == "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(clienttype(player) != CLIENTTYPE_NOTACLIENT)
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(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(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(clienttype(other) == CLIENTTYPE_NOTACLIENT)
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 not(self.target) self.target = save1;
188                 if not(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 not(self.target) self.target = save1;
196                 if not(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;
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 #ifndef 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 not(self.target) self.target = save1;
352                 if not(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 not(self.target) self.target = save1;
359                 if not(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         float f;
376         if(other.classname == "trigger_warpzone")
377                 return TRUE;
378
379         // no further impacts if we teleported this frame!
380         // this is because even if we did teleport, the engine still may raise
381         // touch events for the previous location
382         // engine now aborts moves on teleport, so this SHOULD not happen any more
383         // but if this is called from TouchAreaGrid of the projectile moving,
384         // then this won't do
385         if(time == self.warpzone_teleport_time)
386                 return TRUE;
387
388 #ifndef WARPZONELIB_KEEPDEBUG
389         // this SEEMS to not happen at the moment, but if it did, it would be more reliable
390         {
391                 float save_dpstartcontents;
392                 float save_dphitcontents;
393                 float save_dphitq3surfaceflags;
394                 string save_dphittexturename;
395                 float save_allsolid;
396                 float save_startsolid;
397                 float save_fraction;
398                 vector save_endpos;
399                 vector save_plane_normal;
400                 float save_plane_dist;
401                 entity save_ent;
402                 float save_inopen;
403                 float save_inwater;
404                 save_dpstartcontents = trace_dpstartcontents;
405                 save_dphitcontents = trace_dphitcontents;
406                 save_dphitq3surfaceflags = trace_dphitq3surfaceflags;
407                 save_dphittexturename = trace_dphittexturename;
408                 save_allsolid = trace_allsolid;
409                 save_startsolid = trace_startsolid;
410                 save_fraction = trace_fraction;
411                 save_endpos = trace_endpos;
412                 save_plane_normal = trace_plane_normal;
413                 save_plane_dist = trace_plane_dist;
414                 save_ent = trace_ent;
415                 save_inopen = trace_inopen;
416                 save_inwater = trace_inwater;
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 not(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         if(self.aiment)
574         {
575                 org = self.aiment.origin;
576                 ang = self.aiment.angles;
577                 if(area > 0)
578                 {
579                         org = org - ((org - point) * norm) * norm; // project to plane
580                         makevectors(ang);
581                         if(norm * v_forward < 0)
582                         {
583                                 print("Position target of trigger_warpzone near ", vtos(self.aiment.origin), " points into trigger_warpzone. BEWARE.\n");
584                                 norm = -1 * norm;
585                         }
586                         ang = vectoangles2(norm, v_up); // keep rotation, but turn exactly against plane
587                         ang_x = -ang_x;
588                         if(norm * v_forward < 0.99)
589                                 print("trigger_warpzone near ", vtos(self.aiment.origin), " has been turned to match plane orientation (", vtos(self.aiment.angles), " -> ", vtos(ang), "\n");
590                         if(vlen(org - self.aiment.origin) > 0.5)
591                                 print("trigger_warpzone near ", vtos(self.aiment.origin), " has been moved to match the plane (", vtos(self.aiment.origin), " -> ", vtos(org), ").\n");
592                 }
593         }
594         else if(area > 0)
595         {
596                 org = point;
597                 ang = vectoangles(norm);
598                 ang_x = -ang_x;
599         }
600         else
601                 error("cannot infer origin/angles for this warpzone, please use a killtarget or a trigger_warpzone_position");
602
603         self.warpzone_origin = org;
604         self.warpzone_angles = ang;
605 }
606
607 void WarpZone_InitStep_ClearTarget()
608 {
609         if(self.enemy)
610                 self.enemy.enemy = world;
611         self.enemy = world;
612 }
613
614 entity warpzone_first; .entity warpzone_next;
615 void WarpZone_InitStep_FindTarget()
616 {
617         float i;
618         entity e, e2;
619
620         if(self.enemy)
621                 return;
622
623         // this way only one of the two ents needs to target
624         if(self.target != "")
625         {
626                 self.enemy = self; // so the if(!e.enemy) check also skips self, saves one IF
627
628                 e2 = world;
629                 for(e = world, i = 0; (e = find(e, targetname, self.target)); )
630                         if(!e.enemy)
631                                 if(e.classname == self.classname) // possibly non-warpzones may use the same targetname!
632                                         if(random() * ++i < 1)
633                                                 e2 = e;
634                 if(!e2)
635                 {
636                         self.enemy = world;
637                         error("Warpzone with non-existing target");
638                         return;
639                 }
640                 self.enemy = e2;
641                 e2.enemy = self;
642         }
643 }
644
645 void WarpZone_Think();
646 void WarpZone_InitStep_FinalizeTransform()
647 {
648         if(!self.enemy || self.enemy.enemy != self)
649         {
650                 error("Invalid warp zone detected. Killed.");
651                 return;
652         }
653
654         warpzone_warpzones_exist = 1;
655         WarpZone_SetUp(self, self.warpzone_origin, self.warpzone_angles, self.enemy.warpzone_origin, self.enemy.warpzone_angles);
656         self.touch = WarpZone_Touch;
657         self.SendFlags = 0xFFFFFF;
658         if(self.spawnflags & 1)
659         {
660                 self.think = WarpZone_Think;
661                 self.nextthink = time;
662         }
663         else
664                 self.nextthink = 0;
665 }
666
667 float warpzone_initialized;
668 entity warpzone_first;
669 entity warpzone_position_first;
670 entity warpzone_camera_first;
671 .entity warpzone_next;
672 void spawnfunc_misc_warpzone_position(void)
673 {
674         // "target", "angles", "origin"
675         self.warpzone_next = warpzone_position_first;
676         warpzone_position_first = self;
677 }
678 void spawnfunc_trigger_warpzone_position(void)
679 {
680         spawnfunc_misc_warpzone_position();
681 }
682 void spawnfunc_trigger_warpzone(void)
683 {
684         // warp zone entities must have:
685         // "killtarget" pointing to a target_position with a direction arrow
686         //              that points AWAY from the warp zone, and that is inside
687         //              the warp zone trigger
688         // "target"     pointing to an identical warp zone at another place in
689         //              the map, with another killtarget to designate its
690         //              orientation
691
692 #ifndef WARPZONE_USE_FIXANGLE
693         // used when teleporting
694         precache_model("null");
695 #endif
696
697         if(!self.scale)
698                 self.scale = self.modelscale;
699         if(!self.scale)
700                 self.scale = 1;
701         string m;
702         m = self.model;
703         WarpZoneLib_ExactTrigger_Init();
704         if(m != "")
705         {
706                 precache_model(m);
707                 setmodel(self, m); // no precision needed
708         }
709         setorigin(self, self.origin);
710         if(self.scale)
711                 setsize(self, self.mins * self.scale, self.maxs * self.scale);
712         else
713                 setsize(self, self.mins, self.maxs);
714         self.SendEntity = WarpZone_Send;
715         self.SendFlags = 0xFFFFFF;
716         BITSET_ASSIGN(self.effects, EF_NODEPTHTEST);
717         self.warpzone_next = warpzone_first;
718         warpzone_first = self;
719 }
720 void spawnfunc_func_camera(void)
721 {
722         if(!self.scale)
723                 self.scale = self.modelscale;
724         if(!self.scale)
725                 self.scale = 1;
726         if(self.model != "")
727         {
728                 precache_model(self.model);
729                 setmodel(self, self.model); // no precision needed
730         }
731         setorigin(self, self.origin);
732         if(self.scale)
733                 setsize(self, self.mins * self.scale, self.maxs * self.scale);
734         else
735                 setsize(self, self.mins, self.maxs);
736         if(!self.solid)
737                 self.solid = SOLID_BSP;
738         else if(self.solid < 0)
739                 self.solid = SOLID_NOT;
740         self.SendEntity = WarpZone_Camera_Send;
741         self.SendFlags = 0xFFFFFF;
742         self.warpzone_next = warpzone_camera_first;
743         warpzone_camera_first = self;
744 }
745 void WarpZones_Reconnect()
746 {
747         entity e;
748         e = self;
749         for(self = warpzone_first; self; self = self.warpzone_next)
750                 WarpZone_InitStep_ClearTarget();
751         for(self = warpzone_first; self; self = self.warpzone_next)
752                 WarpZone_InitStep_FindTarget();
753         for(self = warpzone_camera_first; self; self = self.warpzone_next)
754                 WarpZoneCamera_InitStep_FindTarget();
755         for(self = warpzone_first; self; self = self.warpzone_next)
756                 WarpZone_InitStep_FinalizeTransform();
757         self = e;
758 }
759
760 void WarpZone_Think()
761 {
762         if(self.warpzone_save_origin != self.origin
763         || self.warpzone_save_angles != self.angles
764         || self.warpzone_save_eorigin != self.enemy.origin
765         || self.warpzone_save_eangles != self.enemy.angles)
766         {
767                 entity oldself;
768                 oldself = self;
769                 WarpZone_InitStep_UpdateTransform();
770                 self = self.enemy;
771                 WarpZone_InitStep_UpdateTransform();
772                 self = oldself;
773                 WarpZone_InitStep_FinalizeTransform();
774                 self = self.enemy;
775                 WarpZone_InitStep_FinalizeTransform();
776                 self = oldself;
777                 self.warpzone_save_origin = self.origin;
778                 self.warpzone_save_angles = self.angles;
779                 self.warpzone_save_eorigin = self.enemy.origin;
780                 self.warpzone_save_eangles = self.enemy.angles;
781         }
782         self.nextthink = time;
783 }
784
785 void WarpZone_StartFrame()
786 {
787         entity e;
788         if(warpzone_initialized == 0)
789         {
790                 warpzone_initialized = 1;
791                 e = self;
792                 for(self = warpzone_first; self; self = self.warpzone_next)
793                         WarpZone_InitStep_FindOriginTarget();
794                 for(self = warpzone_position_first; self; self = self.warpzone_next)
795                         WarpZonePosition_InitStep_FindTarget();
796                 for(self = warpzone_first; self; self = self.warpzone_next)
797                         WarpZone_InitStep_UpdateTransform();
798                 self = e;
799                 WarpZones_Reconnect();
800                 WarpZone_PostInitialize_Callback();
801         }
802
803         entity oldself, oldother;
804         oldself = self;
805         oldother = other;
806         for(e = world; (e = nextent(e)); )
807         {
808                 if(warpzone_warpzones_exist) { WarpZone_StoreProjectileData(e); }
809                 
810                 float f = clienttype(e);
811                 if(f == CLIENTTYPE_REAL)
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(f == CLIENTTYPE_NOTACLIENT)
835                 {
836                         if(warpzone_warpzones_exist)
837                                 for(; (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); clienttype(e) != CLIENTTYPE_NOTACLIENT; e = nextent(e))
851                 if(e.classname == "player" && clienttype(e) == CLIENTTYPE_REAL)
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(clienttype(self) == CLIENTTYPE_REAL)
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 }