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