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