etqw-sdk/source/game/Teleporter.cpp
2008-05-29 00:00:00 +00:00

654 lines
19 KiB
C++

// Copyright (C) 2007 Id Software, Inc.
//
#include "precompiled.h"
#pragma hdrstop
#if defined( _DEBUG ) && !defined( ID_REDIRECT_NEWDELETE )
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#include "Teleporter.h"
#include "structures/TeamManager.h"
#include "Trigger.h"
#include "Player.h"
#include "vehicles/Transport.h"
#include "vehicles/VehicleControl.h"
#include "vehicles/JetPack.h"
/*
===============================================================================
sdTeleporter
===============================================================================
*/
const idEventDef EV_EnableTeam( "enableTeam", '\0', DOC_TEXT( "Allows the given team to use the teleporter." ), 1, "An error will be thrown if the team name is invalid.", "s", "name", "Name of the team." );
const idEventDef EV_DisableTeam( "disableTeam", '\0', DOC_TEXT( "Disables the teleporter for the given team." ), 1, "An error will be thrown if the team name is invalid.", "s", "name", "Name of the team." );
const idEventDefInternal EV_FinishTeleport( "internal_finishTeleport", "h" );
CLASS_DECLARATION( idEntity, sdTeleporter )
EVENT( EV_EnableTeam, sdTeleporter::Event_EnableTeam )
EVENT( EV_DisableTeam, sdTeleporter::Event_DisableTeam )
EVENT( EV_FinishTeleport, sdTeleporter::Event_FinishTeleport )
END_CLASS
/*
================
sdTeleporter::sdTeleporter
================
*/
sdTeleporter::sdTeleporter( void ) {
}
/*
================
sdTeleporter::~sdTeleporter
================
*/
sdTeleporter::~sdTeleporter( void ) {
}
/*
================
sdTeleporter::Spawn
================
*/
void sdTeleporter::Spawn( void ) {
BecomeActive( TH_THINK );
sdTeamManagerLocal& manager = sdTeamManager::GetInstance();
teamInfo.SetNum( manager.GetNumTeams() );
for ( int i = 0; i < manager.GetNumTeams(); i++ ) {
sdTeamInfo& team = manager.GetTeamByIndex( i );
// default to enabled
teamInfo[ i ].enabled = spawnArgs.GetBool( va( "%s_starts_enabled", team.GetLookupName() ), "1" );
}
delay = spawnArgs.GetInt( "delay" );
exitVelocity = spawnArgs.GetVector( "velocity_exit", "100 0 0" );
deployReverse = spawnArgs.GetFloat( "deploy_reverse" );
deployLength = spawnArgs.GetFloat( "deploy_length" );
deployWidth = spawnArgs.GetFloat( "deploy_width" );
telefragDamage = DAMAGE_FOR_NAME( spawnArgs.GetString( "dmg_telefrag" ) );
// set up the physics
const char* triggerModel = spawnArgs.GetString( "cm_trigger" );
staticPhysics.SetSelf( this );
staticPhysics.SetClipModel( new idClipModel( GetPhysics()->GetClipModel() ), 1.0f );
if ( triggerModel != NULL && *triggerModel ) {
// the trigger for the physics
staticPhysics.SetClipModel( new idClipModel( triggerModel ), 1.0f, 1 );
}
staticPhysics.SetOrigin( GetPhysics()->GetOrigin() );
staticPhysics.SetAxis( GetPhysics()->GetAxis() );
SetPhysics( &staticPhysics );
}
/*
================
sdTeleporter::Think
================
*/
void sdTeleporter::Think( void ) {
const idBounds& myBounds = GetPhysics()->GetAbsBounds();
for ( int i = 0; i < latches.Num(); ) {
idEntity* ent = latches[ i ];
if ( ent == NULL ) {
latches.RemoveIndexFast( i );
continue;
}
// check if its still touching
if ( GetPhysics()->GetNumClipModels() < 2 ) {
continue;
}
idClipModel* triggerCM = GetPhysics()->GetClipModel( 1 );
// check all the collision models of the other against our trigger model
int numClipModels = ent->GetPhysics()->GetNumClipModels();
int contents = 0;
for ( int j = 0; j < numClipModels; j++ ) {
idClipModel* otherCM = ent->GetPhysics()->GetClipModel( j );
contents |= gameLocal.clip.ContentsModel( CLIP_DEBUG_PARMS otherCM->GetOrigin(), otherCM, otherCM->GetAxis(), -1,
triggerCM, triggerCM->GetOrigin(), triggerCM->GetAxis() );
}
if ( !contents ) {
latches.RemoveIndexFast( i );
continue;
}
i++;
}
if ( latches.Num() == 0 ) {
BecomeInactive( TH_THINK );
}
}
/*
================
sdTeleporter::Latch
================
*/
void sdTeleporter::Latch( idEntity* ent ) {
latches.Alloc() = ent;
BecomeActive( TH_THINK );
}
/*
================
sdTeleporter::PostMapSpawn
================
*/
void sdTeleporter::PostMapSpawn( void ) {
storageLocation = gameLocal.FindEntity( spawnArgs.GetString( "storage_entity" ) );
viewLocation = gameLocal.FindEntity( spawnArgs.GetString( "view_entity" ) );
idEntity::PostMapSpawn();
}
/*
================
sdTeleporter::Event_EnableTeam
================
*/
void sdTeleporter::Event_EnableTeam( const char* team ){
int teamIndex = sdTeamManager::GetInstance().GetTeam( team ).GetIndex();
teamInfo[ teamIndex ].enabled = true;
}
/*
================
sdTeleporter::Event_DisableTeam
================
*/
void sdTeleporter::Event_DisableTeam( const char* team ) {
int teamIndex = sdTeamManager::GetInstance().GetTeam( team ).GetIndex();
teamInfo[ teamIndex ].enabled = false;
}
/*
================
sdTeleporter::Event_FinishTeleport
================
*/
void sdTeleporter::Event_FinishTeleport( int spawnId ) {
FinishTeleport( gameLocal.EntityForSpawnId( spawnId ) );
}
/*
================
sdTeleporter::OnTouch
================
*/
void sdTeleporter::OnTouch( idEntity *other, const trace_t& trace ) {
if ( gameLocal.isClient ) {
return;
}
idPlayer* player = other->Cast< idPlayer >();
if ( player != NULL && !player->IsSpectator() && player->GetHealth() <= 0 ) {
return;
}
// abort if it didn't touch the trigger model
if ( trace.c.id != 1 ) {
return;
}
// abort if there is no target
if ( targets.Num() == 0 ) {
return;
}
bool teamValid = true;
// abort if the other is on a disabled team
sdTeamInfo* otherTeam = other->GetGameTeam();
if ( otherTeam != NULL ) {
if ( !teamInfo[ otherTeam->GetIndex() ].enabled ) {
teamValid = false;
idPlayer* otherPlayer = other->Cast< idPlayer >();
if ( otherPlayer != NULL ) {
if ( otherPlayer->IsDisguised() ) {
sdTeamInfo* disguiseTeam = otherPlayer->GetDisguiseTeam();
if ( disguiseTeam != NULL ) {
if ( teamInfo[ disguiseTeam->GetIndex() ].enabled ) {
teamValid = true;
}
}
}
}
}
}
if ( !teamValid ) {
return;
}
if ( !other->AllowTeleport() ) {
return;
}
// check if this entity has already been touched
if ( latches.FindIndex( other ) != -1 ) {
return;
}
const idVec3& myPosition = GetPhysics()->GetOrigin();
const idMat3& myAxes = GetAxis();
// only allow the entity to teleport if its moving INTO the teleporter
float moveDir = other->GetPhysics()->GetLinearVelocity() * myAxes[ 0 ];
if ( moveDir > 0.0f ) {
return;
}
Latch( other );
idEntity *targetEntity = targets[ 0 ].GetEntity();
// get the positions of the target and the latch
idVec3 targetPosition = targetEntity->GetPhysics()->GetOrigin();
idMat3 targetAxes = targetEntity->GetAxis();
// find the target teleporter
// TODO: Make it an entity key on the target entity
sdTeleporter* targetTeleporter = NULL;
idClass* typeInstance = Type.instances.Next();
for ( ; typeInstance; typeInstance = typeInstance->GetInstanceNode()->Next() ) {
sdTeleporter* testTeleporter = reinterpret_cast< sdTeleporter* >( typeInstance );
idBounds bounds = testTeleporter->GetPhysics()->GetAbsBounds();
if ( bounds.ContainsPoint( targetPosition ) ) {
targetTeleporter = testTeleporter;
}
}
if ( targetTeleporter != NULL ) {
targetTeleporter->Latch( other );
}
// modify the position & angles if its a vehicle that should land on the ramp
sdTransport* transportOther = other->Cast< sdTransport >();
idPlayer* playerOther = other->Cast< idPlayer >();
sdJetPack* jetPackOther = other->Cast< sdJetPack >();
bool shouldTraceDown = false;
bool shouldOrientToRamp = false;
bool shouldOrientBackTrace = false;
float traceDownOffset = 0.0f;
if ( transportOther != NULL && transportOther->GetVehicleControl() != NULL ) {
shouldTraceDown = transportOther->GetVehicleControl()->TeleportOntoRamp();
shouldOrientToRamp = transportOther->GetVehicleControl()->TeleportOntoRampOriented();
shouldOrientBackTrace = transportOther->GetVehicleControl()->TeleportBackTraceOriented();
traceDownOffset = transportOther->GetVehicleControl()->TeleportOntoRampHeightOffset();
}
if ( playerOther != NULL || jetPackOther != NULL ) {
shouldTraceDown = true;
}
if ( shouldOrientToRamp ) {
// find the normal of the surface
trace_t downTrace;
gameLocal.clip.TracePoint( CLIP_DEBUG_PARMS downTrace, targetPosition + idVec3( 0.0f, 0.0f, 128.0f ), targetPosition - idVec3( 0.0f, 0.0f, 512.0f ), CONTENTS_MOVEABLECLIP, NULL );
// find the new target axes using that
idMat3 newTargetAxes;
newTargetAxes[ 2 ] = downTrace.c.normal;
newTargetAxes[ 1 ] = newTargetAxes[ 2 ].Cross( targetAxes[ 0 ] );
newTargetAxes[ 1 ].Normalize();
newTargetAxes[ 0 ] = newTargetAxes[ 1 ].Cross( newTargetAxes[ 2 ] );
newTargetAxes[ 0 ].Normalize();
targetAxes = newTargetAxes;
}
if ( shouldTraceDown ) {
// trace down to find where to land
trace_t downTrace;
const idBounds& bounds = other->GetPhysics()->GetBounds();
idMat3 traceAxis = targetAxes;
if ( !shouldOrientToRamp ) {
traceAxis = mat3_identity;
}
gameLocal.clip.TraceBounds( CLIP_DEBUG_PARMS downTrace, targetPosition + idVec3( 0.0f, 0.0f, 128.0f ), targetPosition - idVec3( 0.0f, 0.0f, 512.0f ),
bounds, traceAxis, CONTENTS_MOVEABLECLIP, NULL );
targetPosition = downTrace.endpos + idVec3( 0.0f, 0.0f, traceDownOffset );
}
// trace from front to back against the trigger to make sure the new position is outside the teleporting trigger
if ( targetTeleporter != NULL ) {
trace_t backTrace;
const idBounds& bounds = other->GetPhysics()->GetBounds();
idMat3 traceAxis = targetAxes;
if ( !shouldOrientBackTrace ) {
traceAxis = mat3_identity;
}
idClipModel* triggerCM = targetTeleporter->GetPhysics()->GetClipModel( 1 );
const idClipModel* boundsClip = gameLocal.clip.GetTemporaryClipModel( bounds );
idVec3 endTraceBack = targetPosition;
idVec3 startTraceBack = endTraceBack + 1024.0f*targetAxes[ 0 ];
// check all the collision models of the other against our trigger model
gameLocal.clip.TranslationModel( CLIP_DEBUG_PARMS backTrace, startTraceBack, endTraceBack,
boundsClip, traceAxis, -1, triggerCM, triggerCM->GetOrigin(), triggerCM->GetAxis() );
if ( backTrace.fraction < 1.0f ) {
// push forwards a bit
targetPosition = backTrace.endpos + 32.0f * targetAxes[ 0 ];
}
}
teleportParms_t parms;
parms.location = targetPosition;
parms.orientation = targetAxes;
parms.linearVelocity = exitVelocity * targetAxes;
parms.angularVelocity = vec3_zero;
parms.spawnId = gameLocal.GetSpawnId( other );
// finally, we want to check if the entity went in backwards, in which case
// they should come out backwards too
const idMat3& otherAxes = other->GetAxis();
if ( otherAxes[ 0 ] * myAxes[ 0 ] > 0.7f ) {
parms.orientation[ 0 ] *= -1.0f;
parms.orientation[ 1 ] *= -1.0f;
}
if ( delay > 0 ) {
StartTeleport( parms );
return;
}
FinishTeleport( parms );
}
/*
================
sdTeleporter::StartTeleport
================
*/
void sdTeleporter::StartTeleport( const teleportParms_t& parms ) {
idEntity* ent = gameLocal.EntityForSpawnId( parms.spawnId );
if ( !ent ) {
return;
}
idEntity* storage = storageLocation;
if ( storage != NULL ) {
ent->SetOrigin( storage->GetPhysics()->GetOrigin() );
ent->SetAxis( mat3_identity );
}
ent->OnTeleportStarted( this );
teleportInfo.Alloc() = parms;
idEventArg arg;
arg.SetHandle( parms.spawnId );
PostEventMS( &EV_FinishTeleport, delay, arg );
}
/*
================
sdTeleporter::FinishTeleport
================
*/
void sdTeleporter::FinishTeleport( idEntity* ent ) {
int spawnId = gameLocal.GetSpawnId( ent );
int i;
for ( i = 0; i < teleportInfo.Num(); i++ ) {
if ( teleportInfo[ i ].spawnId == spawnId ) {
break;
}
}
if ( i == teleportInfo.Num() ) {
gameLocal.Warning( "sdTeleporter::Event_FinishTeleport Failed To Find Entry For SpawnId %d", spawnId );
return;
}
teleportParms_t& parms = teleportInfo[ i ];
FinishTeleport( parms );
teleportInfo.RemoveIndexFast( i );
}
/*
================
sdTeleporter::GetTeleportEndPoint
================
*/
void sdTeleporter::GetTeleportEndPoint( idEntity* ent, idVec3& org, idMat3& axes ) {
int spawnId = gameLocal.GetSpawnId( ent );
int i;
for ( i = 0; i < teleportInfo.Num(); i++ ) {
if ( teleportInfo[ i ].spawnId == spawnId ) {
break;
}
}
if ( i == teleportInfo.Num() ) {
org = vec3_origin;
axes = mat3_identity;
return;
}
org = teleportInfo[ i ].location;
axes = teleportInfo[ i ].orientation;
}
/*
================
sdTeleporter::FinishTeleport
================
*/
void sdTeleporter::FinishTeleport( const teleportParms_t& parms ) {
idEntity* ent = gameLocal.EntityForSpawnId( parms.spawnId );
if ( !ent ) {
return;
}
idVec3 portLocation = parms.location;
bool teleFragSelf = false;
idPhysics* phys = ent->GetPhysics();
const idClipModel* clipModels[ MAX_GENTITIES ];
// players and jetpacks can spawn in a deploy zone around the exit point
idPlayer* playerOther = ent->Cast< idPlayer >();
sdJetPack* jetPackOther = ent->Cast< sdJetPack >();
if ( playerOther != NULL || jetPackOther != NULL ) {
// check if the target position is clear
int clipMask = ent->GetPhysics()->GetClipMask();
if ( gameLocal.clip.Contents( CLIP_DEBUG_PARMS parms.location + idVec3( 0.0f, 0.0f, 4.0f ), phys->GetClipModel(), mat3_identity, clipMask, this ) ) {
// not clear - try to find an alternative
bool foundClear = false;
// get the positions of the target
idEntity *targetEntity = targets[ 0 ].GetEntity();
const idVec3& targetPosition = targetEntity->GetPhysics()->GetOrigin();
const idMat3& targetAxes = targetEntity->GetAxis();
idVec3 xAxis = targetAxes[ 0 ];
idVec3 yAxis = targetAxes[ 1 ];
xAxis.z = yAxis.z = 0.0f;
xAxis.Normalize();
yAxis.Normalize();
idBounds bounds = phys->GetBounds();
float step = bounds.Size().Length() * 0.7071f;
float yStart = -0.5f * deployWidth;
float yEnd = yStart + deployWidth + step * 0.5f;
float xStart = -deployReverse;
float xEnd = xStart + deployLength;
trace_t trace;
idVec3 testPosition;
for ( float x = xStart; x <= xEnd; x += step ) {
for ( float y = yStart; y <= yEnd; y += step ) {
testPosition = targetPosition + y*yAxis + x*xAxis;
gameLocal.clip.TraceBounds( CLIP_DEBUG_PARMS trace, testPosition + idVec3( 0.0f, 0.0f, 128.0f ),
testPosition - idVec3( 0.0f, 0.0f, 512.0f ), bounds,
mat3_identity, CONTENTS_MOVEABLECLIP | CONTENTS_SOLID, NULL );
int contents = gameLocal.clip.Contents( CLIP_DEBUG_PARMS trace.endpos + idVec3( 0.0f, 0.0f, 4.0f ), phys->GetClipModel(), mat3_identity, clipMask, this );
if ( !contents ) {
foundClear = true;
portLocation = trace.endpos;
break;
// gameRenderWorld->DebugBounds( colorGreen, phys->GetBounds(), trace.endpos, mat3_identity, 10000 );
} else {
// gameRenderWorld->DebugBounds( colorRed, phys->GetBounds(), trace.endpos, mat3_identity, 10000 );
}
}
if ( foundClear ) {
break;
}
}
// check in between the last lot
if ( !foundClear ) {
for ( float x = xStart + step * 0.5f; x <= xEnd; x += step ) {
for ( float y = yStart + step * 0.5f; y <= yEnd; y += step ) {
testPosition = targetPosition + y*yAxis + x*xAxis;
gameLocal.clip.TraceBounds( CLIP_DEBUG_PARMS trace, testPosition + idVec3( 0.0f, 0.0f, 128.0f ),
testPosition - idVec3( 0.0f, 0.0f, 512.0f ), bounds,
mat3_identity, CONTENTS_MOVEABLECLIP | CONTENTS_SOLID, NULL );
int contents = gameLocal.clip.Contents( CLIP_DEBUG_PARMS trace.endpos + idVec3( 0.0f, 0.0f, 4.0f ), phys->GetClipModel(), mat3_identity, clipMask, this );
if ( !contents ) {
foundClear = true;
portLocation = trace.endpos;
break;
// gameRenderWorld->DebugBounds( colorGreen, phys->GetBounds(), trace.endpos, mat3_identity, 10000 );
} else {
// gameRenderWorld->DebugBounds( colorRed, phys->GetBounds(), trace.endpos, mat3_identity, 10000 );
}
}
if ( foundClear ) {
break;
}
}
}
if ( !foundClear ) {
teleFragSelf = true;
}
}
}
// do the moving
ent->SetOrigin( portLocation );
ent->SetAxis( parms.orientation );
phys->SetLinearVelocity( parms.linearVelocity );
phys->SetAngularVelocity( parms.angularVelocity );
ent->OnTeleportFinished();
if ( telefragDamage != NULL ) {
sdTransport* transportOther = ent->Cast< sdTransport >();
if ( transportOther != NULL ) {
// telefrag stuff
// see what we're touching
idStaticList< idEntity*, MAX_GENTITIES > fragEntities;
int numTouching = gameLocal.clip.ClipModelsTouchingBounds( CLIP_DEBUG_PARMS phys->GetAbsBounds(), phys->GetClipMask(),
clipModels, MAX_GENTITIES, ent );
for ( int i = 0; i < numTouching; i++ ) {
const idClipModel* cm = clipModels[ i ];
// don't check render entities
if ( cm->IsRenderModel() ) {
continue;
}
idEntity* hit = cm->GetEntity();
if ( hit == this || hit == NULL ) {
continue;
}
if ( !hit->fl.takedamage ) {
continue;
}
// decide how to hurt it or us
// some vehicles can't be teleported into, so will telefrag anything that attempts to teleport into it
bool fragIt = true;
sdTransport* transportHit = hit->Cast< sdTransport >();
if ( transportHit != NULL && transportHit->GetVehicleControl() != NULL ) {
if ( !transportHit->GetVehicleControl()->TeleportCanBeFragged() ) {
teleFragSelf = true;
break;
}
}
fragEntities.AddUnique( hit );
}
if ( !teleFragSelf ) {
idPlayer* attacker = transportOther->GetPositionManager().FindDriver();
// telefrag everything that was to be fragged
for ( int i = 0; i < fragEntities.Num(); i++ ) {
fragEntities[ i ]->Damage( ent, attacker, vec3_origin, telefragDamage, 1.0f, NULL, true );
}
}
}
if ( teleFragSelf ) {
ent->Damage( ent, ent, vec3_origin, telefragDamage, 1.0f, NULL, true );
}
}
}
/*
================
sdTeleporter::CancelTeleport
================
*/
void sdTeleporter::CancelTeleport( idEntity* other ) {
int spawnId = gameLocal.GetSpawnId( other );
for ( int i = 0; i < teleportInfo.Num(); i++ ) {
if ( teleportInfo[ i ].spawnId != spawnId ) {
continue;
}
teleportInfo.RemoveIndexFast( i );
return;
}
}
/*
================
sdTeleporter::GetTargetPosition
================
*/
void sdTeleporter::GetTargetPosition( idVec3& origin, idMat3& axis ) {
idEntity *targetEntity = targets[ 0 ].GetEntity();
origin = targetEntity->GetPhysics()->GetOrigin();
axis = targetEntity->GetAxis();
}