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