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

1077 lines
30 KiB
C++

#include "precompiled.h"
#pragma hdrstop
#include "prey_local.h"
// Events
const idEventDef EV_ModelDoorSpawnTrigger( "<strig>" );
const idEventDef EV_ModelDoorOpen( "open" );
const idEventDef EV_ModelDoorClose( "close" );
const idEventDef EV_ModelDoorClosedBegin( "<mdclosedbegin>" );
const idEventDef EV_ModelDoorOpeningBegin( "<mdopeningbegin>" );
const idEventDef EV_ModelDoorOpenBegin( "<mdopenbegin>" );
const idEventDef EV_ModelDoorClosingBegin( "<mdclosingbegin>" );
const idEventDef EV_SetBuddiesShaderParm( "setBuddiesShaderParm", "df" );
//--------------------------------
// hhDoorTrigger
//--------------------------------
CLASS_DECLARATION( idEntity, hhDoorTrigger )
EVENT( EV_Touch, hhDoorTrigger::Event_TriggerDoor )
END_CLASS
//--------------------------------
// hhDoorTrigger::hhDoorTrigger()
//--------------------------------
hhDoorTrigger::hhDoorTrigger() {
enabled = true; // Need to have the ability to disable a trigger
}
//--------------------------------
// hhDoorTrigger::Event_TriggerDoor
//--------------------------------
void hhDoorTrigger::Event_TriggerDoor( idEntity *other, trace_t *trace ) {
if( door && IsEnabled() ) {
door->ProcessEvent( &EV_Touch, other, trace );
}
}
//---------------------
// hhDoorTrigger::GetEntitiesWithin
// ents should be an array of idEntity of length entLength
//
// If not enabled, always returns 0
//---------------------
int hhDoorTrigger::GetEntitiesWithin( idEntity **ents, int entsLength ) {
int num;
if ( !enabled ) {
return( 0 );
}
num = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), MASK_SHOT_BOUNDINGBOX, ents, entsLength );
return( num );
}
void hhDoorTrigger::Save( idSaveGame *savefile ) const {
savefile->WriteObject( door );
savefile->WriteBool( enabled );
}
void hhDoorTrigger::Restore( idRestoreGame *savefile ) {
savefile->ReadObject( reinterpret_cast<idClass *&> ( door ) );
savefile->ReadBool( enabled );
}
//--------------------------------
// hhModelDoor
//--------------------------------
CLASS_DECLARATION( hhAnimatedEntity, hhModelDoor )
EVENT( EV_TeamBlocked, hhModelDoor::Event_TeamBlocked )
EVENT( EV_PartBlocked, hhModelDoor::Event_PartBlocked )
EVENT( EV_Activate, hhModelDoor::Event_Activate )
EVENT( EV_Touch, hhModelDoor::Event_Touch )
EVENT( EV_ModelDoorOpen, hhModelDoor::Event_OpenDoor )
EVENT( EV_ModelDoorClose, hhModelDoor::Event_CloseDoor )
EVENT( EV_Thread_SetCallback, hhModelDoor::Event_SetCallback )
EVENT( EV_SetBuddiesShaderParm, hhModelDoor::Event_SetBuddiesShaderParm )
// Internal events
EVENT( EV_ModelDoorSpawnTrigger, hhModelDoor::Event_SpawnNewDoorTrigger )
EVENT( EV_ModelDoorClosedBegin, hhModelDoor::Event_STATE_ClosedBegin )
EVENT( EV_ModelDoorOpeningBegin, hhModelDoor::Event_STATE_OpeningBegin )
EVENT( EV_ModelDoorOpenBegin, hhModelDoor::Event_STATE_OpenBegin )
EVENT( EV_ModelDoorClosingBegin, hhModelDoor::Event_STATE_ClosingBegin )
END_CLASS
//--------------------------------
// hhModelDoor::Spawn
//--------------------------------
void hhModelDoor::Spawn( void ) {
idEntity* master = NULL;
doorTrigger = NULL;
fl.takedamage = true;
SetBlocking(true);
// spawnArgs.GetFloat( "dmg", "2", &damage );
spawnArgs.GetFloat( "triggersize", "50", triggersize );
spawnArgs.GetBool( "no_touch", "0", noTouch );
spawnArgs.GetInt( "locked", "0", locked );
spawnArgs.GetFloat( "wait", "1.5", wait );
if ( wait <= 0.1f ) {
//sanity check for waittime
wait = 0.1f;
}
spawnArgs.GetFloat( "damage", "1", damage );
spawnArgs.GetFloat( "airlockwait", "3.0", airLockSndWait );
airLockSndWait *= 1000.0f; // Convert from seconds to ms
hhUtils::GetValues( spawnArgs, "buddy", buddyNames, true );
// If "health" is supplied, door will open when killed
// So this key determines if we should really take damage or not
fl.takedamage = (health > 0);
forcedOpen = false;
bOpenForMonsters = spawnArgs.GetBool("OpenForMonsters", "1");
SetShaderParm( SHADERPARM_MODE, GetDoorShaderParm( locked != 0, true ) ); // 2=locked, 1=unlocked, 0=never locked
//HUMANHEAD: aob - airlock stuff
airlockMaster = NULL;
airlockTeam.SetOwner( this );
airlockTeamName = spawnArgs.GetString( "airlockTeam" );
master = DetermineTeamMaster( GetAirLockTeamName() );
if( master ) {
airlockMaster = static_cast<hhModelDoor*>( master );
if( airlockMaster != this ) {
JoinAirLockTeam( airlockMaster );
}
}
//HUMANHEAD END
openAnim = GetAnimator()->GetAnim( "open" );
closeAnim = GetAnimator()->GetAnim( "close" );
idleAnim = GetAnimator()->GetAnim( "idle" );
painAnim = GetAnimator()->GetAnim( "pain" );
// Spawn the trigger
if (!gameLocal.isClient) {
PostEventMS( &EV_ModelDoorSpawnTrigger, 0 );
}
// see if we are on an areaportal
areaPortal = gameRenderWorld->FindPortal( GetPhysics()->GetAbsBounds() );
StartSound( "snd_idle", SND_CHANNEL_ANY );
if( spawnArgs.GetBool("start_open") ) {
StartOpen();
} else {
StartClosed();
}
threadNum = 0;
nextAirLockSnd = 0;
finishedSpawn = true;
fl.networkSync = true;
bShuttleDoors = spawnArgs.GetBool( "shuttle_doors" );
}
//--------------------------------
// hhModelDoor::hhModelDoor
//--------------------------------
hhModelDoor::hhModelDoor() {
bOpen = false;
bTransition = false;
finishedSpawn = false;
sndTrigger = NULL;
nextSndTriggerTime = 0;
}
//--------------------------------
// hhModelDoor::~hhModelDoor
//--------------------------------
hhModelDoor::~hhModelDoor() {
airlockTeam.Remove();
if ( sndTrigger ) {
delete sndTrigger;
sndTrigger = NULL;
}
}
void hhModelDoor::Save( idSaveGame *savefile ) const {
savefile->WriteFloat( damage );
savefile->WriteFloat( wait );
savefile->WriteFloat( triggersize );
savefile->WriteInt( areaPortal );
if ( areaPortal ) {
savefile->WriteInt( gameRenderWorld->GetPortalState( areaPortal ) );
}
savefile->WriteStringList( buddyNames );
savefile->WriteInt( locked );
savefile->WriteInt( openAnim );
savefile->WriteInt( closeAnim );
savefile->WriteInt( idleAnim );
savefile->WriteInt( painAnim );
savefile->WriteBool( forcedOpen );
savefile->WriteBool( bOpenForMonsters );
savefile->WriteBool( noTouch );
savefile->WriteInt( threadNum );
savefile->WriteObject( doorTrigger );
savefile->WriteString( airlockTeamName );
savefile->WriteObject( airlockMaster );
savefile->WriteBounds( crusherBounds );
activatedBy.Save( savefile );
savefile->WriteBool( bOpen );
savefile->WriteBool( bTransition );
savefile->WriteFloat( airLockSndWait );
savefile->WriteClipModel( sndTrigger );
savefile->WriteInt( nextSndTriggerTime );
}
void hhModelDoor::Restore( idRestoreGame *savefile ) {
savefile->ReadFloat( damage );
savefile->ReadFloat( wait );
savefile->ReadFloat( triggersize );
savefile->ReadInt( areaPortal );
if ( areaPortal ) {
int portalState;
savefile->ReadInt( portalState );
gameLocal.SetPortalState( areaPortal, portalState );
}
savefile->ReadStringList( buddyNames );
savefile->ReadInt( locked );
savefile->ReadInt( openAnim );
savefile->ReadInt( closeAnim );
savefile->ReadInt( idleAnim );
savefile->ReadInt( painAnim );
savefile->ReadBool( forcedOpen );
savefile->ReadBool( bOpenForMonsters );
savefile->ReadBool( noTouch );
savefile->ReadInt( threadNum );
savefile->ReadObject( reinterpret_cast<idClass *&> ( doorTrigger ) );
savefile->ReadString( airlockTeamName );
savefile->ReadObject( reinterpret_cast<idClass *&> ( airlockMaster ) );
savefile->ReadBounds( crusherBounds );
activatedBy.Restore( savefile );
savefile->ReadBool( bOpen );
savefile->ReadBool( bTransition );
savefile->ReadFloat( airLockSndWait );
airlockTeam.SetOwner( this );
if( airlockMaster && airlockMaster != this ) {
JoinAirLockTeam( airlockMaster );
}
nextAirLockSnd = 0;
finishedSpawn = true;
bShuttleDoors = spawnArgs.GetBool( "shuttle_doors" );
savefile->ReadClipModel( sndTrigger );
savefile->ReadInt( nextSndTriggerTime );
}
//--------------------------------
// hhModelDoor::Lock
//--------------------------------
void hhModelDoor::Lock( int f ) {
locked = f;
float parmValue = GetDoorShaderParm( locked != 0, false );
SetShaderParm( SHADERPARM_MODE, parmValue );
SetBuddiesShaderParm( SHADERPARM_MODE, parmValue );
if (locked) {
CloseDoor();
}
}
//--------------------------------
// hhModelDoor::CanOpen
//--------------------------------
bool hhModelDoor::CanOpen() const {
if( IsLocked() ) {
return false;
}
if( !GetAirLockMaster() ) {
return true;
}
for( hhModelDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) {
if( node != this && node->IsOpen() ) {
return false;
}
}
return true;
}
//--------------------------------
// hhModelDoor::CanClose
//--------------------------------
bool hhModelDoor::CanClose() const {
return true;
}
//--------------------------------
// hhModelDoor::OpenDoor
//--------------------------------
void hhModelDoor::OpenDoor() {
if( IsClosed() && CanOpen() ) {
bTransition = true;
PostEventMS( &EV_ModelDoorOpeningBegin, 0 );
}
}
//--------------------------------
// hhModelDoor::CloseDoor
//--------------------------------
void hhModelDoor::CloseDoor() {
if( bOpen && !bTransition && CanClose() && !forcedOpen ) {
bTransition = true;
PostEventMS( &EV_ModelDoorClosingBegin, 0 );
// CJR: It's possible to open a door, then get back into it while it's animating closed, and then get squished
// the fix for this is to block the player from getting back in while it's closing. Projectiles will still pass through
GetPhysics()->SetContents( CONTENTS_PLAYERCLIP );
}
}
//--------------------------------
// hhModelDoor::SetBlocking
//--------------------------------
void hhModelDoor::SetBlocking( bool on ) {
GetPhysics()->SetContents( on ? CONTENTS_SOLID : 0 );
}
//--------------------------------
// hhModelDoor::ClosePortal
//--------------------------------
void hhModelDoor::ClosePortal( void ) {
if ( areaPortal ) {
gameLocal.SetPortalState( areaPortal, PS_BLOCK_VIEW );
}
}
//--------------------------------
// hhModelDoor::OpenPortal
//--------------------------------
void hhModelDoor::OpenPortal( void ) {
if ( areaPortal ) {
gameLocal.SetPortalState( areaPortal, PS_BLOCK_NONE );
}
}
//--------------------------------
// hhModelDoor::InformDone
//--------------------------------
void hhModelDoor::InformDone() {
idThread::ObjectMoveDone( threadNum, this );
threadNum = 0;
}
//--------------------------------
// hhModelDoor::Damage
//--------------------------------
void hhModelDoor::Damage( idEntity *inflictor, idEntity *attacker, const idVec3 &dir, const char *damageDefName, const float damageScale, const int location ) {
if (fl.takedamage) {
if ( !bOpen && painAnim ) {
GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, painAnim, gameLocal.time, 500);
}
hhAnimatedEntity::Damage(inflictor, attacker, dir, damageDefName, damageScale, location);
}
}
//--------------------------------
// hhModelDoor::Killed
//--------------------------------
void hhModelDoor::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) {
if (IsLocked()) {
Lock(0);
}
OpenDoor();
fl.takedamage = false;
forcedOpen = true;
}
//--------------------------------
// hhModelDoor::SetBuddiesShaderParm
//--------------------------------
void hhModelDoor::SetBuddiesShaderParm( int parm, float value ) {
idEntity* buddy = NULL;
for( int ix = buddyNames.Num() - 1; ix >= 0; --ix ) {
if( !buddyNames[ix].Length() ) {
continue;
}
buddy = gameLocal.FindEntity( buddyNames[ix].c_str() );
if( !buddy ) {
continue;
}
buddy->SetShaderParm( parm, value );
}
}
//--------------------------------
// hhModelDoor::ToggleBuddiesShaderParm
//--------------------------------
void hhModelDoor::ToggleBuddiesShaderParm( int parm, float firstValue, float secondValue, float toggleDelay ) {
SetBuddiesShaderParm( parm, firstValue );
CancelEvents( &EV_SetBuddiesShaderParm );
PostEventSec( &EV_SetBuddiesShaderParm, toggleDelay, parm, secondValue );
}
//--------------------------------
// hhModelDoor::DetermineTeamMaster
//--------------------------------
idEntity* hhModelDoor::DetermineTeamMaster( const char* teamName ) {
idEntity* ent = NULL;
if ( teamName && teamName[0] ) {
// find the first entity spawned on this team (which could be us)
for( ent = gameLocal.spawnedEntities.Next(); ent != NULL; ent = ent->spawnNode.Next() ) {
if (ent->IsType(hhModelDoor::Type) && !idStr::Icmp( static_cast<hhModelDoor *>(ent)->GetAirLockTeamName(), teamName )) {
return ent;
}
if (ent->IsType(hhDoor::Type) && !idStr::Icmp( static_cast<hhDoor *>(ent)->GetAirLockTeamName(), teamName )) {
return ent;
}
}
}
return NULL;
}
//--------------------------------
// hhModelDoor::JoinAirLockTeam
//--------------------------------
void hhModelDoor::JoinAirLockTeam( hhModelDoor *master ) {
assert( master );
airlockTeam.AddToEnd( master->airlockTeam );
}
//--------------------------------
// hhModelDoor::VerifyAirlockTeamStatus
//--------------------------------
void hhModelDoor::VerifyAirlockTeamStatus() {
if( !GetAirLockMaster() ) {
return;
}
for( hhModelDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) {
if( node != this && !node->IsClosed() ) {
gameLocal.Warning( "Airlock team '%s' has more than one member starting open", GetAirLockTeamName() );
}
}
}
//--------------------------------
// hhModelDoor::StartOpen
//--------------------------------
void hhModelDoor::StartOpen() {
VerifyAirlockTeamStatus();
bTransition = false;
bOpen = true;
OpenPortal();
SetBlocking(false);
GetAnimator()->ClearAllAnims( gameLocal.time, 0 );
GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, openAnim, gameLocal.time, 500);
Event_STATE_OpenBegin();
}
//--------------------------------
// hhModelDoor::StartClosed
//--------------------------------
void hhModelDoor::StartClosed() {
bTransition = false;
bOpen = false;
Event_STATE_ClosedBegin();
}
//--------------------------------
// hhModelDoor::Event_SpawnNewDoorTrigger
//
// All of the parts of a door have been spawned, so create
// a trigger that encloses all of them
//--------------------------------
void hhModelDoor::Event_SpawnNewDoorTrigger( void ) {
idBounds bounds,localbounds;
idBounds triggerBounds, soundTriggerBounds;
int i;
int best;
// Since models bounds are overestimated, we need to use the bounds from the
// clipmodel, which was set before the over-estimation
localbounds = GetPhysics()->GetBounds();
// Save the original bounds in case we need to crush something
crusherBounds = GetPhysics()->GetAbsBounds();
// find the thinnest axis, which will be the one we expand
best = 0;
for ( i = 1 ; i < 3 ; i++ ) {
if ( localbounds[1][ i ] - localbounds[0][ i ] < localbounds[1][ best ] - localbounds[0][ best ] ) {
best = i;
}
}
triggerBounds = localbounds;
triggerBounds[1][ best ] += triggersize;
triggerBounds[0][ best ] -= triggersize;
// Now transform into absolute coordintates
if ( GetPhysics()->GetAxis().IsRotated() ) {
bounds.FromTransformedBounds( triggerBounds, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() );
}
else {
bounds[0] = triggerBounds[0] + GetPhysics()->GetOrigin();
bounds[1] = triggerBounds[1] + GetPhysics()->GetOrigin();
}
// create a trigger with this size
idDict args;
args.Set( "mins", bounds[0].ToString(0) );
args.Set( "maxs", bounds[1].ToString(0) );
doorTrigger = ( hhDoorTrigger * ) gameLocal.SpawnEntityType( hhDoorTrigger::Type, &args );
doorTrigger->GetPhysics()->SetContents( CONTENTS_TRIGGER );
doorTrigger->door = this;
// Disable the trigger if it is no_touch and not start_open
if ( noTouch ) {
doorTrigger->Disable();
}
// ------------------------
// Create the sound trigger
// ------------------------
float soundTriggerSize = triggersize * 0.3f;
soundTriggerBounds = localbounds;
soundTriggerBounds[1][ best ] += soundTriggerSize;
soundTriggerBounds[0][ best ] -= soundTriggerSize;
// Now transform into absolute coordintates
if ( GetPhysics()->GetAxis().IsRotated() ) {
bounds.FromTransformedBounds( soundTriggerBounds, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis() );
}
else {
bounds[0] = soundTriggerBounds[0] + GetPhysics()->GetOrigin();
bounds[1] = soundTriggerBounds[1] + GetPhysics()->GetOrigin();
}
bounds[0] -= GetPhysics()->GetOrigin();
bounds[1] -= GetPhysics()->GetOrigin();
// create a trigger clip model
sndTrigger = new idClipModel( idTraceModel( bounds ) );
sndTrigger->Link( gameLocal.clip, this, 254, GetPhysics()->GetOrigin(), mat3_identity );
sndTrigger->SetContents( CONTENTS_TRIGGER );
// HACK: Also, since all the parts are now spawned, update the buddies' shaderparms
if (locked) {
float parmValue = GetDoorShaderParm( locked != 0, true );
for ( i = 0; i < buddyNames.Num(); i++ ) {
if ( buddyNames[ i ].Length() ) {
idEntity *buddy = gameLocal.FindEntity( buddyNames[ i ] );
if( buddy ) {
buddy->SetShaderParm(SHADERPARM_MODE, parmValue);
}
}
}
}
}
//--------------------------------
// hhModelDoor::ToggleDoorState
//--------------------------------
void hhModelDoor::ToggleDoorState( void ) {
if ( bOpen ) {
CloseDoor();
}
else {
OpenDoor();
}
}
//--------------------------------
// hhModelDoor::ForceAirLockTeamClosed
//--------------------------------
void hhModelDoor::ForceAirLockTeamClosed() {
if( !GetAirLockMaster() ) {
return;
}
ToggleBuddiesShaderParm( SHADERPARM_DIVERSITY, 1.0f, 0.0f, 2.0f );
for( hhModelDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) {
if( node != this && !node->IsClosed() ) {
node->CloseDoor();
}
}
}
//--------------------------------
// hhModelDoor::ForceAirLockTeamOpen
//--------------------------------
void hhModelDoor::ForceAirLockTeamOpen() {
if( !GetAirLockMaster() ) {
return;
}
for( hhModelDoor* node = GetAirLockMaster()->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) {
if( node != this && !node->IsOpen() ) {
node->OpenDoor();
}
}
}
// State code
void hhModelDoor::Event_STATE_ClosedBegin() {
// Alert any entities squished in the door
idEntity *touch[ MAX_GENTITIES ];
int contentsMask = (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_CORPSE|CONTENTS_TRIGGER); // corpses, moveableitems, spiritplayers, moveables
int num = hhUtils::EntitiesTouchingClipmodel(GetPhysics()->GetClipModel(), touch, MAX_GENTITIES, contentsMask);
for (int ix=0; ix<num; ix++) {
if (touch[ix] != this && touch[ix] != doorTrigger) {
idEntity *ent = touch[ix];
if ( doorTrigger && damage > 0.0f ) {
// HUMANHEAD mdl
// Throw spirit players back to their bodies if they get caught in a closing door
// Don't check this for MP, the damage below is enough to send them back
if ( !gameLocal.isMultiplayer && ent->IsType( hhPlayer::Type ) ) {
hhPlayer *player = reinterpret_cast< hhPlayer * > ( ent );
if ( player->IsSpiritWalking() ) {
player->StopSpiritWalk();
continue;
}
}
if( ent->fl.takedamage ) {
ent->Damage( this, this, vec3_origin, "damage_doorbonk", damage, INVALID_JOINT );
}
}
ent->SquishedByDoor(this);
}
}
SetBlocking(true);
ClosePortal();
InformDone();
if ( idleAnim ) {
GetAnimator()->CycleAnim( ANIMCHANNEL_ALL, idleAnim, gameLocal.time, 500 );
}
// If we are noTouch, then disable the trigger. (Could have been enabled by GUI activation)
if ( noTouch && doorTrigger ) {
doorTrigger->Disable();
}
// Trigger entities if we have finished spawning
if ( !finishedSpawn ) {
return;
}
ActivatePrefixed("triggerClosed", GetActivator());
if( GetAirLockMaster() ) {
// Mark the other team members as locked
for( hhModelDoor* node = airlockMaster->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) {
// Skip this door and it's buddies
if( node == this || buddyNames.Find( node->name ) ) {
continue;
}
// Don't change doors that are really locked
if( node->IsLocked() ) {
continue;
}
// Mark as locked
node->SetBuddiesShaderParm( SHADERPARM_MISC, 0.0f );
}
}
HH_ASSERT( bOpen == true && bTransition == true );
bTransition = false;
bOpen = false;
}
void hhModelDoor::Event_STATE_OpeningBegin() {
SetBlocking(false);
OpenPortal();
StartSound( "snd_open", SND_CHANNEL_ANY );
// Fire any triggerStartOpen entities
ActivatePrefixed("triggerStartOpen", GetActivator());
idEntity *ent;
idEntity *next;
for( ent = teamChain; ent != NULL; ent = next ) {
next = ent->GetTeamChain();
if ( ent && ent->IsType( hhProjectile::Type ) ) {
ent->Unbind(); // bjk drops all bound projectiles such as arrows and mines
next = teamChain;
if (ent->IsType(hhProjectileSpiritArrow::Type)) {
ent->PostEventMS(&EV_Remove, 0);
}
//HUMANHEAD bjk PCF (4-28-06) - explode crawlers
if (ent->IsType(hhProjectileStickyCrawlerGrenade::Type)) {
static_cast<hhProjectile*>(ent)->PostEventSec( &EV_Explode, 0.2f );
}
}
}
GetAnimator()->ClearAllAnims( gameLocal.time, 0 );
GetAnimator()->PlayAnim( ANIMCHANNEL_ALL, openAnim, gameLocal.time, 500);
int opentime = (openAnim) ? GetAnimator()->GetAnim( openAnim )->Length() : 0;
PostEventMS( &EV_ModelDoorOpenBegin, opentime );
if( airlockMaster ) {
// Mark the other team members as locked
for( hhModelDoor* node = airlockMaster->airlockTeam.ListHead()->Owner(); node != NULL; node = node->airlockTeam.Next() ) {
// Skip this door and it's buddies
if( node == this || buddyNames.Find( node->name ) ) {
continue;
}
// Don't change doors that are really locked
if( node->IsLocked() ) {
continue;
}
// Mark as locked
node->SetBuddiesShaderParm( SHADERPARM_MISC, 1.0f );
}
// This prevents the our shader from being set as locked from the player moving too quickly between airlock doors
SetBuddiesShaderParm( SHADERPARM_MISC, 0.0f );
}
HH_ASSERT( bOpen == false && bTransition == true );
}
void hhModelDoor::Event_STATE_OpenBegin() {
InformDone();
// If we are no touch, then we were opened by something else, so let us care about the player being in the trigger/enable the trigger
if ( noTouch && doorTrigger ) {
doorTrigger->Enable();
}
// Trigger entities if we have finished spawning
if ( !finishedSpawn ) {
return;
}
ActivatePrefixed("triggerOpened", GetActivator());
WakeTouchingEntities();
HH_ASSERT( bOpen == false && bTransition == true );
bOpen = true;
bTransition = false;
}
void hhModelDoor::Event_STATE_ClosingBegin() {
StartSound("snd_close", SND_CHANNEL_ANY);
//GetAnimator()->ClearAllAnims( gameLocal.time, 0 );
//FIXME: Despite the blend in/out here, it still pops when the idle is reapplied
GetAnimator()->PlayAnim(ANIMCHANNEL_ALL, closeAnim, gameLocal.time, 500);
int closetime = (closeAnim) ? GetAnimator()->GetAnim( closeAnim )->Length() : 0;
PostEventMS( &EV_ModelDoorClosedBegin, closetime );
HH_ASSERT( bOpen == true && bTransition == true );
}
// -------------------------------------------------------------
// Non-state code
// -------------------------------------------------------------
//--------------------------------
// hhModelDoor::Event_Blocked
//--------------------------------
void hhModelDoor::Event_TeamBlocked( idEntity *blockedEntity, idEntity *blockingEntity ) {
// reverse direction
ToggleDoorState();
}
//--------------------------------
// hhModelDoor::Event_PartBlocked
//--------------------------------
void hhModelDoor::Event_PartBlocked( idEntity *blockingEntity ) {
if ( damage > 0.0f ) {
blockingEntity->Damage( this, this, vec3_origin, "damage_doorbonk", 1.0f, INVALID_JOINT );
}
}
//--------------------------------
// hhModelDoor::Event_Activate
//--------------------------------
void hhModelDoor::Event_Activate( idEntity *activator ) {
idEntity *buddy = NULL;
if ( IsLocked() ) {
int oldLocked = locked;
Lock(0);
if ( oldLocked == 2 ) {
return;
}
ToggleDoorState();
CancelEvents( &EV_ModelDoorClose );
PostEventMS( &EV_ModelDoorClose, wait * 1000 );
}
else {
TryOpen( activator );
}
}
//--------------------------------
// hhModelDoor::WakeTouchingEntities
//--------------------------------
void hhModelDoor::WakeTouchingEntities() {
idEntity *touch[ MAX_GENTITIES ];
idEntity *ent;
int num;
num = gameLocal.clip.EntitiesTouchingBounds( GetPhysics()->GetAbsBounds(), MASK_ALL, touch, MAX_GENTITIES );
for ( int i = 0; i < num; i++ ) {
ent = touch[ i ];
if ( !ent || ent == this ) {
continue;
}
ent->ActivatePhysics(this);
}
}
//--------------------------------
// hhModelDoor::TryOpen
// If conditions are right, open. Otherwise don't open
//--------------------------------
void hhModelDoor::TryOpen( idEntity *whoTrying ) {
activatedBy = whoTrying; // Should thie be above OpenDoor
// Only allow the player to affect airlock doors
if ( GetAirLockMaster() && !whoTrying->IsType( idPlayer::Type ) ) {
return;
}
// Check if this actor is valid to open this door (if the player is spiritwalking) - cjr
if ( !whoTrying->ShouldTouchTrigger( this ) ) {
return;
}
// If we're shuttle doors, make sure we only open for actors in vehicles.
if ( bShuttleDoors && ( ! whoTrying->IsType( idActor::Type ) || ! reinterpret_cast<idActor *> ( whoTrying )->InVehicle() ) ) {
return;
}
if ( !bOpenForMonsters && whoTrying->IsType( hhMonsterAI::Type ) ) {
return;
}
// Delay Close event since we're still in the doorway
CancelEvents( &EV_ModelDoorClose );
PostEventMS( &EV_ModelDoorClose, wait * 1000 );
//This feels like ahack but I need to get this done
if( IsClosed() && !CanOpen() && GetAirLockMaster() ) {
ForceAirLockTeamClosed();
if( gameLocal.time > nextAirLockSnd ) {
int length;
StartSound( "snd_airlock", SND_CHANNEL_BODY, 0, false, &length );
nextAirLockSnd = gameLocal.time + length + airLockSndWait;
}
}
OpenDoor();
}
//--------------------------------
// hhModelDoor::EntitiesInTrigger
//--------------------------------
bool hhModelDoor::EntitiesInTrigger() {
idEntity * ents[ MAX_GENTITIES ];
idEntity * ent;
int num;
if ( !doorTrigger ) {
return( false );
}
num = doorTrigger->GetEntitiesWithin( ents, MAX_GENTITIES );
for ( int i = 0; i < num; ++i ) {
ent = ents[ i ];
if ( ent->fl.touchTriggers && ent->ShouldTouchTrigger( this ) ) {
if ( !bOpenForMonsters && ent->IsType( hhMonsterAI::Type ) ) {
continue;
}
if ( bShuttleDoors && ( ! ent->IsType( idActor::Type ) || ! reinterpret_cast<idActor *> ( ent )->InVehicle() ) ) {
continue;
}
if ( !GetAirLockMaster() || ent->IsType( idPlayer::Type ) ) { // Only allow the player to affect airlock doors
return( true );
}
}
}
return( false );
}
//--------------------------------
// hhModelDoor::Event_Touch
//--------------------------------
void hhModelDoor::Event_Touch( idEntity *other, trace_t* trace ) {
if ( sndTrigger && trace->c.id == sndTrigger->GetId() ) {
if (other && other->IsType(hhPlayer::Type) && IsLocked() && gameLocal.time > nextSndTriggerTime) {
StartSound("snd_locked", SND_CHANNEL_ANY, 0, false, NULL );
nextSndTriggerTime = gameLocal.time + 10000;
}
return;
}
// Skip if locked, or noTouch
if( IsLocked() || noTouch ) {
// play locked sound
return;
}
TryOpen( other );
}
/*
================
idModelDoor::GetActivator
================
*/
idEntity *hhModelDoor::GetActivator( void ) const {
return activatedBy.GetEntity();
}
/*
================
hhModelDoor::ClientPredictionThink
================
*/
void hhModelDoor::ClientPredictionThink( void ) {
hhAnimatedEntity::ClientPredictionThink();
}
/*
================
hhModelDoor::WriteToSnapshot
================
*/
void hhModelDoor::WriteToSnapshot( idBitMsgDelta &msg ) const {
hhAnimatedEntity::WriteToSnapshot(msg);
hhAnimator *animator = (hhAnimator *)GetAnimator();
const idAnimBlend *anim = animator->CurrentAnim(ANIMCHANNEL_ALL);
if (anim) {
msg.WriteBits(1, 1);
msg.WriteBits(anim->AnimNum(), 32);
}
else {
msg.WriteBits(0, 1);
}
msg.WriteBits(GetPhysics()->GetContents(), 32);
}
/*
================
hhModelDoor::ReadFromSnapshot
================
*/
void hhModelDoor::ReadFromSnapshot( const idBitMsgDelta &msg ) {
hhAnimatedEntity::ReadFromSnapshot(msg);
bool hasAnim = !!msg.ReadBits(1);
if (hasAnim) {
int animNum = msg.ReadBits(32);
hhAnimator *animator = (hhAnimator *)GetAnimator();
const idAnimBlend *anim = animator->CurrentAnim(ANIMCHANNEL_ALL);
if (!anim || anim->AnimNum() != animNum) {
animator->PlayAnim(ANIMCHANNEL_ALL, animNum, gameLocal.time, 500);
}
}
int contents = msg.ReadBits(32);
if (contents != GetPhysics()->GetContents()) {
GetPhysics()->SetContents(contents);
}
}
//--------------------------------
// hhModelDoor::Event_ToggleDoorState
//--------------------------------
void hhModelDoor::Event_ToggleDoorState( void ) {
ToggleDoorState();
}
//--------------------------------
// hhModelDoor::Event_OpenDoor
//--------------------------------
void hhModelDoor::Event_OpenDoor() {
OpenDoor();
}
//--------------------------------
// hhModelDoor::Event_CloseDoor
//--------------------------------
void hhModelDoor::Event_CloseDoor() {
// Added due to the fact that monsters which don't move, also don't ActivateTriggers.
// So now we check before we close if we can actually close
// We can't actually close if are transitioning or if we have someone in our trigger that we care about
if ( bTransition || EntitiesInTrigger() ) {
// So if someone here, cancel any further events
CancelEvents( &EV_ModelDoorClose );
// And try again in a bit.
PostEventMS( &EV_ModelDoorClose, wait * 1000 );
return;
}
CloseDoor();
}
//--------------------------------
// hhModelDoor::Event_SetCallback
//--------------------------------
void hhModelDoor::Event_SetCallback( void ) {
if ( !threadNum && bTransition ) {
threadNum = idThread::CurrentThreadNum();
idThread::ReturnInt( true );
} else {
idThread::ReturnInt( false );
}
}
//--------------------------------
// hhModelDoor::Event_SetBuddiesShaderParm
//--------------------------------
void hhModelDoor::Event_SetBuddiesShaderParm( int parm, float value ) {
SetBuddiesShaderParm( parm, value );
}