7 #include <common/constants.qh>
8 #include <common/net_linked.qh>
9 #include <common/mapobjects/subs.qh>
10 #include <common/mapobjects/teleporters.qh>
11 #include <common/util.qh>
12 #include <server/constants.qh>
13 #include <common/weapons/_all.qh>
14 #include <common/stats.qh>
15 #include <server/utils.qh>
19 bool autocvar_sv_warpzone_allow_selftarget;
22 #ifdef WARPZONELIB_KEEPDEBUG
23 #define WARPZONELIB_REMOVEHACK
27 .vector warpzone_save_origin;
28 .vector warpzone_save_angles;
29 .vector warpzone_save_eorigin;
30 .vector warpzone_save_eangles;
33 .vector warpzone_oldorigin, warpzone_oldvelocity, warpzone_oldangles;
34 .float warpzone_teleport_time;
35 .float warpzone_teleport_finishtime;
36 .entity warpzone_teleport_zone;
38 #define WarpZone_StoreProjectileData(e_) MACRO_BEGIN \
40 e.warpzone_oldorigin = e.origin; \
41 e.warpzone_oldvelocity = e.velocity; \
42 e.warpzone_oldangles = e.angles; \
45 void WarpZone_TeleportPlayer(entity teleporter, entity player, vector to, vector to_angles, vector to_velocity)
48 player.lastteleport_origin = player.origin;
49 player.lastteleporttime = time;
51 setorigin(player, to); // NOTE: this also aborts the move, when this is called by touch
52 player.angles = to_angles;
54 player.oldorigin = to; // for DP's unsticking
55 player.fixangle = true;
56 if (IS_BOT_CLIENT(player))
58 // FIXME find a way to smooth view's angles change for bots too
59 player.v_angle = player.angles;
60 bot_aim_reset(player);
63 player.velocity = to_velocity;
65 BITXOR_ASSIGN(player.effects, EF_TELEPORT_BIT);
68 BITCLR_ASSIGN(player.flags, FL_ONGROUND);
70 WarpZone_PostTeleportPlayer_Callback(player);
74 bool WarpZone_Teleported_Send(entity this, entity to, int sf)
76 WriteHeader(MSG_ENTITY, ENT_CLIENT_WARPZONE_TELEPORTED);
77 WriteVector(MSG_ENTITY, this.angles);
82 float WarpZone_Teleport(entity wz, entity player, float f0, float f1)
84 vector o0, a0, v0, o1, a1, v1, o10;
86 o0 = player.origin + player.view_ofs;
90 o10 = o1 = WarpZone_TransformOrigin(wz, o0);
91 v1 = WarpZone_TransformVelocity(wz, v0);
92 if (!IS_NOT_A_CLIENT(player))
93 a1 = WarpZone_TransformVAngles(wz, PHYS_INPUT_ANGLES(player));
95 a1 = WarpZone_TransformAngles(wz, a0);
97 if(f0 != 0 || f1 != 0)
99 // retry last move but behind the warpzone!
100 // we must first go back as far as we can, then forward again, to not cause double touch events!
102 tracebox(o1 - player.view_ofs + v1 * frametime * f1, player.mins, player.maxs, o1 - player.view_ofs + v1 * frametime * f0, MOVE_WORLDONLY, player);
107 tracebox(trace_endpos, player.mins, player.maxs, o1 - player.view_ofs + v1 * frametime * f1, MOVE_NORMAL, player); // this should get us through the warpzone
110 o1 = trace_endpos + player.view_ofs;
113 md = max(vlen(player.mins), vlen(player.maxs));
114 d = WarpZone_TargetPlaneDist(wz, o1);
115 dv = WarpZone_TargetPlaneDist(wz, v1);
117 o1 = o1 - v1 * (d / dv);
120 // put him out of solid
121 tracebox(o1 - player.view_ofs, player.mins, player.maxs, o1 - player.view_ofs, MOVE_NOMONSTERS, player);
124 setorigin(player, o1 - player.view_ofs);
125 if(WarpZoneLib_MoveOutOfSolid(player))
127 o1 = player.origin + player.view_ofs;
128 setorigin(player, o0 - player.view_ofs);
132 LOG_INFO("would have to put player in solid, won't do that");
133 setorigin(player, o0 - player.view_ofs);
139 WarpZone_RefSys_Add(player, wz);
140 WarpZone_TeleportPlayer(wz, player, o1 - player.view_ofs, a1, v1);
141 WarpZone_StoreProjectileData(player);
142 player.warpzone_teleport_time = time;
143 player.warpzone_teleport_finishtime = time;
144 player.warpzone_teleport_zone = wz;
147 // prevent further teleports back
148 float dt = (o1 - o10) * v1 * (1 / (v1 * v1));
149 if(dt < PHYS_INPUT_FRAMETIME)
150 player.warpzone_teleport_finishtime += PHYS_INPUT_FRAMETIME - dt;
153 #ifndef WARPZONE_USE_FIXANGLE
155 if(IS_VEHICLE(player) && player.owner)
156 player = player.owner; // hax
157 if(IS_PLAYER(player))
159 // instead of fixangle, send the transform to the client for smoother operation
160 player.fixangle = false;
162 entity ts = new(warpzone_teleported);
163 setmodel(ts, MDL_Null);
164 setSendEntity(ts, WarpZone_Teleported_Send);
165 ts.SendFlags = 0xFFFFFF;
166 ts.drawonlytoclient = player;
167 setthink(ts, SUB_Remove);
168 ts.nextthink = time + 1;
171 ts.effects = EF_NODEPTHTEST;
172 ts.angles = wz.warpzone_transform;
175 setproperty(VF_CL_VIEWANGLES, WarpZone_TransformVAngles(wz, getpropertyvec(VF_CL_VIEWANGLES)));
176 //if(checkextension("DP_CSQC_ROTATEMOVES"))
177 //CL_RotateMoves(wz.warpzone_transform);
184 void WarpZone_Touch(entity this, entity toucher)
186 if(toucher.classname == "trigger_warpzone")
189 if(time <= toucher.warpzone_teleport_finishtime) // already teleported this frame
192 // FIXME needs a better check to know what is safe to teleport and what not
193 if(toucher.move_movetype == MOVETYPE_NONE || toucher.move_movetype == MOVETYPE_FOLLOW || toucher.tag_entity)
196 if(WarpZoneLib_ExactTrigger_Touch(this, toucher))
199 if(WarpZone_PlaneDist(this, toucher.origin + toucher.view_ofs) >= 0) // wrong side of the trigger_warpzone (don't teleport yet)
203 // number of frames we need to go back:
204 // dist = 16*sqrt(2) qu
207 // 24 qu = v*frametime*n
208 // n = 24 qu/(v*frametime)
209 // for clients go only one frame though, may be too irritating otherwise
210 // but max 0.25 sec = 0.25/frametime frames
211 // 24/(0.25/frametime)
214 d = 24 + max(vlen(toucher.mins), vlen(toucher.maxs));
215 if(IS_NOT_A_CLIENT(toucher))
216 f = -d / bound(frametime * d * 1, frametime * vlen(toucher.velocity), d);
219 if(WarpZone_Teleport(this, toucher, f, 0))
222 SUB_UseTargets_SkipTargets(this, toucher, toucher, BIT(1) | BIT(3)); // use toucher too?
223 SUB_UseTargets_SkipTargets(this.enemy, toucher, toucher, BIT(1) | BIT(2)); // use toucher too?
228 LOG_TRACE("WARPZONE FAIL AHAHAHAHAH))");
233 bool WarpZone_Send(entity this, entity to, int sendflags)
235 WriteHeader(MSG_ENTITY, ENT_CLIENT_WARPZONE);
237 // we must send this flag for clientside to match properly too
239 if(this.warpzone_isboxy)
241 if(this.warpzone_fadestart)
243 if(this.origin != '0 0 0')
245 WriteByte(MSG_ENTITY, f);
247 // we need THESE to render the warpzone (and cull properly)...
250 WriteCoord(MSG_ENTITY, this.origin.x);
251 WriteCoord(MSG_ENTITY, this.origin.y);
252 WriteCoord(MSG_ENTITY, this.origin.z);
255 WriteShort(MSG_ENTITY, this.modelindex);
256 WriteCoord(MSG_ENTITY, this.mins.x);
257 WriteCoord(MSG_ENTITY, this.mins.y);
258 WriteCoord(MSG_ENTITY, this.mins.z);
259 WriteCoord(MSG_ENTITY, this.maxs.x);
260 WriteCoord(MSG_ENTITY, this.maxs.y);
261 WriteCoord(MSG_ENTITY, this.maxs.z);
262 WriteByte(MSG_ENTITY, bound(1, this.scale * 16, 255));
264 // we need THESE to calculate the proper transform
265 WriteCoord(MSG_ENTITY, this.warpzone_origin.x);
266 WriteCoord(MSG_ENTITY, this.warpzone_origin.y);
267 WriteCoord(MSG_ENTITY, this.warpzone_origin.z);
268 WriteCoord(MSG_ENTITY, this.warpzone_angles.x);
269 WriteCoord(MSG_ENTITY, this.warpzone_angles.y);
270 WriteCoord(MSG_ENTITY, this.warpzone_angles.z);
271 WriteCoord(MSG_ENTITY, this.warpzone_targetorigin.x);
272 WriteCoord(MSG_ENTITY, this.warpzone_targetorigin.y);
273 WriteCoord(MSG_ENTITY, this.warpzone_targetorigin.z);
274 WriteCoord(MSG_ENTITY, this.warpzone_targetangles.x);
275 WriteCoord(MSG_ENTITY, this.warpzone_targetangles.y);
276 WriteCoord(MSG_ENTITY, this.warpzone_targetangles.z);
280 WriteShort(MSG_ENTITY, this.warpzone_fadestart);
281 WriteShort(MSG_ENTITY, this.warpzone_fadeend);
287 bool WarpZone_Camera_Send(entity this, entity to, int sendflags)
290 WriteHeader(MSG_ENTITY, ENT_CLIENT_WARPZONE_CAMERA);
292 if(this.warpzone_fadestart)
294 if(this.origin != '0 0 0')
296 WriteByte(MSG_ENTITY, f);
298 // we need THESE to render the warpzone (and cull properly)...
301 WriteCoord(MSG_ENTITY, this.origin.x);
302 WriteCoord(MSG_ENTITY, this.origin.y);
303 WriteCoord(MSG_ENTITY, this.origin.z);
306 WriteShort(MSG_ENTITY, this.modelindex);
307 WriteCoord(MSG_ENTITY, this.mins.x);
308 WriteCoord(MSG_ENTITY, this.mins.y);
309 WriteCoord(MSG_ENTITY, this.mins.z);
310 WriteCoord(MSG_ENTITY, this.maxs.x);
311 WriteCoord(MSG_ENTITY, this.maxs.y);
312 WriteCoord(MSG_ENTITY, this.maxs.z);
313 WriteByte(MSG_ENTITY, bound(1, this.scale * 16, 255));
315 // we need THESE to calculate the proper transform
316 WriteCoord(MSG_ENTITY, this.enemy.origin.x);
317 WriteCoord(MSG_ENTITY, this.enemy.origin.y);
318 WriteCoord(MSG_ENTITY, this.enemy.origin.z);
319 WriteCoord(MSG_ENTITY, this.enemy.angles.x);
320 WriteCoord(MSG_ENTITY, this.enemy.angles.y);
321 WriteCoord(MSG_ENTITY, this.enemy.angles.z);
325 WriteShort(MSG_ENTITY, this.warpzone_fadestart);
326 WriteShort(MSG_ENTITY, this.warpzone_fadeend);
332 #ifdef WARPZONELIB_KEEPDEBUG
333 float WarpZone_CheckProjectileImpact(entity player)
337 o0 = player.origin + player.view_ofs;
338 v0 = player.velocity;
340 // if we teleported shortly before, abort
341 if(time <= player.warpzone_teleport_finishtime + 0.1)
344 // if player hit a warpzone, abort
346 wz = WarpZone_Find(o0 + player.mins, o0 + player.maxs);
350 #ifdef WARPZONELIB_REMOVEHACK
351 LOG_INFO("impactfilter found something - and it no longer gets handled correctly - please tell divVerent whether anything behaves broken now");
353 LOG_INFO("impactfilter found something - and it even gets handled correctly - please tell divVerent that this code apparently gets triggered again");
355 LOG_INFO("Entity type: ", player.classname);
356 LOG_INFO("Origin: ", vtos(player.origin));
357 LOG_INFO("Velocity: ", vtos(player.velocity));
359 #ifdef WARPZONELIB_REMOVEHACK
362 // retry previous move
363 setorigin(player, player.warpzone_oldorigin);
364 player.velocity = player.warpzone_oldvelocity;
365 if(WarpZone_Teleport(wz, player, 0, 1))
367 SUB_UseTargets_SkipTargets(wz, player, player, BIT(1) | BIT(3));
368 SUB_UseTargets_SkipTargets(wz.enemy, player, player, BIT(1) | BIT(2));
372 setorigin(player, o0 - player.view_ofs);
373 player.velocity = v0;
382 float WarpZone_Projectile_Touch(entity this, entity toucher)
384 if(toucher.classname == "trigger_warpzone")
387 // no further impacts if we teleported this frame!
388 // this is because even if we did teleport, the engine still may raise
389 // touch events for the previous location
390 // engine now aborts moves on teleport, so this SHOULD not happen any more
391 // but if this is called from TouchAreaGrid of the projectile moving,
392 // then this won't do
393 if(time == this.warpzone_teleport_time)
397 #ifdef WARPZONELIB_KEEPDEBUG
398 // this SEEMS to not happen at the moment, but if it did, it would be more reliable
400 float save_dpstartcontents;
401 float save_dphitcontents;
402 float save_dphitq3surfaceflags;
403 string save_dphittexturename;
405 float save_startsolid;
408 vector save_plane_normal;
409 float save_plane_dist;
413 save_dpstartcontents = trace_dpstartcontents;
414 save_dphitcontents = trace_dphitcontents;
415 save_dphitq3surfaceflags = trace_dphitq3surfaceflags;
416 save_dphittexturename = trace_dphittexturename;
417 save_allsolid = trace_allsolid;
418 save_startsolid = trace_startsolid;
419 save_fraction = trace_fraction;
420 save_endpos = trace_endpos;
421 save_plane_normal = trace_plane_normal;
422 save_plane_dist = trace_plane_dist;
423 save_ent = trace_ent;
424 save_inopen = trace_inopen;
425 save_inwater = trace_inwater;
426 float f = WarpZone_CheckProjectileImpact(this);
427 if (f) return (f > 0);
428 trace_dpstartcontents = save_dpstartcontents;
429 trace_dphitcontents = save_dphitcontents;
430 trace_dphitq3surfaceflags = save_dphitq3surfaceflags;
431 trace_dphittexturename = save_dphittexturename;
432 trace_allsolid = save_allsolid;
433 trace_startsolid = save_startsolid;
434 trace_fraction = save_fraction;
435 trace_endpos = save_endpos;
436 trace_plane_normal = save_plane_normal;
437 trace_plane_dist = save_plane_dist;
438 trace_ent = save_ent;
439 trace_inopen = save_inopen;
440 trace_inwater = save_inwater;
444 if(WarpZone_Projectile_Touch_ImpactFilter_Callback(this, toucher))
453 void WarpZone_InitStep_FindOriginTarget(entity this)
455 if(this.killtarget != "")
457 this.aiment = find(NULL, targetname, this.killtarget);
458 if(this.aiment == NULL)
460 error("Warp zone with nonexisting killtarget");
463 this.killtarget = string_null;
467 void WarpZonePosition_InitStep_FindTarget(entity this)
469 if(this.target == "")
471 error("Warp zone position with no target");
474 this.enemy = find(NULL, targetname, this.target);
475 if(this.enemy == NULL)
477 error("Warp zone position with nonexisting target");
480 if(this.enemy.aiment)
482 // already is positioned
483 error("Warp zone position targeting already oriented warpzone");
486 this.enemy.aiment = this;
489 void WarpZoneCamera_Think(entity this)
491 if(this.warpzone_save_origin != this.origin
492 || this.warpzone_save_angles != this.angles
493 || this.warpzone_save_eorigin != this.enemy.origin
494 || this.warpzone_save_eangles != this.enemy.angles)
496 WarpZone_Camera_SetUp(this, this.enemy.origin, this.enemy.angles);
497 this.warpzone_save_origin = this.origin;
498 this.warpzone_save_angles = this.angles;
499 this.warpzone_save_eorigin = this.enemy.origin;
500 this.warpzone_save_eangles = this.enemy.angles;
502 this.nextthink = time;
505 void WarpZoneCamera_InitStep_FindTarget(entity this)
509 if(this.target == "")
511 error("Camera with no target");
515 for(e = NULL, i = 0; (e = find(e, targetname, this.target)); )
516 if(random() * ++i < 1)
518 if(this.enemy == NULL)
520 error("Camera with nonexisting target");
523 warpzone_cameras_exist = 1;
524 WarpZone_Camera_SetUp(this, this.enemy.origin, this.enemy.angles);
525 this.SendFlags = 0xFFFFFF;
526 if(this.spawnflags & 1)
528 setthink(this, WarpZoneCamera_Think);
529 this.nextthink = time;
535 void WarpZone_InitStep_UpdateTransform(entity this)
537 vector org, ang, norm, point;
539 vector tri, a, b, c, n;
545 org = 0.5 * (this.mins + this.maxs);
547 norm = point = '0 0 0';
549 for(i_s = 0; ; ++i_s)
551 tex = getsurfacetexture(this, i_s);
553 break; // this is beyond the last one
554 if(tex == "textures/common/trigger" || tex == "trigger")
556 n_t = getsurfacenumtriangles(this, i_s);
557 for(i_t = 0; i_t < n_t; ++i_t)
559 tri = getsurfacetriangle(this, i_s, i_t);
560 a = getsurfacepoint(this, i_s, tri.x);
561 b = getsurfacepoint(this, i_s, tri.y);
562 c = getsurfacepoint(this, i_s, tri.z);
563 n = cross(c - a, b - a);
564 area = area + vlen(n);
566 point = point + vlen(n) * (a + b + c);
571 norm = norm * (1 / area);
572 point = point * (1 / (3 * area));
573 if(vdist(norm, <, 0.99))
575 LOG_INFO("trigger_warpzone near ", vtos(this.aiment.origin), " is nonplanar. BEWARE.");
576 area = 0; // no autofixing in this case
578 norm = normalize(norm);
584 org = this.aiment.origin;
585 ang = this.aiment.angles;
588 org = org - ((org - point) * norm) * norm; // project to plane
589 vector forward, right, up;
590 MAKE_VECTORS(ang, forward, right, up);
591 if(norm * forward < 0)
593 LOG_INFO("Position target of trigger_warpzone near ", vtos(this.aiment.origin), " points into trigger_warpzone. BEWARE.");
596 ang = vectoangles2(norm, up); // keep rotation, but turn exactly against plane
598 if(norm * forward < 0.99)
599 LOG_INFO("trigger_warpzone near ", vtos(this.aiment.origin), " has been turned to match plane orientation (", vtos(this.aiment.angles), " -> ", vtos(ang));
600 if(vdist(org - this.aiment.origin, >, 0.5))
601 LOG_INFO("trigger_warpzone near ", vtos(this.aiment.origin), " has been moved to match the plane (", vtos(this.aiment.origin), " -> ", vtos(org), ").");
607 ang = vectoangles(norm);
611 error("cannot infer origin/angles for this warpzone, please use a killtarget or a trigger_warpzone_position");
613 this.warpzone_origin = org;
614 this.warpzone_angles = ang;
617 void WarpZone_InitStep_ClearTarget(entity this)
620 this.enemy.enemy = NULL;
624 void WarpZone_InitStep_FindTarget(entity this)
632 // this way only one of the two ents needs to target
633 if(this.target != "")
635 if(!autocvar_sv_warpzone_allow_selftarget)
636 this.enemy = this; // so the if(!e.enemy) check also skips this, saves one IF
639 for(e = NULL, i = 0; (e = find(e, targetname, this.target)); )
641 if(e.classname == this.classname) // possibly non-warpzones may use the same targetname!
642 if(random() * ++i < 1)
647 error("Warpzone with non-existing target");
655 void WarpZone_Think(entity this);
656 void WarpZone_InitStep_FinalizeTransform(entity this)
658 if(!this.enemy || this.enemy.enemy != this)
660 error("Invalid warp zone detected. Killed.");
664 warpzone_warpzones_exist = 1;
665 WarpZone_SetUp(this, this.warpzone_origin, this.warpzone_angles, this.enemy.warpzone_origin, this.enemy.warpzone_angles);
666 settouch(this, WarpZone_Touch);
667 this.SendFlags = 0xFFFFFF;
668 if(this.spawnflags & 1)
670 setthink(this, WarpZone_Think);
671 this.nextthink = time;
677 float warpzone_initialized;
678 //entity warpzone_first;
679 entity warpzone_position_first;
680 entity warpzone_camera_first;
681 .entity warpzone_next;
682 spawnfunc(misc_warpzone_position)
684 // "target", "angles", "origin"
685 this.warpzone_next = warpzone_position_first;
686 warpzone_position_first = this;
688 spawnfunc(trigger_warpzone_position)
690 spawnfunc_misc_warpzone_position(this);
692 spawnfunc(trigger_warpzone)
694 // warp zone entities must have:
695 // "killtarget" pointing to a target_position with a direction arrow
696 // that points AWAY from the warp zone, and that is inside
697 // the warp zone trigger
698 // "target" pointing to an identical warp zone at another place in
699 // the map, with another killtarget to designate its
703 this.scale = this.modelscale;
708 WarpZoneLib_ExactTrigger_Init(this);
712 _setmodel(this, m); // no precision needed
714 setorigin(this, this.origin);
716 setsize(this, this.mins * this.scale, this.maxs * this.scale);
718 setsize(this, this.mins, this.maxs);
719 setSendEntity(this, WarpZone_Send);
720 this.SendFlags = 0xFFFFFF;
721 BITSET_ASSIGN(this.effects, EF_NODEPTHTEST);
722 this.warpzone_next = warpzone_first;
723 warpzone_first = this;
725 IL_PUSH(g_warpzones, this);
727 spawnfunc(func_camera)
730 this.scale = this.modelscale;
735 precache_model(this.model);
736 _setmodel(this, this.model); // no precision needed
738 setorigin(this, this.origin);
740 setsize(this, this.mins * this.scale, this.maxs * this.scale);
742 setsize(this, this.mins, this.maxs);
744 this.solid = SOLID_BSP;
745 else if(this.solid < 0)
746 this.solid = SOLID_NOT;
747 setSendEntity(this, WarpZone_Camera_Send);
748 this.SendFlags = 0xFFFFFF;
749 this.warpzone_next = warpzone_camera_first;
750 warpzone_camera_first = this;
752 void WarpZones_Reconnect()
754 for(entity e = warpzone_first; e; e = e.warpzone_next)
755 WarpZone_InitStep_ClearTarget(e);
756 for(entity e = warpzone_first; e; e = e.warpzone_next)
757 WarpZone_InitStep_FindTarget(e);
758 for(entity e = warpzone_camera_first; e; e = e.warpzone_next)
759 WarpZoneCamera_InitStep_FindTarget(e);
760 for(entity e = warpzone_first; e; e = e.warpzone_next)
761 WarpZone_InitStep_FinalizeTransform(e);
764 void WarpZone_Think(entity this)
766 if(this.warpzone_save_origin != this.origin
767 || this.warpzone_save_angles != this.angles
768 || this.warpzone_save_eorigin != this.enemy.origin
769 || this.warpzone_save_eangles != this.enemy.angles)
771 WarpZone_InitStep_UpdateTransform(this);
772 WarpZone_InitStep_UpdateTransform(this.enemy);
773 WarpZone_InitStep_FinalizeTransform(this);
774 WarpZone_InitStep_FinalizeTransform(this.enemy);
775 this.warpzone_save_origin = this.origin;
776 this.warpzone_save_angles = this.angles;
777 this.warpzone_save_eorigin = this.enemy.origin;
778 this.warpzone_save_eangles = this.enemy.angles;
780 this.nextthink = time;
783 void WarpZone_StartFrame()
785 if (!warpzone_initialized)
787 warpzone_initialized = true;
788 for(entity e = warpzone_first; e; e = e.warpzone_next)
789 WarpZone_InitStep_FindOriginTarget(e);
790 for(entity e = warpzone_position_first; e; e = e.warpzone_next)
791 WarpZonePosition_InitStep_FindTarget(e);
792 for(entity e = warpzone_first; e; e = e.warpzone_next)
793 WarpZone_InitStep_UpdateTransform(e);
794 WarpZones_Reconnect();
795 WarpZone_PostInitialize_Callback();
798 if(warpzone_warpzones_exist)
800 IL_EACH(g_projectiles, true,
802 WarpZone_StoreProjectileData(it);
809 if(warpzone_warpzones_exist)
810 WarpZone_StoreProjectileData(it); // TODO: not actually needed
812 if(IS_OBSERVER(it) || it.solid == SOLID_NOT)
813 if(IS_CLIENT(it)) // we don't care about it being a bot
816 if (warpzone_warpzones_exist) {
817 entity e = WarpZone_Find(it.origin + it.mins, it.origin + it.maxs);
819 if (!WarpZoneLib_ExactTrigger_Touch(e, it))
820 if (WarpZone_PlaneDist(e, it.origin + it.view_ofs) <= 0)
821 WarpZone_Teleport(e, it, -1, 0); // NOT triggering targets by this!
827 entity ent = Teleport_Find(it.origin + it.mins, it.origin + it.maxs);
829 if (!WarpZoneLib_ExactTrigger_Touch(ent, it))
830 Simple_TeleportPlayer(ent, it); // NOT triggering targets by this!
836 .float warpzone_reconnecting;
837 bool visible_to_some_client(entity ent)
839 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it) && checkpvs(it.origin + it.view_ofs, ent),
845 void trigger_warpzone_reconnect_use(entity this, entity actor, entity trigger)
847 // NOTE: this matches for target, not targetname, but of course
848 // targetname must be set too on the other entities
849 for(entity e = warpzone_first; e; e = e.warpzone_next)
850 e.warpzone_reconnecting = ((this.target == "" || e.target == this.target) && !((this.spawnflags & 1) && (visible_to_some_client(e) || visible_to_some_client(e.enemy))));
851 for(entity e = warpzone_camera_first; e; e = e.warpzone_next)
852 e.warpzone_reconnecting = ((this.target == "" || e.target == this.target) && !((this.spawnflags & 1) && visible_to_some_client(e)));
853 for(entity e = warpzone_first; e; e = e.warpzone_next)
854 if(e.warpzone_reconnecting)
855 WarpZone_InitStep_ClearTarget(e);
856 for(entity e = warpzone_first; e; e = e.warpzone_next)
857 if(e.warpzone_reconnecting)
858 WarpZone_InitStep_FindTarget(e);
859 for(entity e = warpzone_camera_first; e; e = e.warpzone_next)
860 if(e.warpzone_reconnecting)
861 WarpZoneCamera_InitStep_FindTarget(e);
862 for(entity e = warpzone_first; e; e = e.warpzone_next)
863 if(e.warpzone_reconnecting || e.enemy.warpzone_reconnecting)
864 WarpZone_InitStep_FinalizeTransform(e);
867 spawnfunc(trigger_warpzone_reconnect)
869 this.use = trigger_warpzone_reconnect_use;
872 spawnfunc(target_warpzone_reconnect)
874 spawnfunc_trigger_warpzone_reconnect(this); // both names make sense here :(
877 void WarpZone_PlayerPhysics_FixVAngle(entity this)
879 #ifndef WARPZONE_DONT_FIX_VANGLE
880 if(IS_REAL_CLIENT(this))
881 if(this.v_angle.z <= 360) // if not already adjusted
882 if(time - CS(this).ping * 0.001 < this.warpzone_teleport_time)
884 this.v_angle = WarpZone_TransformVAngles(this.warpzone_teleport_zone, this.v_angle);
885 this.v_angle_z += 720; // mark as adjusted