1 #define PORTALS_ARE_NOT_SOLID
3 #define SAFENUDGE '1 1 1'
4 #define SAFERNUDGE '8 8 8'
6 .vector portal_transform;
7 .vector portal_safe_origin;
8 .float portal_wants_to_vanish;
9 .float portal_activatetime;
11 float PlayerEdgeDistance(entity p, vector v)
15 if(v_x < 0) vbest_x = p.mins_x; else vbest_x = p.maxs_x;
16 if(v_y < 0) vbest_y = p.mins_y; else vbest_y = p.maxs_y;
17 if(v_z < 0) vbest_z = p.mins_z; else vbest_z = p.maxs_z;
22 vector Portal_ApplyTransformToPlayerAngle(vector transform, vector vangle)
24 vector old_forward, old_up;
25 vector old_yawforward;
26 vector new_forward, new_up;
27 vector new_yawforward;
32 ang_x = bound(-89, mod(-ang_x + 180, 360) - 180, 89);
33 ang = AnglesTransform_Multiply(transform, ang);
36 // PLAYERS use different math
39 //print("reference: ", vtos(AnglesTransform_Multiply(transform, ang)), "\n");
41 fixedmakevectors(ang);
42 old_forward = v_forward;
44 fixedmakevectors(ang_y * '0 1 0');
45 old_yawforward = v_forward;
47 // their aiming directions are portalled...
48 new_forward = AnglesTransform_Apply(transform, old_forward);
49 new_up = AnglesTransform_Apply(transform, old_up);
50 new_yawforward = AnglesTransform_Apply(transform, old_yawforward);
52 // but now find a new sense of direction
54 // assume new_forward points straight up.
57 // new_up could now point forward OR backward... which direction to choose?
59 if(new_forward_z > 0.7 || new_forward_z < -0.7) // far up; in this case, the "up" vector points backwards
61 // new_yawforward and new_yawup define the new aiming half-circle
62 // we "just" need to find out whether new_up or -new_up is in that half circle
63 ang = fixedvectoangles(new_forward); // this still gets us a nice pitch value...
64 if(new_up * new_yawforward < 0)
66 ang_y = vectoyaw(new_up); // this vector is the yaw we want
67 //print("UP/DOWN path: ", vtos(ang), "\n");
71 // good angles; here, "forward" suffices
72 ang = fixedvectoangles(new_forward);
73 //print("GOOD path: ", vtos(ang), "\n");
82 float Portal_TeleportPlayer(entity teleporter, entity player)
84 vector from, to, safe, step, transform, ang, newvel;
85 float planeshift, s, t;
87 if not(teleporter.enemy)
89 backtrace("Portal_TeleportPlayer called without other portal being set. Stop.");
93 from = teleporter.origin;
94 transform = teleporter.portal_transform;
96 to = teleporter.enemy.origin;
97 to = to + AnglesTransform_Apply(teleporter.portal_transform, player.origin - from);
98 newvel = AnglesTransform_Apply(transform, player.velocity);
99 // this now is INSIDE the plane... can't use that
102 fixedmakevectors(teleporter.enemy.angles);
104 // first shift it ON the plane if needed
105 planeshift = ((teleporter.enemy.origin - to) * v_forward) + PlayerEdgeDistance(player, v_forward) + 1;
107 if(planeshift > 0 && (newvel * v_forward) > vlen(newvel) * 0.01)
108 // if we can't, let us not do the planeshift and do somewhat incorrect transformation in the end
109 to += newvel * (planeshift / (newvel * v_forward));
112 to += v_forward * planeshift;
114 s = (to - teleporter.enemy.origin) * v_right;
115 t = (to - teleporter.enemy.origin) * v_up;
116 s = bound(-48, s, 48);
117 t = bound(-48, t, 48);
118 to = teleporter.enemy.origin
119 + ((to - teleporter.enemy.origin) * v_forward) * v_forward
123 safe = teleporter.enemy.portal_safe_origin; // a valid player origin
124 step = to + ((safe - to) * v_forward) * v_forward;
125 tracebox(safe, player.mins - SAFENUDGE, player.maxs + SAFENUDGE, step, MOVE_NOMONSTERS, player);
128 print("'safe' teleport location is not safe!\n");
129 // FAIL TODO why does this happen?
132 safe = trace_endpos + normalize(safe - trace_endpos) * 0;
133 tracebox(safe, player.mins - SAFENUDGE, player.maxs + SAFENUDGE, to, MOVE_NOMONSTERS, player);
136 print("trace_endpos in solid, this can't be!\n");
137 // FAIL TODO why does this happen? (reported by MrBougo)
140 to = trace_endpos + normalize(safe - trace_endpos) * 0;
141 //print(vtos(to), "\n");
143 // ang_x stuff works around weird quake angles
144 if(player.classname == "player")
145 ang = Portal_ApplyTransformToPlayerAngle(transform, player.v_angle);
147 ang = AnglesTransform_Multiply(transform, player.angles);
149 // factor -1 allows chaining portals, but may be weird
150 player.right_vector = -1 * AnglesTransform_Apply(transform, player.right_vector);
152 if(player.flagcarried)
153 DropFlag(player.flagcarried, player, world);
155 if not(teleporter.enemy)
157 backtrace("Portal_TeleportPlayer ended up without other portal being set BEFORE TeleportPlayer. Stop.");
162 TeleportPlayer(teleporter, player, to, ang, newvel, teleporter.enemy.absmin, teleporter.enemy.absmax, TELEPORT_FLAGS_PORTAL);
165 // telefrag within 1 second of portal creation = amazing
166 if(time < teleporter.teleport_time + 1)
167 AnnounceTo(player, "amazing");
170 if not(teleporter.enemy)
172 backtrace("Portal_TeleportPlayer ended up without other portal being set AFTER TeleportPlayer. Stop.");
176 // reset fade counter
177 teleporter.portal_wants_to_vanish = 0;
178 teleporter.fade_time = time + cvar("g_balance_portal_lifetime");
179 teleporter.health = cvar("g_balance_portal_health");
180 teleporter.enemy.health = cvar("g_balance_portal_health");
185 float Portal_FindSafeOrigin(entity portal)
189 portal.mins = PL_MIN - SAFERNUDGE;
190 portal.maxs = PL_MAX + SAFERNUDGE;
191 fixedmakevectors(portal.angles);
192 portal.origin += 16 * v_forward;
193 if(!move_out_of_solid(portal))
196 print("NO SAFE ORIGIN\n");
200 portal.portal_safe_origin = portal.origin;
201 setorigin(portal, o);
205 float Portal_WillHitPlane(vector eorg, vector emins, vector emaxs, vector evel, vector porg, vector pnorm, float psize)
207 float dist, distpersec, delta;
210 dist = (eorg - porg) * pnorm;
211 dist += min(emins_x * pnorm_x, emaxs_x * pnorm_x);
212 dist += min(emins_y * pnorm_y, emaxs_y * pnorm_y);
213 dist += min(emins_z * pnorm_z, emaxs_z * pnorm_z);
214 if(dist < -1) // other side?
216 #ifdef PORTALS_ARE_NOT_SOLID
217 distpersec = evel * pnorm;
218 if(distpersec >= 0) // going away from the portal?
220 // we don't need this check with solid portals, them being SOLID_BSP should suffice
221 delta = dist / distpersec;
222 v = eorg - evel * delta - porg;
223 v = v - pnorm * (pnorm * v);
224 return vlen(v) < psize;
234 #ifdef PORTALS_ARE_NOT_SOLID
235 // portal is being removed?
236 if(self.solid != SOLID_TRIGGER)
237 return; // possibly engine bug
239 if(other.classname == "player")
240 return; // handled by think
243 if(other.classname == "item_flag_team")
244 return; // never portal these
246 if(other.classname == "grapplinghook")
247 return; // handled by think
250 error("Portal_Touch called for a broken portal\n");
252 #ifdef PORTALS_ARE_NOT_SOLID
253 if(trace_fraction < 1)
254 return; // only handle TouchAreaGrid ones (only these can teleport)
256 if(trace_fraction >= 1)
257 return; // only handle impacts
260 if(other.classname == "porto")
262 if(other.portal_id == self.portal_id)
265 if(time < self.portal_activatetime)
266 if(other == self.aiment)
268 self.portal_activatetime = time + 0.1;
271 if(other != self.aiment)
272 if(other.classname == "player")
273 if(IS_INDEPENDENT_PLAYER(other) || IS_INDEPENDENT_PLAYER(self.aiment))
274 return; // cannot go through someone else's portal
275 if(other.aiment != self.aiment)
276 if(other.aiment.classname == "player")
277 if(IS_INDEPENDENT_PLAYER(other.aiment) || IS_INDEPENDENT_PLAYER(self.aiment))
278 return; // cannot go through someone else's portal
279 fixedmakevectors(self.angles);
280 g = frametime * '0 0 -1' * cvar("sv_gravity");
281 if(!Portal_WillHitPlane(other.origin, other.mins, other.maxs, other.velocity + g, self.origin, v_forward, self.maxs_x))
285 if(other.mins_x < PL_MIN_x || other.mins_y < PL_MIN_y || other.mins_z < PL_MIN_z
286 || other.maxs_x > PL_MAX_x || other.maxs_y > PL_MAX_y || other.maxs_z > PL_MAX_z)
288 // can't teleport this
293 if(Portal_TeleportPlayer(self, other))
294 if(other.classname == "porto")
295 if(other.effects & EF_RED)
296 other.effects += EF_BLUE - EF_RED;
300 void Portal_MakeBrokenPortal(entity portal)
303 portal.solid = SOLID_NOT;
304 portal.touch = SUB_Null;
305 portal.think = SUB_Null;
307 portal.nextthink = 0;
308 portal.takedamage = DAMAGE_NO;
311 void Portal_MakeWaitingPortal(entity portal)
314 portal.solid = SOLID_NOT;
315 portal.touch = SUB_Null;
316 portal.think = SUB_Null;
317 portal.effects = EF_ADDITIVE;
318 portal.nextthink = 0;
319 portal.takedamage = DAMAGE_YES;
322 void Portal_MakeInPortal(entity portal)
325 portal.solid = SOLID_NOT; // this is done when connecting them!
326 portal.touch = Portal_Touch;
327 portal.think = Portal_Think;
328 portal.effects = EF_RED;
329 portal.nextthink = time;
330 portal.takedamage = DAMAGE_NO;
333 void Portal_MakeOutPortal(entity portal)
336 portal.solid = SOLID_NOT;
337 portal.touch = SUB_Null;
338 portal.think = SUB_Null;
339 portal.effects = EF_STARDUST | EF_BLUE;
340 portal.nextthink = 0;
341 portal.takedamage = DAMAGE_YES;
344 void Portal_Disconnect(entity teleporter, entity destination)
346 teleporter.enemy = world;
347 destination.enemy = world;
348 Portal_MakeBrokenPortal(teleporter);
349 Portal_MakeBrokenPortal(destination);
352 void Portal_Connect(entity teleporter, entity destination)
354 teleporter.portal_transform = AnglesTransform_Divide(AnglesTransform_TurnDirectionFR(destination.angles), teleporter.angles);
356 teleporter.enemy = destination;
357 destination.enemy = teleporter;
358 Portal_MakeInPortal(teleporter);
359 Portal_MakeOutPortal(destination);
360 teleporter.fade_time = time + cvar("g_balance_portal_lifetime");
361 destination.fade_time = teleporter.fade_time;
362 teleporter.portal_wants_to_vanish = 0;
363 destination.portal_wants_to_vanish = 0;
364 teleporter.teleport_time = time;
365 #ifdef PORTALS_ARE_NOT_SOLID
366 teleporter.solid = SOLID_TRIGGER;
368 teleporter.solid = SOLID_BSP;
372 void Portal_Remove(entity portal, float killed)
379 Portal_Disconnect(portal, e);
380 Portal_Remove(e, killed);
383 if(portal == portal.aiment.portal_in)
384 portal.aiment.portal_in = world;
385 if(portal == portal.aiment.portal_out)
386 portal.aiment.portal_out = world;
387 //portal.aiment = world;
389 // makes the portal vanish
392 fixedmakevectors(portal.angles);
393 sound(portal, CHAN_PROJECTILE, "porto/explode.wav", VOL_BASE, ATTN_NORM);
394 pointparticles(particleeffectnum("rocket_explode"), portal.origin + v_forward * 16, v_forward * 1024, 4);
399 Portal_MakeBrokenPortal(portal);
400 sound(portal, CHAN_PROJECTILE, "porto/expire.wav", VOL_BASE, ATTN_NORM);
401 SUB_SetFade(portal, time, 0.5);
405 void Portal_Damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
407 if(deathtype == DEATH_TELEFRAG)
409 if(attacker != self.aiment)
410 if(IS_INDEPENDENT_PLAYER(attacker) || IS_INDEPENDENT_PLAYER(self.aiment))
412 self.health -= damage;
414 Portal_Remove(self, 1);
417 void Portal_Think_TryTeleportPlayer(entity e, vector g)
419 if(!Portal_WillHitPlane(e.origin, e.mins, e.maxs, e.velocity + g, self.origin, v_forward, self.maxs_x))
422 // if e would hit the portal in a frame...
423 // already teleport him
424 tracebox(e.origin, e.mins, e.maxs, e.origin + e.velocity * 2 * frametime, MOVE_NORMAL, e);
425 if(trace_ent == self)
426 Portal_TeleportPlayer(self, e);
434 #ifdef PORTALS_ARE_NOT_SOLID
435 // portal is being removed?
436 if(self.solid != SOLID_TRIGGER)
437 return; // possibly engine bug
440 error("Portal_Think called for a broken portal\n");
443 self.solid = SOLID_BBOX;
446 g = frametime * '0 0 -1' * cvar("sv_gravity");
448 fixedmakevectors(self.angles);
453 if(IS_INDEPENDENT_PLAYER(e) || IS_INDEPENDENT_PLAYER(o))
454 continue; // cannot go through someone else's portal
456 if(e != o || time >= self.portal_activatetime)
457 Portal_Think_TryTeleportPlayer(e, g);
460 Portal_Think_TryTeleportPlayer(e.hook, g);
462 self.solid = SOLID_TRIGGER;
466 self.nextthink = time;
468 if(time > self.fade_time)
469 Portal_Remove(self, 0);
472 float Portal_Customize()
474 if(other.classname == "spectator")
476 if(other == self.aiment)
478 self.modelindex = self.modelindex_lod0;
480 else if(IS_INDEPENDENT_PLAYER(other) || IS_INDEPENDENT_PLAYER(self.aiment))
486 self.modelindex = self.modelindex_lod0;
492 // when creating in-portal:
494 // clear existing in-portal
497 // when creating out-portal:
499 // clear existing out-portal
502 // disconnect portals
503 // clear both portals
504 // after timeout of in-portal:
505 // disconnect portals
506 // clear both portals
507 // TODO: ensure only one portal shot at once
508 float Portal_SetInPortal(entity own, entity portal)
513 Portal_Disconnect(own.portal_in, own.portal_out);
514 Portal_Remove(own.portal_in, 0);
516 own.portal_in = portal;
519 own.portal_out.portal_id = portal.portal_id;
520 Portal_Connect(own.portal_in, own.portal_out);
524 float Portal_SetOutPortal(entity own, entity portal)
529 Portal_Disconnect(own.portal_in, own.portal_out);
530 Portal_Remove(own.portal_out, 0);
532 own.portal_out = portal;
535 own.portal_in.portal_id = portal.portal_id;
536 Portal_Connect(own.portal_in, own.portal_out);
540 void Portal_ClearAll_PortalsOnly(entity own)
543 Portal_Remove(own.portal_in, 0);
545 Portal_Remove(own.portal_out, 0);
547 void Portal_ClearAll(entity own)
549 Portal_ClearAll_PortalsOnly(own);
552 void Portal_RemoveLater_Think()
554 Portal_Remove(self, self.cnt);
556 void Portal_RemoveLater(entity portal, float kill)
558 Portal_MakeBrokenPortal(portal);
560 portal.think = Portal_RemoveLater_Think;
561 portal.nextthink = time;
563 void Portal_ClearAllLater_PortalsOnly(entity own)
566 Portal_RemoveLater(own.portal_in, 0);
568 Portal_RemoveLater(own.portal_out, 0);
570 void Portal_ClearAllLater(entity own)
572 Portal_ClearAllLater_PortalsOnly(own);
575 void Portal_ClearWithID(entity own, float id)
578 if(own.portal_in.portal_id == id)
581 Portal_Disconnect(own.portal_in, own.portal_out);
582 Portal_Remove(own.portal_in, 0);
585 if(own.portal_out.portal_id == id)
588 Portal_Disconnect(own.portal_in, own.portal_out);
589 Portal_Remove(own.portal_out, 0);
593 entity Portal_Spawn(entity own, vector org, vector ang)
597 fixedmakevectors(ang);
598 if(!CheckWireframeBox(own, org - 48 * v_right - 48 * v_up + 16 * v_forward, 96 * v_right, 96 * v_up, 96 * v_forward))
602 portal.classname = "portal";
604 setorigin(portal, org);
606 portal.think = Portal_Think;
607 portal.nextthink = 0;
608 portal.portal_activatetime = time + 0.1;
609 portal.takedamage = DAMAGE_AIM;
610 portal.event_damage = Portal_Damage;
611 portal.fade_time = time + cvar("g_balance_portal_lifetime");
612 portal.health = cvar("g_balance_portal_health");
613 setmodel(portal, "models/portal.md3");
614 portal.modelindex_lod0 = portal.modelindex;
615 portal.customizeentityforclient = Portal_Customize;
617 if(!Portal_FindSafeOrigin(portal))
623 setsize(portal, '-48 -48 -48', '48 48 48');
624 Portal_MakeWaitingPortal(portal);
629 float Portal_SpawnInPortalAtTrace(entity own, vector dir, float portal_id_val)
636 ang = fixedvectoangles2(trace_plane_normal, dir);
637 fixedmakevectors(ang);
639 portal = Portal_Spawn(own, org, ang);
642 // if(!own.portal_out || own.portal_out.portal_id == portal_id_val)
643 Portal_ClearAll_PortalsOnly(own);
647 portal.portal_id = portal_id_val;
648 Portal_SetInPortal(own, portal);
653 float Portal_SpawnOutPortalAtTrace(entity own, vector dir, float portal_id_val)
660 ang = fixedvectoangles2(trace_plane_normal, dir);
661 fixedmakevectors(ang);
663 portal = Portal_Spawn(own, org, ang);
666 // if(!own.portal_in || own.portal_in.portal_id == portal_id_val)
667 Portal_ClearAll_PortalsOnly(own);
671 portal.portal_id = portal_id_val;
672 Portal_SetOutPortal(own, portal);