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