Files
DoomRTX/neo/prey/Prey/game_portal.cpp
T
2026-05-05 15:01:01 -07:00

1169 lines
37 KiB
C++

//**************************************************************************
//**
//** GAME_PORTAL.CPP
//**
//** Game code for Prey-specific portals
//**
//**************************************************************************
// HEADER FILES ------------------------------------------------------------
#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
#define MP_PORTAL_RANGE_DEFAULT 356.0f //rww - was 512, and then it was 256, and now it is 356
#define MAX_PORTAL_BOUNDS 256.0f //used for unchanging bounds in mp
//==========================================================================
//
// hhArtificialPortal
//
//==========================================================================
const idEventDef EV_SetGamePortalState("setPortalState", "dd");
#if GAMEPORTAL_PVS
CLASS_DECLARATION(idEntity, hhArtificialPortal)
EVENT( EV_SetGamePortalState, hhArtificialPortal::Event_SetPortalState )
END_CLASS
void hhArtificialPortal::Spawn() {
areaPortal = gameRenderWorld->FindGamePortal( GetName() );
if (areaPortal && !gameLocal.isClient) {
SetPortalState(spawnArgs.GetBool("startOpenPVS"), spawnArgs.GetBool("startOpenSound"));
}
fl.networkSync = true;
}
void hhArtificialPortal::SetPortalState(bool openPVS, bool openSound) {
if (areaPortal && !gameLocal.isClient) {
int blockMask = (openPVS ? PS_BLOCK_NONE : PS_BLOCK_VIEW) | (openSound ? PS_BLOCK_NONE : PS_BLOCK_SOUND);
gameLocal.SetPortalState( areaPortal, blockMask );
}
}
void hhArtificialPortal::Event_SetPortalState(bool openPVS, bool openSound) {
SetPortalState(openPVS, openSound);
}
void hhArtificialPortal::Save(idSaveGame *savefile) const {
savefile->WriteInt(areaPortal);
if ( areaPortal ) {
savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) );
}
}
void hhArtificialPortal::Restore(idRestoreGame *savefile) {
savefile->ReadInt(areaPortal);
if ( areaPortal ) {
int portalState;
savefile->ReadInt( portalState );
gameLocal.SetPortalState( areaPortal, portalState );
}
}
#endif
//==========================================================================
//
// hhPortal
//
//==========================================================================
const idEventDef EV_Opened("<portalopened>", NULL);
const idEventDef EV_Closed("<portalclosed>", NULL);
const idEventDef EV_PortalSpark("<portalSpark>", NULL );
const idEventDef EV_PortalSparkEnd("<portalSparkEnd>", NULL );
const idEventDef EV_ShowGlowPortal( "showGlowPortal", NULL );
const idEventDef EV_HideGlowPortal( "hideGlowPortal", NULL );
CLASS_DECLARATION(hhAnimatedEntity, hhPortal)
EVENT( EV_PostSpawn, hhPortal::PostSpawn )
EVENT( EV_Activate, hhPortal::Event_Trigger)
EVENT( EV_Opened, hhPortal::Event_Opened )
EVENT( EV_Closed, hhPortal::Event_Closed )
EVENT( EV_ResetGravity, hhPortal::Event_ResetGravity )
EVENT( EV_PortalSpark, hhPortal::Event_PortalSpark )
EVENT( EV_PortalSparkEnd, hhPortal::Event_PortalSparkEnd )
EVENT( EV_ShowGlowPortal, hhPortal::Event_ShowGlowPortal )
EVENT( EV_HideGlowPortal, hhPortal::Event_HideGlowPortal )
END_CLASS
hhPortal::hhPortal(void) {
#if GAMEPORTAL_PVS
areaPortal = 0;
#endif
}
hhPortal::~hhPortal() {
proximityEntities.Clear(); // Clear the list of potential entities to be portalled
SAFE_REMOVE(m_portalIdleFx);
#if GAMEPORTAL_PVS
if (areaPortal && !gameLocal.isClient) {
gameLocal.SetPortalState( areaPortal, PS_BLOCK_ALL );
}
#endif
}
void hhPortal::Spawn(void) {
idBounds bounds;
fl.clientEvents = true;
bNoTeleport = spawnArgs.GetBool( "noTeleport");
bGlowPortal = spawnArgs.GetBool( "glowPortal"); // This is a glow portal, and so will have fx, sound, particles, etc
closeDelay = spawnArgs.GetFloat( "closeDelay");
monsterportal = spawnArgs.GetBool( "monsterportal" );
distanceToggle = spawnArgs.GetFloat( "distanceToggle" );
if (gameLocal.isMultiplayer && bGlowPortal/* && distanceToggle == 0.0f*/) { //mp has a default shutoff distance for glow portals.
//now ignoring distance toggle keys in mp, and forcing it.
distanceToggle = MP_PORTAL_RANGE_DEFAULT;
}
if (!spawnArgs.GetFloat( "distanceCull", "0.0", distanceCull )) { //rww - distance to shut off the vis gameportal but not the render portal
//if not value specified, default to shaderParm5+4
if (!renderEntity.shaderParms[5]) {
distanceCull = 0.0f;
}
else {
distanceCull = renderEntity.shaderParms[5]+4.0f;
}
}
areaPortalCulling = false;
if ( !gameLocal.isMultiplayer && bGlowPortal ) {
alertMonsters = spawnArgs.GetBool( "alertMonsters", "0" );
} else {
alertMonsters = false;
}
if ( bNoTeleport ) {
GetPhysics()->SetContents( 0 );
} else {
GetPhysics()->SetContents( CONTENTS_SOLID );
}
// Setup the initial state for the portal
if(spawnArgs.GetBool("startActive")) { // Start Active
portalState = PORTAL_OPENED;
if ( bGlowPortal ) {
int anim = GetAnimator()->GetAnim("opened");
GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, 0 );
StartSound( "snd_loop", SND_CHANNEL_ANY );
PostEventSec( &EV_PortalSpark, gameLocal.random.RandomFloat() );
SetSkinByName( spawnArgs.GetString( "skin" ) );
}
} else { // Start closed
Hide();
portalState = PORTAL_CLOSED;
GetPhysics()->SetContents( 0 ); //rww - if closed, ensure things will not hit and go through
}
#if GAMEPORTAL_PVS
const char *gamePortalName = spawnArgs.GetString("gamePortalName", GetName());
areaPortal = gameRenderWorld->FindGamePortal( gamePortalName );
if (areaPortal && !gameLocal.isClient) {
gameLocal.SetPortalState( areaPortal, portalState == PORTAL_CLOSED ? PS_BLOCK_ALL : PS_BLOCK_NONE );
}
#endif
// Default to normal gravity. This could be changed by any zones the portal is within
SetGravity( gameLocal.GetGravity() );
BecomeActive( TH_THINK | TH_UPDATEVISUALS | TH_ANIMATE );
UpdateVisuals();
PostEventMS( &EV_PostSpawn, 0 );
fl.networkSync = true; //rww
if (gameLocal.isMultiplayer) { //rww - if portals grab their renderEntity bounds from the animation, they can cause client-server pvs discrepancies
renderEntity.bounds[0] = idVec3(-MAX_PORTAL_BOUNDS, -MAX_PORTAL_BOUNDS, -MAX_PORTAL_BOUNDS);
renderEntity.bounds[1] = idVec3(MAX_PORTAL_BOUNDS, MAX_PORTAL_BOUNDS, MAX_PORTAL_BOUNDS);
}
proximityEntities.Clear(); // Clear the list of potential entities to be portalled
}
void hhPortal::PostSpawn( void ) {
CheckForBuddy();
}
void hhPortal::Save(idSaveGame *savefile) const {
#if GAMEPORTAL_PVS
savefile->WriteInt( areaPortal );
if ( areaPortal ) {
savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) );
}
#endif
savefile->WriteInt( portalState );
savefile->WriteBool( bNoTeleport );
savefile->WriteBool( bGlowPortal );
savefile->WriteVec3( portalGravity );
savefile->WriteFloat( closeDelay );
savefile->WriteBool( monsterportal );
savefile->WriteBool( alertMonsters );
savefile->WriteInt( slavePortals.Num() ); // idList<idEntityPtr<hhPortal> >
for (int i=0; i<slavePortals.Num(); i++) {
slavePortals[i].Save(savefile);
}
masterPortal.Save(savefile);
savefile->WriteFloat(distanceToggle);
savefile->WriteFloat(distanceCull);
savefile->WriteBool(areaPortalCulling);
savefile->WriteInt( proximityEntities.Num() );
for( int i = 0; i < proximityEntities.Num(); i++ ) {
proximityEntities[i].entity.Save( savefile );
savefile->WriteVec3( proximityEntities[i].lastPortalPoint );
}
m_portalIdleFx.Save(savefile);
}
void hhPortal::Restore( idRestoreGame *savefile ) {
int i, num;
#if GAMEPORTAL_PVS
savefile->ReadInt( areaPortal );
if ( areaPortal ) {
int portalState;
savefile->ReadInt( portalState );
gameLocal.SetPortalState( areaPortal, portalState );
}
#endif
savefile->ReadInt( (int &)portalState );
savefile->ReadBool( bNoTeleport );
savefile->ReadBool( bGlowPortal );
savefile->ReadVec3( portalGravity );
savefile->ReadFloat( closeDelay );
savefile->ReadBool( monsterportal );
savefile->ReadBool( alertMonsters );
slavePortals.Clear();
savefile->ReadInt( num ); // idList<idEntityPtr<hhPortal> >
slavePortals.SetNum( num );
for (i=0; i<num; i++) {
slavePortals[i].Restore(savefile);
}
masterPortal.Restore(savefile);
savefile->ReadFloat(distanceToggle);
savefile->ReadFloat(distanceCull);
savefile->ReadBool(areaPortalCulling);
savefile->ReadInt( num );
proximityEntities.SetNum( num );
for( i = 0; i < num; i++ ) {
proximityEntities[i].entity.Restore( savefile );
savefile->ReadVec3( proximityEntities[i].lastPortalPoint );
}
m_portalIdleFx.Restore(savefile);
}
void hhPortal::WriteToSnapshot( idBitMsgDelta &msg ) const {
msg.WriteFloat(portalGravity.x);
msg.WriteFloat(portalGravity.y);
msg.WriteFloat(portalGravity.z);
msg.WriteBits(masterPortal.GetSpawnId(), 32);
msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_MODE]);
msg.WriteFloat(renderEntity.shaderParms[SHADERPARM_TIMEOFFSET]);
msg.WriteBits(portalState, 4);
}
void hhPortal::ReadFromSnapshot( const idBitMsgDelta &msg ) {
portalGravity.x = msg.ReadFloat();
portalGravity.y = msg.ReadFloat();
portalGravity.z = msg.ReadFloat();
masterPortal.SetSpawnId(msg.ReadBits(32));
renderEntity.shaderParms[SHADERPARM_MODE] = msg.ReadFloat();
renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = msg.ReadFloat();
portalStates_t newPortalState = (portalStates_t)msg.ReadBits(4);
if ((newPortalState == PORTAL_CLOSED || newPortalState == PORTAL_OPENED) &&
newPortalState != portalState && portalState != PORTAL_CLOSING && portalState != PORTAL_OPENING) {
Trigger(this);
}
}
void hhPortal::ClientPredictionThink( void ) {
Think();
}
void hhPortal::CheckPlayerDistances(void) {
float closest = distanceToggle+distanceCull;
hhPortal *targetPortal = NULL;
if (cameraTarget && cameraTarget->IsType(hhPortal::Type)) { //we want to measure distance from the target portal too if we have one
targetPortal = static_cast<hhPortal *>(cameraTarget);
}
for (int i = 0; i < gameLocal.numClients; i++) { //loop through any active clients and get the closest distance to one.
idEntity *ent = gameLocal.entities[i];
if (ent && ent->IsType(hhPlayer::Type)) {
hhPlayer *pl = static_cast<hhPlayer *>(ent);
if (pl->health > 0 && !pl->spectating && !pl->InVehicle()) { //don't open for spectators or dead players or players in vehicles
float d = (pl->GetOrigin()-GetOrigin()).Length();
if (d < closest) {
closest = d;
}
if (targetPortal) { //check distance from the target portal
d = (pl->GetOrigin()-targetPortal->GetOrigin()).Length();
if (d < closest) {
closest = d;
}
}
}
}
}
if (distanceToggle != 0.0f) { //toggling portal based on distance of player
if (closest < distanceToggle) { //should be open
if (portalState == PORTAL_CLOSED) {
Trigger(this);
}
}
else { //should be closed
if (portalState == PORTAL_OPENED) {
Trigger(this);
}
}
}
#if GAMEPORTAL_PVS // jmarshall - added
if (distanceCull != 0.0f) { //toggling only area portal based on distance of player
if (closest >= distanceCull) { //should be closed
if (!areaPortalCulling) {
areaPortalCulling = true;
if (!gameLocal.isClient) {
gameLocal.SetPortalState( areaPortal, PS_BLOCK_ALL );
}
}
}
else if (areaPortalCulling && (portalState == PORTAL_OPENING || portalState == PORTAL_OPENED)) { //otherwise make sure it's on (if the portal is open)
if (!gameLocal.isClient) {
gameLocal.SetPortalState( areaPortal, PS_BLOCK_NONE );
}
areaPortalCulling = false;
}
}
# endif
}
//==========================================================================
//
// hhPortal::Think
//
//==========================================================================
#define NEAR_CLIP 0 //6.5
void hhPortal::Think( void ) {
int i;
idEntity *hit;
idPlane plane;
// jmarshall - portal.
if ((distanceCull != 0.0f || distanceToggle != 0.0f) && /*areaPortal*/ 0) { //check to turn the areaportal on and off based on distance.
if (!gameLocal.isClient) {
CheckPlayerDistances();
}
else { //since this is not sync'd, and we don't need to sync it, do an extra check here (for mp)
if (portalState == PORTAL_OPENED && renderEntity.customSkin && renderEntity.customSkin == declManager->FindSkin(spawnArgs.GetString( "skin_onlyWarp" ))) {
SetSkinByName( spawnArgs.GetString( "skin" ) );
}
}
if (distanceToggle != 0.0f) { //for pop-open portals, check fx state
if ((portalState == PORTAL_CLOSED || portalState == PORTAL_CLOSING) && bGlowPortal) {
//rww - broadcast fx while closed
if (!m_portalIdleFx.IsValid()) {
const char *portalIdleFx = spawnArgs.GetString("fx_idleclosed", "fx/portal_closed_idle");
if (portalIdleFx[0]) {
hhFxInfo fxInfo;
fxInfo.SetNormal( GetAxis()[2] );
fxInfo.RemoveWhenDone(false);
m_portalIdleFx = SpawnFxLocal(portalIdleFx, GetOrigin(), GetAxis(), &fxInfo, true);
if (!m_portalIdleFx.IsValid()) { //spawn failure?
gameLocal.Warning("hhPortal::Think: portal could not spawn fx for fx_idleclosed (%s).", portalIdleFx);
}
}
}
if (m_portalIdleFx.IsValid() && !m_portalIdleFx->IsActive(TH_THINK)) {
m_portalIdleFx->Nozzle(true);
}
}
else if (m_portalIdleFx.IsValid() && m_portalIdleFx->IsActive(TH_THINK)) { //if it's not closed/closing and has fx, then stop them
m_portalIdleFx->Nozzle(false);
}
}
}
hhAnimatedEntity::Think();
if (gameLocal.isMultiplayer) { //rww - if portals grab their renderEntity bounds from the animation, they can cause client-server pvs discrepancies
renderEntity.bounds[0] = idVec3(-MAX_PORTAL_BOUNDS, -MAX_PORTAL_BOUNDS, -MAX_PORTAL_BOUNDS);
renderEntity.bounds[1] = idVec3(MAX_PORTAL_BOUNDS, MAX_PORTAL_BOUNDS, MAX_PORTAL_BOUNDS);
}
if( portalState == PORTAL_CLOSED || bNoTeleport ) {
return;
}
// Force visuals to update until a remote renderview has been created for this portal
if ( !renderEntity.remoteRenderView ) {
BecomeActive( TH_UPDATEVISUALS );
}
// Bit of a hack for noclipping players: If they are close to the portal, then add them to the proximity list automatically
idPlayer *player = gameLocal.GetLocalPlayer();
if ( player && player->noclip ) { //rww - note that the local player is NULL for dedicated servers.
if ( (player->GetOrigin() - GetOrigin()).LengthFast() < 256.0f ) {
AddProximityEntity( player );
}
}
// Build a plane for the portal surface
plane.SetNormal(GetPhysics()->GetAxis()[0]);
plane.FitThroughPoint( GetPhysics()->GetOrigin() + plane.Normal() * NEAR_CLIP );
idVec3 origin = GetOrigin();
idMat3 axis = GetAxis();
for ( i = 0; i < proximityEntities.Num(); i++ ) {
if ( !proximityEntities[i].entity.IsValid() ) {
// Remove this entity from the list
proximityEntities.RemoveIndex( i );
continue;
}
hit = proximityEntities[i].entity.GetEntity();
idVec3 location = proximityEntities[i].lastPortalPoint;
idVec3 nextLocation = hit->GetPortalPoint();
proximityEntities[i].lastPortalPoint = nextLocation;
if ( !AttemptPortal( plane, hit, location, nextLocation ) ) {
proximityEntities.RemoveIndex( i );
// If the entity is a player, then inform the player that they are no longer close to a portal
if ( hit->IsType( hhPlayer::Type ) ) {
hhPlayer *player = static_cast<hhPlayer *>(hit);
player->SetPortalColliding( false );
}
}
}
}
bool hhPortal::AttemptPortal( idPlane &plane, idEntity *hit, idVec3 location, idVec3 nextLocation ) {
// Don't try to portal self
if( hit == this ) {
return false;
}
if ( hit->IsBound() ) { // Do not portal bound objects -- let the master portalling handle bound entities
return false;
}
// Don't allow idMovers to portal. TODO: Restrict other entities?
if ( hit->IsType( idMover::Type ) || hit->IsType(hhVehicle::Type) ) { //rww - do not allow shuttles either
return false;
}
if (hit->IsType(hhPlayer::Type)) { //rww - don't portal dead players
hhPlayer *pl = static_cast<hhPlayer *>(hit);
if (pl->health <= 0) {
return false;
}
// Check if the player is intersecting this portal. If not, then don't try to portal it
if ( !GetPhysics()->GetAbsBounds().IntersectsBounds( pl->GetPhysics()->GetAbsBounds() ) ) {
return false;
}
}
int side = plane.Side( location );
if ( side == PLANESIDE_ON || side == PLANESIDE_CROSS ) {
side = PLANESIDE_BACK;
}
int nextSide = plane.Side( nextLocation );
if ( nextSide == PLANESIDE_ON || nextSide == PLANESIDE_CROSS ) {
nextSide = PLANESIDE_BACK;
}
if( side == PLANESIDE_BACK ) { // On the backside, remove this entity from the list
return false;
} else if ( side == nextSide ) { // Entirely on one side
// Check if the entity is too far from the plane and remove it from the list
if ( !GetPhysics()->GetAbsBounds().IntersectsBounds( hit->GetPhysics()->GetAbsBounds() ) ) {
return false;
}
return true;
}
// Compute the location on the plane where the entity would hit
float scale;
idVec3 dir = nextLocation - location;
plane.RayIntersection( location, dir, scale );
// Portal the entity
PortalEntity( hit, location + dir * scale * 1.01f );
// Add this entity to the destination portal's proximityEntity list
if (cameraTarget && cameraTarget->IsType(hhPortal::Type)) {
hhPortal *targetPortal = static_cast<hhPortal *>(cameraTarget);
targetPortal->CollideWithPortal( hit ); // CJR PCF 04/26/06: Previously CheckPortal
}
return false;
}
void hhPortal::PortalProjectile( hhProjectile *projectile, idVec3 collideLocation, idVec3 nextLocation ) {
idPlane plane;
plane.SetNormal(GetPhysics()->GetAxis()[0]);
plane.FitThroughPoint( GetPhysics()->GetOrigin() );
AttemptPortal( plane, projectile, collideLocation, nextLocation );
}
//==========================================================================
//==========================================================================
void hhPortal::CheckForBuddy() {
const char * buddyName;
idEntity * foundEntity;
hhPortal* buddy;
// Check for a buddy portal name
buddyName = spawnArgs.GetString( "partner", NULL );
if ( buddyName ) {
gameLocal.Warning( "Portal %s has 'partner' key. Please change this to 'buddy'", GetName() );
}
else {
buddyName = spawnArgs.GetString( "buddy" );
}
if ( !buddyName || !buddyName[ 0 ] ) {
return;
}
// Get the actual entity
foundEntity = gameLocal.FindEntity( buddyName );
if ( !foundEntity ) {
return;
}
if ( foundEntity->IsType( hhPortal::Type ) ) {
buddy = (hhPortal *) foundEntity;
if (buddy->spawnArgs.FindKey("buddy")) {
gameLocal.Error( "Portals %s and %s both have 'buddy' key set. Use only on master", GetName(), buddy->GetName() );
return;
}
// Don't link up to buddy if a monster portal, just points to portal room
if ( !monsterportal || closeDelay == 0.0f ) {
//? OK, let's be lazy, assume only 2 portals are buddied. Make us the master
buddy->SetMasterPortal( this );
//! Set the cameraTargets of each, then update each
buddy->spawnArgs.Set( "cameraTarget", name.c_str() );
buddy->ProcessEvent( &EV_UpdateCameraTarget );
}
AddSlavePortal( buddy );
}
spawnArgs.Set( "cameraTarget", buddyName );
ProcessEvent( &EV_UpdateCameraTarget );
}
//=============================
// hhPortal::TriggerTargets
//=============================
void hhPortal::TriggerTargets() {
const char * key = "";
idEntity * entity = NULL;
idList< idStr > entityNames;
// Find the right key based on our state
if ( portalState == PORTAL_OPENING ) {
key = "targetOpening";
}
else if ( portalState == PORTAL_OPENED ) {
key = "targetOpened";
}
else if ( portalState == PORTAL_CLOSING ) {
key = "targetClosing";
}
else if ( portalState == PORTAL_CLOSED ) {
key = "targetClosed";
}
//gameLocal.Printf( "Keying %s\n", key );
// Loop through the entities, spawning each one
hhUtils::GetValues( spawnArgs, key, entityNames, true );
for ( int i = 0; i < entityNames.Num(); ++i ) {
//gameLocal.Printf( "Gonna trigger %s\n", entityNames[ i ].c_str() );
entity = gameLocal.FindEntity( entityNames[ i ] );
if ( entity ) {
entity->PostEventMS( &EV_Activate, 0, this );
}
}
}
//==========================================================================
//
// hhPortal::CheckPortal
//
// If this entity can be portaled, typically called from the low-level physics clip functions
//
// CJR PCF 04/26/06: Changed this function to separate CheckPortal and CollideWithPortal
//==========================================================================
bool hhPortal::CheckPortal( const idEntity *other, int contentMask ) {
if ( contentMask & CONTENTS_GAME_PORTAL ) { // Check if the other entity should clip against the portal
return false;
}
if ( !other ) {
return true;
}
if ( other->fl.noPortal ) {
return false; // Do not allow this entity to portal, make it collide with the portal instead
}
return true;
}
bool hhPortal::CheckPortal( const idClipModel *mdl, int contentMask ) {
if ( contentMask & CONTENTS_GAME_PORTAL ) { // Check if the other entity should clip against the portal
return false;
}
if ( !mdl ) {
return true;
}
return CheckPortal( mdl->GetEntity(), contentMask );
}
//==========================================================================
//
// hhPortal::CollideWithPortal
//
// Called from low-level clip functions, actually collide the entity with the portal
//
// CJR PCF 04/26/06: Formerly part of CheckPortal
//==========================================================================
void hhPortal::CollideWithPortal( const idEntity *other ) {
if ( !other ) {
return;
}
if ( other->IsType( hhProjectile::Type ) ) { // Projectiles move so fast that they should get portaled next time they think
// Only add this projectile to the collided list if it hits the front side of the portal
idPlane plane;
plane.SetNormal(GetPhysics()->GetAxis()[0]);
plane.FitThroughPoint( GetPhysics()->GetOrigin() );
int side = plane.Side( other->GetOrigin() );
if ( side == PLANESIDE_FRONT ) { // It's on the front, so add this portal to the list
hhProjectile *projectile = (hhProjectile *)(other);
// Check if the projectile will actually impact the portal
idVec3 end = projectile->GetOrigin() + projectile->GetPhysics()->GetLinearVelocity();
if ( plane.LineIntersection( projectile->GetOrigin(), end ) ) { // Projectile collides with the portal
projectile->SetCollidedPortal( this, projectile->GetPortalPoint(), projectile->GetPhysics()->GetLinearVelocity() );
}
}
} else {
AddProximityEntity( other );
}
}
void hhPortal::CollideWithPortal( const idClipModel *mdl ) {
if ( !mdl ) {
return;
}
CollideWithPortal( mdl->GetEntity() );
}
//==========================================================================
//
// hhPortal::AddProximityEntity
//
// Saves this entity on a list, and will check if it can be portalled
// the next time the portal thinks
//==========================================================================
void hhPortal::AddProximityEntity( const idEntity *other) {
// Go through the list and guarantee that this entity isn't in multiple times
// note: cannot use IdList::AddUnique, because the lastPortalPoint might be different during this add
for( int i = 0; i < proximityEntities.Num(); i++ ) {
if ( proximityEntities[i].entity.GetEntity() == other ) {
return;
}
}
// Add this entity to the potential portal list
proximityEntity_t prox;
prox.entity = other;
prox.lastPortalPoint = ((idEntity *)(other))->GetPortalPoint();
proximityEntities.Append( prox );
// If the entity is a player, then inform the player that they are close to this portal
// needed for weapon projectile firing
if ( other->IsType( hhPlayer::Type ) ) {
hhPlayer *player = (hhPlayer *)(other);
player->SetPortalColliding( true );
}
}
//==========================================================================
//
// hhPortal::PortalEntity
//
// To rotate a vector from one portal space into another:
// transform vector into Source Portal Space (mul by axis transpose)
// flip X & Y (only flip X for angles, not for position)
// transform vector into Destination Portal Space
//==========================================================================
void PortalRotate( idVec3 &vec, const idMat3 &sourceTranspose, const idMat3 &dest, const bool flipX ) {
vec *= sourceTranspose;
vec.y *= -1;
if ( flipX ) {
vec.x *= -1;
}
vec *= dest;
}
bool hhPortal::PortalEntity( idEntity *ent, const idVec3 &point ) {
idMat3 sourceAxis;
idMat3 destAxis;
idMat3 newEntAxis;
if ( !ent ) {
return(false);
}
if ( cameraTarget ) {
sourceAxis = GetAxis().Transpose();
destAxis = cameraTarget->GetAxis();
// Compute new location
idVec3 newLocation = point - GetOrigin();
PortalRotate( newLocation, sourceAxis, destAxis, false );
newLocation += cameraTarget->GetOrigin();
// Compute new axis
newEntAxis = ent->GetAxis();
// Rotate the vector into new portal space
PortalRotate( newEntAxis[0], sourceAxis, destAxis, true );
PortalRotate( newEntAxis[1], sourceAxis, destAxis, true );
PortalRotate( newEntAxis[2], sourceAxis, destAxis, true );
// Actually attempt to portal the entity
if ( PortalTeleport( ent, newLocation, newEntAxis, sourceAxis, destAxis ) ) {
if ( alertMonsters && !gameLocal.isMultiplayer && ent->IsType( idPlayer::Type ) ) {
gameLocal.SendMessageAI( this, GetOrigin(), 2000, MA_EnemyPortal );
}
ent->Portalled( this ); // Inform the actor that it was just portalled
return(true); // Portal succeeded
}
}
return(false); // Portal failed
}
//==========================================================================
//
// hhPortal::PortalTeleport
//
//==========================================================================
bool hhPortal::PortalTeleport( idEntity *ent, const idVec3 &origin, const idMat3 &axis, const idMat3 &sourceAxis, const idMat3 &destAxis ) {
idClipModel *clip = ent->GetPhysics()->GetClipModel();
if ( !clip ) {
return false;
}
// Properly set velocity relative to the new portal
idVec3 vel = ent->GetPhysics()->GetLinearVelocity();
PortalRotate( vel, sourceAxis, destAxis, true );
ent->GetPhysics()->SetLinearVelocity( vel );
//rww - check if this new orientation is going to be in solid or not. if it is, try displacing the new origin based on the portal
idVec3 useOrigin = origin;
trace_t transCheck;
if (gameLocal.clip.TranslationWithExceptions(transCheck, useOrigin, useOrigin, NULL, clip, axis, ent->GetPhysics()->GetClipMask(), ent)) {
if (cameraTarget) {
bool safeSpot = true;
const float distExtrusion = 2.0f;
const float heightAdjust = (ent->GetPhysics()->GetBounds()[1].z-fabsf(ent->GetPhysics()->GetBounds()[0].z))/2.0f;
idVec3 testOrigin = cameraTarget->GetOrigin();
testOrigin += cameraTarget->GetAxis()[0]*distExtrusion;
testOrigin -= cameraTarget->GetAxis()[2]*heightAdjust;
if (gameLocal.clip.TranslationWithExceptions(transCheck, testOrigin, testOrigin, NULL, clip, axis, ent->GetPhysics()->GetClipMask(), ent)) {
testOrigin += cameraTarget->GetAxis()[2]*(heightAdjust*2.0f); //then try going up further (could be upside-down or something)
if (gameLocal.clip.TranslationWithExceptions(transCheck, testOrigin, testOrigin, NULL, clip, axis, ent->GetPhysics()->GetClipMask(), ent)) {
//damn, this portal is really busted.
safeSpot = false;
}
}
if (safeSpot) {
useOrigin = testOrigin; //got a safe spot
if (ent->IsType(hhPlayer::Type)) {
hhPlayer *plEnt = static_cast<hhPlayer *>(ent);
if (plEnt->IsWallWalking()) { //if it's a wallwalking player, try to trace down on the other side for wallwalk
idVec3 downPoint = useOrigin - (axis[2]*64.0f);
if (gameLocal.clip.Translation(transCheck, useOrigin, downPoint, clip, axis, ent->GetPhysics()->GetClipMask(), ent)) {
if (gameLocal.GetMatterType(transCheck, NULL) == SURFTYPE_WALLWALK) { //if we hit wallwalk, use the trace endpoint
useOrigin = transCheck.endpos;
}
}
}
}
}
else {
testOrigin = cameraTarget->GetOrigin();
testOrigin += cameraTarget->GetAxis()[0]*distExtrusion;
testOrigin -= cameraTarget->GetAxis()[2]*heightAdjust;
useOrigin = testOrigin;
#if !GOLD
if (developer.GetBool()) {
const char *entName = ent->GetName();
if (!entName || !entName[0]) {
entName = "<unknown>";
}
gameLocal.Warning("Ent '%s' could not get a clean trace on the other side of gameportal at (%f %f %f).", entName, useOrigin.x, useOrigin.y, useOrigin.z);
hhUtils::DebugAxis( testOrigin, cameraTarget->GetAxis(), 32.0f, 5000 );
}
#endif
}
}
}
// If the actor is a player, then disable camera interpolation for this move. This must be done before linking
if( ent->IsType(hhPlayer::Type) ) {
hhPlayer* player = static_cast<hhPlayer*>( ent );
idVec3 viewDir = player->GetAxis()[0];
PortalRotate( viewDir, sourceAxis, destAxis, true );
player->TeleportNoKillBox( useOrigin, axis, viewDir, player->GetUntransformedViewAngles() );
} else {
// Valid move. This code is done in SetOrientation for the player.
ent->SetOrigin( useOrigin );
ent->SetAxis( axis );
}
// Re-link the actor into the clip tree
clip->Link( gameLocal.clip );
if ( ent->IsType( idActor::Type ) ) {
static_cast<idActor *>(ent)->LinkCombat();
}
// players telefrag anything at the new position
if ( ent->IsType( hhPlayer::Type ) ) {
gameLocal.KillBox( ent );
}
// Set the gravity correctly on the entity that was portaled, based upon the destination portal's gravity
ent->CancelEvents( &EV_ResetGravity );
if (!ent->IsType(hhVehicle::Type)) {
ent->SetGravity( cameraTarget->GetGravity() );
}
// If the actor is a player, then check for wallwalk. This should be done after linking and setting gravity
if( ent->IsType(hhPlayer::Type) ) {
hhPlayer* player = static_cast<hhPlayer*>(ent);
if (player->GetPhysics() && player->GetPhysics()->IsType(hhPhysics_Player::Type)) {
static_cast<hhPhysics_Player *>(player->GetPhysics())->CheckWallWalk( true );
}
}
// Sound issues
if ( bGlowPortal ) {
StartSound( "snd_portal_entity", SND_CHANNEL_ANY ); // Play the portal sound at the origin portal
if(this->cameraTarget) { // Play the portal sound at the destination portal as well
const idSoundShader *def = declManager->FindSound(spawnArgs.GetString("snd_portal_entity"));//gameSoundWorld->FinishShader(spawnArgs.GetString("snd_portal_entity"));
this->cameraTarget->StartSoundShader( def, SND_CHANNEL_ANY );
}
}
return(true);
}
//==========================================================================
//
// hhPortal::Event_Trigger
//
// Toggle portal state.
//==========================================================================
void hhPortal::Event_Trigger(idEntity *activator) {
// If we have a master portal, have him trigger everyone
if ( masterPortal.IsValid() ) {
masterPortal->Event_Trigger( activator );
return;
}
// If we have slave portals, trigger them first
if ( slavePortals.Num() > 0 ) {
for ( int i = 0; i < slavePortals.Num(); ++i ) {
slavePortals[ i ]->Trigger( activator );
}
}
Trigger( activator );
}
void hhPortal::Trigger( idEntity *activator ) {
hhFxInfo fxInfo;
fxInfo.SetNormal( GetAxis()[2] );
fxInfo.RemoveWhenDone( true );
if(portalState == PORTAL_CLOSED) {
portalState = PORTAL_OPENING;
if (!bNoTeleport) { //rww - if teleporting, ensure things collide with me while i am opening
GetPhysics()->SetContents( CONTENTS_SOLID );
}
#if GAMEPORTAL_PVS
if (areaPortal && !gameLocal.isClient) {
gameLocal.SetPortalState( areaPortal, PS_BLOCK_NONE );
}
#endif
TriggerTargets(); // nla
renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = MS2SEC( gameLocal.time );
if ( bGlowPortal ) {
if (!gameLocal.isMultiplayer) { //rww - superfast portals don't use fx
BroadcastFxInfo( spawnArgs.GetString("fx_open"), GetOrigin(), GetAxis(), &fxInfo );
}
StartSound( "snd_open", SND_CHANNEL_ANY );
if ( spawnArgs.GetBool( "fast_open", "0" ) ) {
SetSkinByName( spawnArgs.GetString( "skin" ) );
} else {
SetSkinByName( spawnArgs.GetString( "skin_onlyWarp" ) ); // Only show the warp when first opening
}
int anim = GetAnimator()->GetAnim(spawnArgs.GetString("open_anim", "open"));
GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, 0 );
PostEventMS( &EV_Opened, GetAnimator()->AnimLength( anim ) );
SetShaderParm( SHADERPARM_MODE, 0 ); // ensure that sparking is off
fl.neverDormant = true; // Don't allow the portal to go dormant while opening/closing
} else {
PostEventMS( &EV_Opened, 500 );
}
PostEventMS( &EV_Show, 10 ); // Delay showing for a frame so the skin and registers are properly set beforehand
} else if(portalState == PORTAL_OPENED || portalState == PORTAL_OPENING) {
portalState = PORTAL_CLOSING;
TriggerTargets(); // nla
renderEntity.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time );
if ( bGlowPortal ) {
if (!gameLocal.isMultiplayer) { //rww - superfast portals don't use fx
BroadcastFxInfo( spawnArgs.GetString("fx_close"), GetOrigin(), GetAxis(), &fxInfo );
}
StopSound( SND_CHANNEL_ANY );
StartSound( "snd_close", SND_CHANNEL_ANY );
int anim = GetAnimator()->GetAnim("close");
GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, 0.5 );
PostEventMS( &EV_Closed, GetAnimator()->AnimLength( anim ) );
fl.neverDormant = true; // Don't allow the portal to go dormant while opening/closing
} else {
PostEventMS( &EV_Closed, 400 );
}
}
}
//==========================================================================
//
// hhPortal::Event_Opened
//
//==========================================================================
void hhPortal::Event_Opened( void ) {
portalState = PORTAL_OPENED;
if (!bNoTeleport) { //rww - if teleporting, ensure things collide with me while i am open
GetPhysics()->SetContents( CONTENTS_SOLID );
}
TriggerTargets(); // nla
if ( bGlowPortal ) {
StartSound( "snd_loop", SND_CHANNEL_ANY );
int anim = GetAnimator()->GetAnim("opened");
GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, anim, gameLocal.time, 0 );
fl.neverDormant = false; // The portal can go dormant once opened/closed
PostEventSec( &EV_PortalSpark, gameLocal.random.RandomFloat() );
}
UpdateVisuals();
// If we are open, and should close automatically, post an event to trigger us closed - nla
if ( closeDelay > 0.0f ) {
PostEventSec( &EV_Activate, closeDelay, NULL );
// Remove the portal now that it has done it's job
if (monsterportal) {
#ifdef _DEBUG
// If another portal is camera targetting me it will cause a crash so check for it
for( idEntity *ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
if (ent->cameraTarget && ent->cameraTarget == this) {
assert(0);
}
}
#endif
PostEventSec( &EV_Remove, closeDelay+5.0f );
}
}
}
//==========================================================================
//
// hhPortal::Event_Closed
//
//==========================================================================
void hhPortal::Event_Closed( void ) {
renderEntity.shaderParms[SHADERPARM_MODE] = 1.0f;
portalState = PORTAL_CLOSED;
GetPhysics()->SetContents( 0 ); //rww - do not collide with things while closed
#if GAMEPORTAL_PVS
if (areaPortal && !gameLocal.isClient) {
gameLocal.SetPortalState( areaPortal, PS_BLOCK_ALL );
}
#endif
TriggerTargets(); // nla
fl.neverDormant = false; // The portal can go dormant once opened/closed
Hide();
UpdateVisuals();
//? Have an option to remove the portals? - nla
if ( spawnArgs.GetBool( "remove_on_close", "0" ) ) {
PostEventMS( &EV_Remove, 0 );
}
}
//==========================================================================
//
// hhPortal::Event_PortalSpark
//
//==========================================================================
void hhPortal::Event_PortalSpark( void ) {
int spark;
float nextTime;
if ( this->IsHidden() ) { // No sparking if hidden
SetShaderParm( SHADERPARM_MODE, 0 ); // set the material parm (one-based)
return;
}
spark = gameLocal.random.RandomInt( spawnArgs.GetInt( "sparkCount" ) );
// Set the shader parm as needed
SetShaderParm( SHADERPARM_MODE, spark + 1 ); // set the material parm (one-based)
if ( gameLocal.random.RandomFloat() < 0.1f ) { // 1/10th of a chance that another spark will happen quickly after this one
nextTime = 0.2f;
} else { // Normal random time between sparks.
nextTime = spawnArgs.GetFloat( "sparkTimeMin" ) + gameLocal.random.RandomFloat() * spawnArgs.GetFloat( "sparkTimeRnd" );
}
StartSound( "snd_portal_spark", SND_CHANNEL_ANY );
PostEventSec( &EV_PortalSpark, nextTime );
PostEventSec( &EV_PortalSparkEnd, 0.1f ); // spark only lasts for 0.1 sec
}
//==========================================================================
//
// hhPortal::Event_PortalSparkEnd
//
//==========================================================================
void hhPortal::Event_PortalSparkEnd( void ) {
SetShaderParm( SHADERPARM_MODE, 0 ); // Disable the spark parm
}
//==========================================================================
//
// hhPortal::Event_ShowGlowPortal
//
// Simply sets the skin on the portal back to the default or to the
// specified "skin"
//==========================================================================
void hhPortal::Event_ShowGlowPortal( void ) {
SetSkinByName( spawnArgs.GetString( "skin" ) );
}
//==========================================================================
//
// hhPortal::Event_HideGlowPortal
//
// Sets the skin on the portal to a specific skin that hides the glowy parts
//==========================================================================
void hhPortal::Event_HideGlowPortal( void ) {
SetSkinByName( spawnArgs.GetString( "skin_onlyWarp" ) );
}