#include "cbase.h" #include "BasePropDoor.h" #include "ai_basenpc.h" #include "npcevent.h" #include "engine/IEngineSound.h" #include "asw_door.h" #include "locksounds.h" #include "filters.h" #include "physics.h" #include "vphysics_interface.h" #include "entityoutput.h" #include "vcollide_parse.h" #include "studio.h" #include "explode.h" #include "utlrbtree.h" #include "physics_impact_damage.h" #include "KeyValues.h" #include "filesystem.h" #include "scriptevent.h" #include "entityblocker.h" #include "soundent.h" #include "EntityFlame.h" #include "game.h" #include "physics_prop_ragdoll.h" #include "decals.h" #include "hierarchy.h" #include "shareddefs.h" #include "physobj.h" #include "physics_npc_solver.h" #include "SoundEmitterSystem/isoundemittersystembase.h" #include "doors.h" #include "asw_marine.h" #include "asw_fx_shared.h" #include "te_effect_dispatch.h" #include "asw_generic_emitter_entity.h" #include "asw_door_padding.h" #include "asw_marine_speech.h" #include "asw_game_resource.h" #include "asw_marine_resource.h" #include "asw_shareddefs.h" #include "asw_weapon_welder_shared.h" #include "coordsize.h" #include "asw_trace_filter_door_crush.h" #include "asw_player.h" #include "cvisibilitymonitor.h" #include "particle_parse.h" #include "asw_fail_advice.h" #include "asw_gamerules.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" BEGIN_DATADESC(CASW_Door) DEFINE_KEYFIELD(m_eSpawnPosition, FIELD_INTEGER, "spawnpos"), DEFINE_KEYFIELD(m_flDistance, FIELD_FLOAT, "distance"), DEFINE_KEYFIELD(m_angSlideAngle, FIELD_VECTOR, "slideangle"), DEFINE_KEYFIELD(m_flTotalSealTime, FIELD_FLOAT, "totalsealtime"), DEFINE_KEYFIELD(m_flCurrentSealTime, FIELD_FLOAT, "currentsealtime"), DEFINE_KEYFIELD(m_iDoorType, FIELD_INTEGER, "doortype"), DEFINE_KEYFIELD(m_DentAmount, FIELD_INTEGER, "dentamount"), DEFINE_KEYFIELD(m_bShowsOnScanner, FIELD_BOOLEAN, "showsonscanner" ), DEFINE_KEYFIELD(m_bAutoOpen, FIELD_BOOLEAN, "autoopen" ), DEFINE_KEYFIELD(m_bCanCloseToWeld, FIELD_BOOLEAN, "CanCloseToWeld" ), DEFINE_KEYFIELD(m_bDoCutShout, FIELD_BOOLEAN, "DoCutShout" ), DEFINE_KEYFIELD(m_bDoBreachedShout, FIELD_BOOLEAN, "DoBreachedShout" ), DEFINE_KEYFIELD(m_bDoAutoShootChatter, FIELD_BOOLEAN, "DoAutoShootChatter" ), DEFINE_FIELD( m_bBashable, FIELD_BOOLEAN ), DEFINE_FIELD( m_bShootable, FIELD_BOOLEAN ), DEFINE_FIELD( m_vecGoal, FIELD_VECTOR ), DEFINE_FIELD( m_hDoorBlocker, FIELD_EHANDLE ), DEFINE_FIELD( m_hDoorPadding, FIELD_EHANDLE ), DEFINE_FIELD( m_fClosingToWeldTime, FIELD_FLOAT ), DEFINE_FIELD(m_bHasBeenWelded, FIELD_BOOLEAN ), DEFINE_FIELD( m_bFlipped, FIELD_BOOLEAN ), DEFINE_FIELD( m_bSetSide, FIELD_BOOLEAN ), DEFINE_FIELD( m_vecOpenPosition, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_vecClosedPosition, FIELD_POSITION_VECTOR ), DEFINE_FIELD( m_vecBoundsMin, FIELD_VECTOR ), DEFINE_FIELD( m_vecBoundsMax, FIELD_VECTOR ), DEFINE_FIELD( m_fLastMarineShootTime, FIELD_TIME ), DEFINE_FIELD( m_fMarineShootCounter, FIELD_FLOAT ), DEFINE_FIELD( m_bDoneDoorShout, FIELD_BOOLEAN ), DEFINE_FIELD( m_fLastMomentFlipDamage, FIELD_FLOAT ), DEFINE_FIELD( m_fSkillMarineHelping, FIELD_TIME ), DEFINE_FIELD( m_bSkillMarineHelping, FIELD_BOOLEAN ), DEFINE_FIELD( m_bDoorFallen, FIELD_BOOLEAN ), DEFINE_FIELD( m_bRecommendedSeal, FIELD_BOOLEAN ), DEFINE_FIELD( m_bWasWeldedByMarine, FIELD_BOOLEAN ), DEFINE_THINKFUNC( RunAnimation ), DEFINE_INPUTFUNC( FIELD_VOID, "NPCNear", InputNPCNear ), DEFINE_INPUTFUNC( FIELD_VOID, "EnableAutoOpen", InputEnableAutoOpen ), DEFINE_INPUTFUNC( FIELD_VOID, "DisableAutoOpen", InputDisableAutoOpen ), DEFINE_INPUTFUNC( FIELD_VOID, "RecommendWeld", InputRecommendWeld ), DEFINE_OUTPUT( m_OnFullySealed, "OnFullySealed" ), DEFINE_OUTPUT( m_OnFullyCut, "OnFullyCut" ), DEFINE_OUTPUT( m_OnDestroyed, "OnDestroyed" ), END_DATADESC() IMPLEMENT_SERVERCLASS_ST(CASW_Door, DT_ASW_Door) SendPropFloat (SENDINFO(m_flTotalSealTime)), SendPropFloat (SENDINFO(m_flCurrentSealTime)), SendPropInt (SENDINFO(m_iDoorStrength)), SendPropInt (SENDINFO(m_iDoorType)), SendPropInt (SENDINFO(m_iHealth)), SendPropInt (SENDINFO(m_lifeState)), SendPropBool (SENDINFO(m_bAutoOpen)), SendPropBool (SENDINFO(m_bBashable)), SendPropBool (SENDINFO(m_bShootable)), SendPropBool (SENDINFO(m_bCanCloseToWeld)), SendPropBool (SENDINFO(m_bRecommendedSeal)), SendPropBool (SENDINFO(m_bWasWeldedByMarine)), SendPropFloat (SENDINFO(m_fLastMomentFlipDamage)), SendPropVector (SENDINFO(m_vecClosedPosition), -1, SPROP_COORD), SendPropBool (SENDINFO(m_bSkillMarineHelping)), END_SEND_TABLE() LINK_ENTITY_TO_CLASS(asw_door, CASW_Door); ConVar asw_door_drone_damage_scale("asw_door_drone_damage_scale", "2.0f", FCVAR_CHEAT, "Damage scale for drones hitting doors"); ConVar asw_door_seal_damage_reduction("asw_door_seal_damage_reduction", "0.6f", FCVAR_CHEAT, "Alien damage scale when door is fully sealed"); ConVar asw_door_physics("asw_door_physics", "0", FCVAR_CHEAT, "If set, doors will turn into vphysics objects upon death."); extern ConVar asw_debug_marine_chatter; extern ConVar asw_difficulty_alien_damage_step; #define SINGLE_DOOR "models/swarm/doors/swarm_singledoor.mdl" #define RIGHT_DOOR "models/props/doors/heavy_doors/doorright.mdl" #define LEFT_DOOR "models/props/doors/heavy_doors/doorleft.mdl" #define SINGLE_DOOR_FLIPPED "models/swarm/doors/swarm_singledoor_flipped.mdl" static const char *s_pDoorAnimThink = "DoorAnimThink"; enum { AE_DOOR_FALL = 1, // The door has fallen }; namespace { void UTIL_ComputeAABBForBounds( const Vector &mins1, const Vector &maxs1, const Vector &mins2, const Vector &maxs2, Vector *destMins, Vector *destMaxs ) { // Find the minimum extents (*destMins)[0] = MIN( mins1[0], mins2[0] ); (*destMins)[1] = MIN( mins1[1], mins2[1] ); (*destMins)[2] = MIN( mins1[2], mins2[2] ); // Find the maximum extents (*destMaxs)[0] = MAX( maxs1[0], maxs2[0] ); (*destMaxs)[1] = MAX( maxs1[1], maxs2[1] ); (*destMaxs)[2] = MAX( maxs1[2], maxs2[2] ); } } CASW_Door::~CASW_Door( void ) { // Remove our door blocker entity if ( m_hDoorBlocker != NULL ) { UTIL_Remove( m_hDoorBlocker ); } if ( m_hDoorPadding != NULL ) { UTIL_Remove( m_hDoorPadding ); } } void CASW_Door::Precache() { PrecacheScriptSound( "ASW_Door.Dented" ); PrecacheScriptSound( "ASW_Door.MeleeHit" ); PrecacheScriptSound( "ASW_Welder.WeldDeny" ); PrecacheModel(SINGLE_DOOR); PrecacheModel(SINGLE_DOOR_FLIPPED); PrecacheModel(RIGHT_DOOR); PrecacheModel(LEFT_DOOR); BaseClass::Precache(); } void CASW_Door::Spawn() { if (m_flDistance == 0) { m_flDistance = 90; } m_fLastMomentFlipDamage = 0; m_iFallingStage = 0; m_flDistance = fabs(m_flDistance); const char * szModelName = STRING( GetModelName() ); m_bRotateOnFlip = !Q_stricmp( szModelName, SINGLE_DOOR ); // Calculate our closed and open positions m_vecClosedPosition = GetAbsOrigin(); QAngle door_angle = GetLocalAngles(); door_angle += m_angSlideAngle; Vector v(m_flDistance, 0, 0); matrix3x4_t door_angle_matrix; Vector offset; AngleMatrix( door_angle, door_angle_matrix ); VectorRotate( v, door_angle_matrix, offset ); m_vecOpenPosition = GetAbsOrigin() + offset; m_fClosingToWeldTime = 0; m_nHardwareType = -1; // Suppress base class warnings m_nPhysicsMaterial = physprops->GetSurfaceIndex( "metal" ); // Call this last! It relies on stuff we calculated above. BaseClass::Spawn(); // Figure out our volumes of movement as this door opens CalculateDoorVolume( m_vecOpenPosition, m_vecClosedPosition, &m_vecBoundsMin, &m_vecBoundsMax ); if (m_flTotalSealTime <= 0) m_flTotalSealTime = 10.0f; // set door strength and skin according to breakable or not: if (m_iDoorType == 0) // normal weak door { m_bBashable = true; m_bShootable = true; m_iDoorStrength = ASW_DOOR_NORMAL_HEALTH; m_nSkin = 0; } else if (m_iDoorType == 1) { m_bBashable = true; m_bShootable = true; m_iDoorStrength = ASW_DOOR_REINFORCED_HEALTH; m_nSkin = 1; } else if (m_iDoorType == 2) // indestructable { m_bBashable = false; m_bShootable = false; m_iDoorStrength = 0; m_nSkin = 2; } //m_iDoorStrength = 100; //temp for testing m_bSetSide = false; m_bFlipped = false; m_bDoneChatter = false; m_fChatterCounter = 0; if ( m_DentAmount == ASWDD_PARTIAL_PREFLIP ) { m_DentAmount = ASWDD_PARTIAL; FlipDoor(); } else if ( m_DentAmount == ASWDD_COMPLETE_PREFLIP ) { m_DentAmount = ASWDD_COMPLETE; FlipDoor(); } if ( m_iDoorStrength > 0 ) { m_takedamage = DAMAGE_YES; m_iHealth = m_iDoorStrength; if ( m_DentAmount == ASWDD_PARTIAL ) { m_iHealth = ASW_DOOR_PARTIAL_DENT_HEALTH * m_iDoorStrength; SetDentSequence(); } else if ( m_DentAmount == ASWDD_COMPLETE ) { m_iHealth = ASW_DOOR_COMPLETE_DENT_HEALTH * m_iDoorStrength; SetDentSequence(); } SetMaxHealth(m_iHealth); } else { m_takedamage = DAMAGE_YES; // has to receive damage events so marines can be informed they're shooting a junk item m_iHealth = 1; } // if this door isn't sealed, we don't do cut shouting if ( m_flCurrentSealTime <= 0 ) { m_bDoCutShout = false; } m_fLastFullyWeldedSound = 0; // create our padding: m_hDoorPadding = CASW_Door_Padding::CreateDoorPadding(this); if ( VPhysicsGetObject() ) { VPhysicsGetObject()->SetMaterialIndex( physprops->GetSurfaceIndex("metal") ); } SetContextThink( &CASW_Door::RunAnimation, gpGlobals->curtime + 0.1f, s_pDoorAnimThink ); if ( m_flCurrentSealTime > 0.0f ) { VisibilityMonitor_AddEntity( this, asw_visrange_generic.GetFloat() * 0.9f, &CASW_Door::WeldedVismonCallback, NULL ); } else { VisibilityMonitor_AddEntity( this, asw_visrange_generic.GetFloat() * 0.9f, &CASW_Door::DestroyVismonCallback, &CASW_Door::DestroyVismonEvaluator ); } } bool CASW_Door::DestroyVismonEvaluator( CBaseEntity *pVisibleEntity, CBasePlayer *pViewingPlayer ) { CASW_Door *pDoor = static_cast< CASW_Door* >( pVisibleEntity ); if ( !pDoor->m_bShootable ) return false; if ( pDoor->m_bDoAutoShootChatter ) return true; if ( pDoor->m_iHealth <= 0 ) return false; if ( static_cast( pDoor->m_iHealth ) / pDoor->GetMaxHealth() > 0.4f ) return false; return true; } bool CASW_Door::DestroyVismonCallback( CBaseEntity *pVisibleEntity, CBasePlayer *pViewingPlayer ) { IGameEvent * event = gameeventmanager->CreateEvent( "door_recommend_destroy" ); if ( event ) { event->SetInt( "userid", pViewingPlayer->GetUserID() ); event->SetInt( "entindex", pVisibleEntity->entindex() ); gameeventmanager->FireEvent( event ); } return false; } bool CASW_Door::WeldedVismonCallback( CBaseEntity *pVisibleEntity, CBasePlayer *pViewingPlayer ) { IGameEvent * event = gameeventmanager->CreateEvent( "door_welded_visible" ); if ( event ) { event->SetInt( "userid", pViewingPlayer->GetUserID() ); event->SetInt( "subject", pVisibleEntity->entindex() ); event->SetString( "entityname", STRING( pVisibleEntity->GetEntityName() ) ); gameeventmanager->FireEvent( event ); } return false; } void CASW_Door::RunAnimation() { m_flPlaybackRate = 1.0f; StudioFrameAdvance(); DispatchAnimEvents( this ); SetContextThink( &CASW_Door::RunAnimation, gpGlobals->curtime + 0.1f, s_pDoorAnimThink ); m_bSkillMarineHelping = (m_fSkillMarineHelping >= gpGlobals->curtime - 0.2f); } // is this door open or not? bool CASW_Door::IsOpen( void ) { //return ( m_vecGoal == m_vecOpenPosition ); Vector diff = GetAbsOrigin() - m_vecClosedPosition; float dist = diff.LengthSqr(); return (dist > 2); } bool CASW_Door::IsMoving() { return GetLocalVelocity().LengthSqr() > 0; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CASW_Door::OnDoorOpened( void ) { if ( m_hDoorBlocker != NULL ) { // Allow passage through this blocker while open m_hDoorBlocker->AddSolidFlags( FSOLID_NOT_SOLID ); if ( g_debug_doors.GetBool() ) { NDebugOverlay::Box( GetAbsOrigin(), m_hDoorBlocker->CollisionProp()->OBBMins(), m_hDoorBlocker->CollisionProp()->OBBMaxs(), 0, 255, 0, true, 1.0f ); } } if ( m_hDoorPadding != NULL ) { m_hDoorPadding->AddSolidFlags( FSOLID_NOT_SOLID ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CASW_Door::OnDoorClosed( void ) { if ( m_hDoorBlocker != NULL ) { // Destroy the blocker that was preventing NPCs from getting in our way. UTIL_Remove( m_hDoorBlocker ); if ( g_debug_doors.GetBool() ) { NDebugOverlay::Box( GetAbsOrigin(), m_hDoorBlocker->CollisionProp()->OBBMins(), m_hDoorBlocker->CollisionProp()->OBBMaxs(), 0, 255, 0, true, 1.0f ); } } if ( m_hDoorPadding != NULL ) { m_hDoorPadding->RemoveSolidFlags( FSOLID_NOT_SOLID ); } } //----------------------------------------------------------------------------- // Purpose: Returns whether the way is clear for the door to close. // Input : state - Which sides to check, forward, backward, or both. // Output : Returns true if the door can close, false if the way is blocked. //----------------------------------------------------------------------------- bool CASW_Door::DoorCanClose( bool bAutoClose ) { if ( GetMaster() != NULL ) return GetMaster()->DoorCanClose( bAutoClose ); // Check all slaves if ( HasSlaves() ) { int numDoors = m_hDoorList.Count(); CASW_Door *pLinkedDoor = NULL; // Check all links as well for ( int i = 0; i < numDoors; i++ ) { pLinkedDoor = dynamic_cast((CBasePropDoor *)m_hDoorList[i]); if ( pLinkedDoor != NULL ) { if ( !pLinkedDoor->CheckDoorClear() ) return false; } } } // See if our path of movement is clear to allow us to shut return CheckDoorClear(); } void CASW_Door::CalculateDoorVolume( Vector OpenPosition, Vector ClosedPosition, Vector *destMins, Vector *destMaxs ) { // Save our current position and move to our start position Vector savePosition = GetAbsOrigin(); SetAbsOrigin(ClosedPosition); // Find our AABB at the closed state Vector closedMins, closedMaxs; CollisionProp()->WorldSpaceAABB( &closedMins, &closedMaxs ); SetAbsOrigin(OpenPosition); // Find our AABB at the open state Vector openMins, openMaxs; CollisionProp()->WorldSpaceAABB( &openMins, &openMaxs ); // Reset our position to our starting position SetAbsOrigin(savePosition); // Find the minimum extents UTIL_ComputeAABBForBounds( closedMins, closedMaxs, openMins, openMaxs, destMins, destMaxs ); // Move this back into local space *destMins -= GetAbsOrigin(); *destMaxs -= GetAbsOrigin(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CASW_Door::OnRestore( void ) { BaseClass::OnRestore(); // Figure out our volumes of movement as this door opens CalculateDoorVolume( m_vecOpenPosition, m_vecClosedPosition, &m_vecBoundsMin, &m_vecBoundsMax ); } class CASWTraceFilterDoor : public CTraceFilterEntitiesOnly { public: // It does have a base, but we'll never network anything below here.. DECLARE_CLASS_NOBASE( CASWTraceFilterDoor ); CASWTraceFilterDoor( const IHandleEntity *pDoor, const IHandleEntity *passentity, int collisionGroup ) : m_pDoor(pDoor), m_pPassEnt(passentity), m_collisionGroup(collisionGroup) { } virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask ) { if ( !StandardFilterRules( pHandleEntity, contentsMask ) ) return false; if ( !PassServerEntityFilter( pHandleEntity, m_pDoor ) ) return false; if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnt ) ) return false; // Don't test if the game code tells us we should ignore this collision... CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity ); if ( pEntity ) { if ( !pEntity->ShouldCollide( m_collisionGroup, contentsMask ) ) return false; if ( !g_pGameRules->ShouldCollide( m_collisionGroup, pEntity->GetCollisionGroup() ) ) return false; // If objects are small enough and can move, close on them if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS ) { IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject(); Assert(pPhysics); // Must either be squashable or very light if ( pPhysics->IsMoveable() && pPhysics->GetMass() < 32 ) return false; } } return true; } private: const IHandleEntity *m_pDoor; const IHandleEntity *m_pPassEnt; int m_collisionGroup; }; inline void TraceHull_Door( const CBasePropDoor *pDoor, const Vector &vecAbsStart, const Vector &vecAbsEnd, const Vector &hullMin, const Vector &hullMax, unsigned int mask, const CBaseEntity *ignore, int collisionGroup, trace_t *ptr ) { Ray_t ray; ray.Init( vecAbsStart, vecAbsEnd, hullMin, hullMax ); CASWTraceFilterDoor traceFilter( pDoor, ignore, collisionGroup ); enginetrace->TraceRay( ray, mask, &traceFilter, ptr ); } //----------------------------------------------------------------------------- // Purpose: // Input : forward - // mask - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CASW_Door::CheckDoorClear() { // Look for blocking entities, ignoring ourselves and the entity that opened us. Vector vecClosed = m_vecClosedPosition; trace_t tr; TraceHull_Door( this, vecClosed, vecClosed, m_vecBoundsMin, m_vecBoundsMax, MASK_SOLID, GetActivator(), COLLISION_GROUP_NONE, &tr ); if ( tr.allsolid || tr.startsolid ) { if ( g_debug_doors.GetBool() ) { NDebugOverlay::Box( vecClosed, m_vecBoundsMin, m_vecBoundsMax, 255, 0, 0, true, 10.0f ); if ( tr.m_pEnt ) { NDebugOverlay::Box( tr.m_pEnt->GetAbsOrigin(), tr.m_pEnt->CollisionProp()->OBBMins(), tr.m_pEnt->CollisionProp()->OBBMaxs(), 220, 220, 0, true, 10.0f ); } } return false; } if ( g_debug_doors.GetBool() ) { NDebugOverlay::Box( vecClosed, m_vecBoundsMin, m_vecBoundsMax, 0, 255, 0, true, 10.0f ); } return true; } //----------------------------------------------------------------------------- // Purpose: Puts the door in its appropriate position for spawning. //----------------------------------------------------------------------------- void CASW_Door::DoorTeleportToSpawnPosition() { Vector vecSpawn; // The Start Open spawnflag trumps the choices field if ( HasSpawnFlags( SF_DOOR_START_OPEN_OBSOLETE ) || m_eSpawnPosition == DOOR_SPAWN_OPEN ) { vecSpawn = m_vecOpenPosition; SetDoorState( DOOR_STATE_OPEN ); } else if ( m_eSpawnPosition == DOOR_SPAWN_CLOSED ) { vecSpawn = m_vecClosedPosition; SetDoorState( DOOR_STATE_CLOSED ); } else { // Bogus spawn position setting! Assert( false ); vecSpawn = m_vecClosedPosition; SetDoorState( DOOR_STATE_CLOSED ); } SetAbsOrigin( vecSpawn ); // Doesn't relink; that's done in Spawn. } //----------------------------------------------------------------------------- // Purpose: After moving, set position to the exact final position, call "move done" function. //----------------------------------------------------------------------------- void CASW_Door::MoveDone() { SetAbsOrigin(m_vecGoal); SetLocalVelocity(vec3_origin); SetMoveDoneTime(-1); BaseClass::MoveDone(); } //----------------------------------------------------------------------------- // Purpose: Calculate m_vecVelocity and m_flNextThink to reach vecDest from // GetLocalOrigin() traveling at flSpeed. Just like LinearMove, but rotational. // Input : vecDestPosition - // flSpeed - //----------------------------------------------------------------------------- void CASW_Door::SlideMove(const Vector &vecDestPosition, float flSpeed) { ASSERTSZ(flSpeed != 0, "SlideMove: no speed is defined!"); // no moving if our door is dead //if (m_lifeState = LIFE_DEAD) //{ //MoveDone(); //return; //} m_vecGoal = vecDestPosition; // Already there? if (vecDestPosition == GetAbsOrigin()) { MoveDone(); return; } // Set destdelta to the vector needed to move. Vector vecDestDelta = vecDestPosition - GetAbsOrigin(); // Divide by speed to get time to reach dest float flTravelTime = vecDestDelta.Length() / flSpeed; // Call MoveDone when destination position is reached. SetMoveDoneTime(flTravelTime); // Scale the destdelta vector by the time spent traveling to get velocity. SetLocalVelocity(vecDestDelta * (1.0 / flTravelTime)); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CASW_Door::BeginOpening(CBaseEntity *pOwner) { Vector vecOpen = m_vecOpenPosition; // turn off our padding, so it doesn't drag marines along if ( m_hDoorPadding != NULL ) { m_hDoorPadding->AddSolidFlags( FSOLID_NOT_SOLID ); } // Create the door blocker Vector mins, maxs; mins = m_vecBoundsMin; maxs = m_vecBoundsMax; if ( m_hDoorBlocker != NULL ) { UTIL_Remove( m_hDoorBlocker ); } // Create a blocking entity to keep random entities out of our movement path m_hDoorBlocker = CEntityBlocker::Create( GetAbsOrigin(), mins, maxs, pOwner, false ); Vector volumeCenter = ((mins+maxs) * 0.5f) + GetAbsOrigin(); // Ignoring the Z float volumeRadius = MAX( fabs(mins.x), maxs.x ); volumeRadius = MAX( volumeRadius, MAX( fabs(mins.y), maxs.y ) ); // Debug if ( g_debug_doors.GetBool() ) { NDebugOverlay::Cross3D( volumeCenter, -Vector(volumeRadius,volumeRadius,volumeRadius), Vector(volumeRadius,volumeRadius,volumeRadius), 255, 0, 0, true, 1.0f ); } // Make respectful entities move away from our path CSoundEnt::InsertSound( SOUND_MOVE_AWAY, volumeCenter, volumeRadius, 0.5f, pOwner ); // Do final setup if ( m_hDoorBlocker != NULL ) { // Only block NPCs m_hDoorBlocker->SetCollisionGroup( COLLISION_GROUP_DOOR_BLOCKER ); // If we hit something while opening, just stay unsolid until we try again if ( CheckDoorClear() == false ) { m_hDoorBlocker->AddSolidFlags( FSOLID_NOT_SOLID ); } if ( g_debug_doors.GetBool() ) { NDebugOverlay::Box( GetAbsOrigin(), m_hDoorBlocker->CollisionProp()->OBBMins(), m_hDoorBlocker->CollisionProp()->OBBMaxs(), 255, 0, 0, true, 1.0f ); } } SlideMove(vecOpen, m_flSpeed); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CASW_Door::BeginClosing( void ) { if ( m_hDoorBlocker != NULL ) { // Become solid again unless we're already being blocked if ( CheckDoorClear() ) { m_hDoorBlocker->RemoveSolidFlags( FSOLID_NOT_SOLID ); } if ( g_debug_doors.GetBool() ) { NDebugOverlay::Box( GetAbsOrigin(), m_hDoorBlocker->CollisionProp()->OBBMins(), m_hDoorBlocker->CollisionProp()->OBBMaxs(), 255, 0, 0, true, 1.0f ); } } SlideMove(m_vecClosedPosition, m_flSpeed); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CASW_Door::DoorStop( void ) { SetLocalVelocity( vec3_origin ); SetMoveDoneTime( -1 ); } //----------------------------------------------------------------------------- // Purpose: Restart a door moving that was temporarily paused //----------------------------------------------------------------------------- void CASW_Door::DoorResume( void ) { // Restart our linear movement SlideMove( m_vecGoal, m_flSpeed ); } //----------------------------------------------------------------------------- // Purpose: // Input : vecMoveDir - // opendata - //----------------------------------------------------------------------------- void CASW_Door::GetNPCOpenData(CAI_BaseNPC *pNPC, opendata_t &opendata) { // dvs: TODO: finalize open position, direction, activity Vector vecForward; Vector vecRight; AngleVectors(GetAbsAngles(), &vecForward, &vecRight, NULL); // // Figure out where the NPC should stand to open this door, // and what direction they should face. // opendata.vecStandPos = GetAbsOrigin() - (vecRight * 24); opendata.vecStandPos.z -= 54; Vector vecNPCOrigin = pNPC->GetAbsOrigin(); if (pNPC->GetAbsOrigin().Dot(vecForward) > GetAbsOrigin().Dot(vecForward)) { // In front of the door relative to the door's forward vector. opendata.vecStandPos += vecForward * 64; opendata.vecFaceDir = -vecForward; } else { // Behind the door relative to the door's forward vector. opendata.vecStandPos -= vecForward * 64; opendata.vecFaceDir = vecForward; } opendata.eActivity = ACT_OPEN_DOOR; } //----------------------------------------------------------------------------- // Purpose: Returns how long it will take this door to open. //----------------------------------------------------------------------------- float CASW_Door::GetOpenInterval() { // set destdelta to the vector needed to move Vector vecDestDelta = m_vecOpenPosition - GetAbsOrigin(); // divide by speed to get time to reach dest return vecDestDelta.Length() / m_flSpeed; } //----------------------------------------------------------------------------- // Purpose: Draw any debug text overlays // Output : Current text offset from the top //----------------------------------------------------------------------------- int CASW_Door::DrawDebugTextOverlays(void) { int text_offset = BaseClass::DrawDebugTextOverlays(); if (m_debugOverlays & OVERLAY_TEXT_BIT) { char tempstr[512]; Q_snprintf(tempstr, sizeof(tempstr),"Avelocity: %.2f %.2f %.2f", GetLocalVelocity().x, GetLocalVelocity().y, GetLocalVelocity().z); NDebugOverlay::EntityText(entindex(), text_offset, tempstr, 0); text_offset++; if ( IsDoorOpen() ) { Q_strncpy(tempstr, "DOOR STATE: OPEN", sizeof(tempstr)); } else if ( IsDoorClosed() ) { Q_strncpy(tempstr, "DOOR STATE: CLOSED", sizeof(tempstr)); } else if ( IsDoorOpening() ) { Q_strncpy(tempstr, "DOOR STATE: OPENING", sizeof(tempstr)); } else if ( IsDoorClosing() ) { Q_strncpy(tempstr, "DOOR STATE: CLOSING", sizeof(tempstr)); } else if ( IsDoorAjar() ) { Q_strncpy(tempstr, "DOOR STATE: AJAR", sizeof(tempstr)); } NDebugOverlay::EntityText(entindex(), text_offset, tempstr, 0); text_offset++; } return text_offset; } void CASW_Door::InputNPCNear( inputdata_t &inputdata ) { // a marine or an alien has come near the door Msg("[S] NPC near door\n"); } void CASW_Door::ActivateUseIcon( CASW_Marine* pMarine, int nHoldType ) { if ( nHoldType == ASW_USE_HOLD_START ) return; // player has used this item } // returns how sealed this door is, from 0 to 1.0 float CASW_Door::GetSealAmount() { if ( m_flTotalSealTime <= 0 ) return 0; return ( m_flCurrentSealTime / m_flTotalSealTime ); } void CASW_Door::SetCurrentSealTime( float fTime ) { m_flCurrentSealTime = fTime; } void CASW_Door::SetTotalSealTime(float fTime) { m_flTotalSealTime = fTime; } bool CASW_Door::IsDoorLocked() { bool bDented = ( m_DentAmount > ASWDD_PARTIAL ); if ( !bDented && HasSlaves() ) { int numDoors = m_hDoorList.Count(); for ( int i = 0; i < numDoors; i++ ) { CASW_Door *pSlave = dynamic_cast( m_hDoorList[ i ].Get() ); if ( pSlave && pSlave->m_DentAmount > ASWDD_PARTIAL ) { bDented = true; break; } } } return BaseClass::IsDoorLocked() || bDented || ( GetCurrentSealTime() > 0 ); } void CASW_Door::WeldDoor( bool bSeal, float fAmount, CASW_Marine *pMarine ) { if ( !pMarine ) return; float fCurrentSealTime = GetCurrentSealTime(); if ( !bSeal ) { fAmount = -fAmount; } else { m_bWasWeldedByMarine = true; } // once a door has been welded a bit, don't have anyone complain about it being sealed m_bDoCutShout = false; if ( m_fChatterCounter * fAmount < 0 ) // reset the chatter counter if we switch weld direction { m_fChatterCounter = 0; m_bDoneChatter = false; } if ( !m_bDoneChatter ) { m_fChatterCounter += fAmount; if ( m_fChatterCounter > 1.0f ) { pMarine->GetMarineSpeech()->Chatter( CHATTER_SEALING_DOOR ); m_bDoneChatter = true; } else if ( m_fChatterCounter < -1.0f ) { pMarine->GetMarineSpeech()->Chatter( CHATTER_CUTTING_DOOR ); m_bDoneChatter = true; } } float fNewTime = fCurrentSealTime + fAmount; if ( fNewTime <= 0 ) { if ( fCurrentSealTime > 0) { m_OnFullyCut.FireOutput(pMarine, this); // door completely opened if (IsAutoOpen()) { variant_t emptyVariant; AcceptInput("Open", pMarine, this, emptyVariant, 0); } } fNewTime = 0; } if ( fNewTime >= GetTotalSealTime() ) { if ( fCurrentSealTime < fNewTime ) { // door is completely sealed m_OnFullySealed.FireOutput(pMarine, this); } //Msg(" Fully sealed, cur=%f last=%f inh=%d commander=%d\n", gpGlobals->curtime, m_fLastFullyWeldedSound, //pMarine->IsInhabited(), pMarine->GetCommander() != NULL ); if ( gpGlobals->curtime > ( m_fLastFullyWeldedSound + 1.0f ) && pMarine->IsInhabited() && pMarine->GetCommander() ) { m_fLastFullyWeldedSound = gpGlobals->curtime; //Msg("Playing deny sound time=%f!\n", m_fLastFullyWeldedSound); // play a deny sound to the marine's owner pMarine->GetCommander()->EmitPrivateSound( "ASW_Welder.WeldDeny", true ); } fNewTime = GetTotalSealTime(); } if ( fCurrentSealTime != fNewTime ) { // sparks now spawn from the welder weapon if ( fCurrentSealTime > 0.0f && fNewTime <= 0.0f ) { // Door is now unsealed! IGameEvent * event = gameeventmanager->CreateEvent( "door_unwelded" ); if ( event ) { CASW_Player *pPlayer = NULL; pPlayer = pMarine->GetCommander(); event->SetInt( "userid", ( pPlayer ? pPlayer->GetUserID() : 0 ) ); event->SetInt( "entindex", entindex() ); gameeventmanager->FireEvent( event ); } } else if ( fCurrentSealTime < 1.0f && fNewTime >= 1.0f ) { // Door is now sealed! IGameEvent * event = gameeventmanager->CreateEvent( "door_welded" ); if ( event ) { CASW_Player *pPlayer = NULL; pPlayer = pMarine->GetCommander(); event->SetInt( "userid", ( pPlayer ? pPlayer->GetUserID() : 0 ) ); event->SetInt( "entindex", entindex() ); event->SetInt( "inhabited", pMarine->IsInhabited() ); gameeventmanager->FireEvent( event ); } } SetCurrentSealTime( fNewTime ); if ( !m_bHasBeenWelded && bSeal && m_flCurrentSealTime > 0.0f ) { m_bHasBeenWelded = true; ASWFailAdvice()->OnMarineWeldedDoor(); } } } bool CASW_Door::CloseForWeld(CASW_Marine* pMarine) { if (m_bCanCloseToWeld) { variant_t emptyVariant; AcceptInput("Close", pMarine, this, emptyVariant, 0); m_fClosingToWeldTime = gpGlobals->curtime + 1.0f; return true; } return false; } void CASW_Door::AutoOpen(CBaseEntity* pOther) { if (gpGlobals->curtime > m_fClosingToWeldTime) { variant_t emptyVariant; AcceptInput("Open", pOther, this, emptyVariant, 0); } } // the point a marine should look to weld this door // sparks come from here also #define DOOR_CORNER_DISTANCE 62.0f #define DOOR_HEIGHT 135.0f Vector CASW_Door::GetWeldFacingPoint(CBaseEntity* pOther) { // work out which side of the door the marine is on Vector diff = pOther->GetAbsOrigin() - GetAbsOrigin(); VectorNormalize(diff); QAngle angDoorFacing = GetAbsAngles(); Vector vecDoorFacing = vec3_origin; AngleVectors(angDoorFacing, &vecDoorFacing); bool bFrontSide = (DotProduct(vecDoorFacing, diff) > 0); // depending on the side, get one of the corners angDoorFacing.y -= bFrontSide ? 81 : 102; AngleVectors(angDoorFacing, &vecDoorFacing); Vector result = GetAbsOrigin() + vecDoorFacing * DOOR_CORNER_DISTANCE; // correct by height result.z += DOOR_HEIGHT * (1.0f - GetSealAmount()); return result; } bool CASW_Door::KeyValue( const char *szKeyName, const char *szValue ) { if ( FStrEq(szKeyName, "health") ) { // skip over the CBaseProp KeyValue which discards health setting return CBaseAnimating::KeyValue( szKeyName, szValue ); } return BaseClass::KeyValue( szKeyName, szValue ); } int CASW_Door::OnTakeDamage( const CTakeDamageInfo &info ) { Vector vecTemp; if ( !edict() || !m_takedamage ) return 0; if ( (m_takedamage != DAMAGE_EVENTS_ONLY) && info.GetAttacker() && info.GetAttacker()->Classify() == CLASS_ASW_MARINE) { CASW_Marine* pMarine = dynamic_cast(info.GetAttacker()); if (pMarine) pMarine->HurtJunkItem(this, info); } if (info.GetDamageType() == DMG_SLASH || info.GetDamageType() == DMG_CLUB) // if an alien claw attack, then check if it's bashable { EmitSound("ASW_Door.MeleeHit"); if (!m_bBashable) return 0; } else if (!m_bShootable) // otherwise, check if it's shootable return 0; // door doesn't take damage when it's open // todo: take damage, but not below a dent boundary? //if (IsDoorOpen() || IsDoorOpening()) if (!IsDoorClosed()) return 0; // stop buzzers from knocking down the door in 1 swoop (their physics??) if (info.GetAttacker() && info.GetAttacker()->Classify() == CLASS_ASW_BUZZER) { return 0; } if ( info.GetInflictor() ) { vecTemp = info.GetInflictor()->WorldSpaceCenter() - ( WorldSpaceCenter() ); } else { vecTemp.Init( 1, 0, 0 ); } // this global is still used for glass and other non-NPC killables, along with decals. g_vecAttackDir = vecTemp; VectorNormalize(g_vecAttackDir); m_vLastDamageDir = g_vecAttackDir; if ( m_takedamage != DAMAGE_EVENTS_ONLY ) { CTakeDamageInfo newInfo(info); float damage = info.GetDamage(); // reduce damage from shotguns and mining laser if (info.GetDamageType() & DMG_ENERGYBEAM) { damage *= 0.4f; // still makes the mining laser the best weapon for taking down doors } if (info.GetDamageType() & DMG_BUCKSHOT) { damage *= 0.42f; // makes level 3 skill vindicator take around 16 seconds, normal shotgun around 26 (worse than rifle :/) } if ( info.GetInflictor() && info.GetInflictor()->Classify() == CLASS_ASW_T75 ) { damage *= 7.0f; // take extra damage from T75 } if (info.GetAttacker()) { if (info.GetAttacker()->Classify() == CLASS_ASW_DRONE) // scale alien damage { // Undo difficulty damage adjust float fDiff = ASWGameRules()->GetMissionDifficulty() - 5; float f = 1.0 + fDiff * asw_difficulty_alien_damage_step.GetFloat(); damage /= f; damage *= asw_door_drone_damage_scale.GetFloat(); } else if (info.GetAttacker()->Classify() == CLASS_ASW_MARINE && info.GetDamageType() & DMG_CLUB) // make doors immune to kick damage { damage *= 0; } // reduce damage from welding if (GetTotalSealTime() > 0) { float fSeal = GetCurrentSealTime() / GetTotalSealTime(); if (fSeal > 0 && fSeal <= 1.0f) { // up to X% damage reduction fSeal *= asw_door_seal_damage_reduction.GetFloat(); damage *= (1.0f - fSeal); } } } newInfo.SetDamage(damage); CheckForDoorShootChatter(newInfo); // do the damage m_iHealth -= damage; //Msg("Door health now %d (%d) seq %d frame %f DoorOpen=%d DoorOpening=%d DoorClosed=%d DoorClosing=%d\n", //m_iHealth, (int) m_DentAmount, GetSequence(), GetCycle(), //IsDoorOpen(), IsDoorOpening(), IsDoorClosed(), IsDoorClosing() ); if (m_iHealth <= 0) { const int needed_flip_damage = 50; // can't kill the door if it's not facing the right way //change this to check if the door is denting the right way //Msg( "Door dead. needs flip = %d flipped = %d\n", DoorNeedsFlip(), m_bFlipped ); if ( DoorNeedsFlip() ) { //Msg( " keeping door alive\n" ); m_iHealth = 1; m_fLastMomentFlipDamage += newInfo.GetDamage(); if (m_fLastMomentFlipDamage >= needed_flip_damage) { m_fLastMomentFlipDamage = 0; // put it in the middle and the run setdentsequence to actually flip it m_DentAmount = ASWDD_NONE; SetDentSequence(); // now make sure it's completely dented in this new direction (looks weird if the door straightens itself out) m_DentAmount = ASWDD_COMPLETE; SetDentSequence(); m_fLastMomentFlipDamage = -needed_flip_damage; } } /* else if (m_DentAmount != ASWDD_COMPLETE) // not fully dented yet, so can't fall over, but it's facing the right way at least { m_iHealth = 1; m_fLastMomentFlipDamage += newInfo.GetDamage(); if (m_fLastMomentFlipDamage >= needed_flip_damage) { m_fLastMomentFlipDamage = 0; if (m_DentAmount == ASWDD_NONE)// move it 1 step towards fully dented m_DentAmount = ASWDD_PARTIAL; else if (m_DentAmount == ASWDD_PARTIAL) m_DentAmount = ASWDD_COMPLETE; SetDentSequence(); } } */ else // door is facing the right way and is fully dented, let's make it fall over { if (m_fLastMomentFlipDamage < 0) // count up a small amount of damage so it doesn't fall immediately after changing facing { m_fLastMomentFlipDamage += newInfo.GetDamage(); } else { Event_Killed( newInfo ); } } return 0; } else // door is still alive { // figure out how dented the door is if (m_iHealth > ASW_DOOR_PARTIAL_DENT_HEALTH * m_iDoorStrength) { m_DentAmount = ASWDD_NONE; } else if (m_iHealth > ASW_DOOR_COMPLETE_DENT_HEALTH * m_iDoorStrength) { m_DentAmount = ASWDD_PARTIAL; } else { m_DentAmount = ASWDD_COMPLETE; } SetDentSequence(); } } return 1; } bool CASW_Door::DoorNeedsFlip( void ) { // check if we need to rotate the prop to make the right side anim Vector vecFacing; AngleVectors(GetAbsAngles(), &vecFacing); float dot = DotProduct(vecFacing, m_vLastDamageDir ); bool bNeedFlip = (dot > 0); if (bNeedFlip != m_bFlipped) { return true; } return false; } void CASW_Door::SetDentSequence() { if (m_lifeState == LIFE_DEAD) return; if (m_DentAmount == ASWDD_NONE) // always check for flipping if the door is in the 'middle' position { if (DoorNeedsFlip()) FlipDoor(); m_bSetSide = true; } // if we are a new door use body groups if (!stricmp(STRING(GetModelName()), RIGHT_DOOR) || !stricmp(STRING(GetModelName()), LEFT_DOOR) ) { SetDoorDamage(); return; } int iSeq; switch(m_DentAmount) { case ASWDD_COMPLETE: iSeq = LookupSequence("dent2"); break; case ASWDD_PARTIAL: iSeq = LookupSequence("dent1"); break; default: iSeq = LookupSequence("still"); break; } // only do denting when the door is shut if (iSeq != GetSequence() && !IsDoorOpen() && !IsDoorOpening()) { //Msg("Setting door sequence to %d and cycle to 0\n", iSeq); ResetSequence(iSeq); ResetClientsideFrame(); //SetCycle(0); // should make this kinda stuff clientside? // spawn some smoke and stuff, since the door just got dented a bit DoorSmoke(); } } void CASW_Door::SetDoorDamage() { int iDamageGroup; switch(m_DentAmount) { case ASWDD_COMPLETE: iDamageGroup = 4; break; case ASWDD_PARTIAL: iDamageGroup = 3; break; default: iDamageGroup = 0; break; } // apply flip if ( m_bFlipped ) { if ( iDamageGroup == 3 ) iDamageGroup = 1; else if ( iDamageGroup == 4 ) iDamageGroup = 2; } // only do denting when the door is shut if (iDamageGroup != GetSequence() && !IsDoorOpen() && !IsDoorOpening()) { SetBodygroup( 0 , iDamageGroup ); // spawn some smoke and stuff, since the door just got dented a bit DoorSmoke(); } } void CASW_Door::DoorSmoke() { CASW_Emitter* pEmitter = (CASW_Emitter*) CreateEntityByName("asw_emitter"); if (pEmitter) { pEmitter->UseTemplate("doorsmoke1"); UTIL_SetOrigin( pEmitter, GetAbsOrigin() ); pEmitter->SetAbsAngles(GetAbsAngles()); pEmitter->Spawn(); //pEmitter->SetParent( this ); } EmitSound("ASW_Door.Dented"); } void CASW_Door::Event_Killed( const CTakeDamageInfo &info ) { m_OnDestroyed.FireOutput(info.GetInflictor(), this); IGameEvent * event = gameeventmanager->CreateEvent( "door_destroyed" ); if ( event ) { CBasePlayer *pPlayer = NULL; CASW_Marine *pMarine = dynamic_cast< CASW_Marine* >( info.GetAttacker() ); if ( pMarine ) { pPlayer = pMarine->GetCommander(); } event->SetInt( "userid", ( pPlayer ? pPlayer->GetUserID() : 0 ) ); event->SetInt( "entindex", entindex() ); gameeventmanager->FireEvent( event ); } if( info.GetAttacker() ) { info.GetAttacker()->Event_KilledOther(this, info); } // check if marines should shout about this door being bashed down // check there's another marine nearby if (m_bDoBreachedShout && info.GetAttacker() && info.GetAttacker()->Classify() == CLASS_ASW_DRONE && ASWGameResource()) { CASW_Game_Resource *pGameResource = ASWGameResource(); // count how many marines are nearby int iNearby = 0; for (int i=0;iGetMaxMarineResources();i++) { CASW_Marine_Resource *pMR = pGameResource->GetMarineResource(i); CASW_Marine *pMarine = pMR ? pMR->GetMarineEntity() : NULL; if (pMarine && (GetAbsOrigin().DistTo(pMarine->GetAbsOrigin()) < 800) && pMarine->GetHealth() > 0) iNearby++; } if (iNearby >= 0) { if (asw_debug_marine_chatter.GetBool()) Msg("Doing door breached chatter\n"); int iChatter = random->RandomInt(0, iNearby-1); for (int i=0;iGetMaxMarineResources();i++) { CASW_Marine_Resource *pMR = pGameResource->GetMarineResource(i); CASW_Marine *pMarine = pMR ? pMR->GetMarineEntity() : NULL; if (pMarine && (GetAbsOrigin().DistTo(pMarine->GetAbsOrigin()) < 600) && pMarine->GetHealth() > 0) { if (iChatter <= 0) { pMarine->GetMarineSpeech()->QueueChatter(CHATTER_BREACHED_DOOR, gpGlobals->curtime + 0.5f, gpGlobals->curtime + 1.5f); break; } iChatter--; } } } else // only one marine nearby, pretend we did the shout so we don't waste time checking again { if (asw_debug_marine_chatter.GetBool()) Msg("Skipping door breached chatter, no marines nearby\n"); } } m_bDoorFallen = true; m_takedamage = DAMAGE_NO; //Msg("Door broken!\n"); m_lifeState = LIFE_DEAD; //ResetSequence(LookupSequence("fall")); // remove any padding childs CBaseEntity *child; // iterate through all children for ( child = FirstMoveChild(); child != NULL; child = child->NextMovePeer() ) { if( FClassnameIs( child, "asw_door_padding" ) ) { child->SetParent( NULL ); child->SetSolid( SOLID_NONE ); UTIL_Remove(child); } } m_hDoorPadding = NULL; DoorSmoke(); // turn into a physics prop if (asw_door_physics.GetBool()) { VPhysicsDestroyObject(); ASWCreateVPhysics(); if (VPhysicsGetObject()) { Vector vecFacing; QAngle angFacing = GetAbsAngles(); if ( m_bFlipped && m_bRotateOnFlip ) angFacing[YAW] += 180; AngleVectors(angFacing, &vecFacing); VPhysicsGetObject()->ApplyForceOffset( vecFacing * 50000, WorldSpaceCenter() ); } else { DevMsg("Error, failed to create door physics object\n"); } } else { int iSeq; if (!stricmp(STRING(GetModelName()), RIGHT_DOOR) || !stricmp(STRING(GetModelName()), LEFT_DOOR)) { if ( m_bFlipped ) { iSeq = LookupSequence("door_rear_fall"); } else { iSeq = LookupSequence("door_front_fall"); } } else { iSeq = LookupSequence("fall"); } //VPhysicsDestroyObject(); //m_bUseHitboxesForRenderBox = true; CollisionProp()->SetSurroundingBoundsType( USE_HITBOXES ); if (iSeq != GetSequence()) { ResetSequence(iSeq); ResetClientsideFrame(); DoorSmoke(); } } } bool CASW_Door::ASWCreateVPhysics() { // Create the object in the physics system bool asleep = HasSpawnFlags( SF_PHYSPROP_START_ASLEEP ) ? true : false; solid_t tmpSolid; PhysModelParseSolid( tmpSolid, this, GetModelIndex() ); //if ( m_massScale > 0 ) //{ //tmpSolid.params.mass *= m_massScale; //} //if ( m_inertiaScale > 0 ) //{ //tmpSolid.params.inertia *= m_inertiaScale; //if ( tmpSolid.params.inertia < 0.5 ) //tmpSolid.params.inertia = 0.5; //} PhysGetMassCenterOverride( this, modelinfo->GetVCollide( GetModelIndex() ), tmpSolid ); //PhysSolidOverride( tmpSolid, m_iszOverrideScript ); IPhysicsObject *pPhysicsObject = VPhysicsInitNormal( SOLID_VPHYSICS, 0, asleep, &tmpSolid ); if ( !pPhysicsObject ) { SetSolid( SOLID_NONE ); SetMoveType( MOVETYPE_NONE ); Warning("ERROR!: Can't create physics object for %s\n", STRING( GetModelName() ) ); } else { //if ( m_damageType == 1 ) //{ //PhysSetGameFlags( pPhysicsObject, FVPHYSICS_DMG_SLICE ); //} if ( HasSpawnFlags( SF_PHYSPROP_MOTIONDISABLED ) ) //|| m_damageToEnableMotion > 0 || m_flForceToEnableMotion > 0 ) { pPhysicsObject->EnableMotion( false ); } } // fix up any noncompliant blades. if( HasInteraction( PROPINTER_PHYSGUN_LAUNCH_SPIN_Z ) ) { if( !(VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_DMG_SLICE) ) { PhysSetGameFlags( pPhysicsObject, FVPHYSICS_DMG_SLICE ); #if 0 if( g_pDeveloper->GetInt() ) { // Highlight them in developer mode. m_debugOverlays |= (OVERLAY_TEXT_BIT|OVERLAY_BBOX_BIT); } #endif } } if( HasInteraction( PROPINTER_PHYSGUN_DAMAGE_NONE ) ) { PhysSetGameFlags( pPhysicsObject, FVPHYSICS_NO_IMPACT_DMG ); } if ( HasSpawnFlags(SF_PHYSPROP_PREVENT_PICKUP) ) { PhysSetGameFlags(pPhysicsObject, FVPHYSICS_NO_PLAYER_PICKUP); } return true; } void CASW_Door::VPhysicsUpdate( IPhysicsObject *pPhysics ) { BaseClass::VPhysicsUpdate( pPhysics ); //m_bAwake = !pPhysics->IsAsleep(); NetworkStateChanged(); if ( HasSpawnFlags( SF_PHYSPROP_START_ASLEEP ) ) { //if ( m_bAwake ) //{ //m_OnAwakened.FireOutput(this, this); //RemoveSpawnFlags( SF_PHYSPROP_START_ASLEEP ); //} } // If we're asleep, clear the player thrown flag //if ( m_bThrownByPlayer && !m_bAwake ) //{ //m_bThrownByPlayer = false; //} } //----------------------------------------------------------------------------- // Mass / mass center //----------------------------------------------------------------------------- void CASW_Door::GetMassCenter( Vector *pMassCenter ) { if ( !VPhysicsGetObject() ) { pMassCenter->Init(); return; } Vector vecLocal = VPhysicsGetObject()->GetMassCenterLocalSpace(); VectorTransform( vecLocal, EntityToWorldTransform(), *pMassCenter ); } float CASW_Door::GetMass() const { return VPhysicsGetObject() ? VPhysicsGetObject()->GetMass() : 1.0f; } void CASW_Door::FlipDoor() { if (!Q_stricmp(STRING(GetModelName()), RIGHT_DOOR)) { //Msg( "flipping right door\n" ); m_bFlipped = !m_bFlipped; SetModel(RIGHT_DOOR); } else if (!Q_stricmp(STRING(GetModelName()), LEFT_DOOR)) { //Msg( "flipping left door\n" ); m_bFlipped = !m_bFlipped; SetModel(LEFT_DOOR); } else if (!Q_stricmp(STRING(GetModelName()), SINGLE_DOOR)) { m_bFlipped = true; SetModel(SINGLE_DOOR_FLIPPED); } else if (!Q_stricmp(STRING(GetModelName()), SINGLE_DOOR_FLIPPED)) { m_bFlipped = false; SetModel(SINGLE_DOOR); } else { m_bFlipped = !m_bFlipped; } } // always send this entity to players (until we have a radius based network cull thing...) int CASW_Door::ShouldTransmit( const CCheckTransmitInfo *pInfo ) { return FL_EDICT_ALWAYS; } void CASW_Door::InputEnableAutoOpen( inputdata_t &inputdata ) { m_bAutoOpen = true; } void CASW_Door::InputDisableAutoOpen( inputdata_t &inputdata ) { m_bAutoOpen = false; } void CASW_Door::InputRecommendWeld( inputdata_t &inputdata ) { m_bRecommendedSeal = true; IGameEvent * event = gameeventmanager->CreateEvent( "door_recommend_weld" ); if ( event ) { event->SetInt( "entindex", entindex() ); gameeventmanager->FireEvent( event ); } } // called by door areas that want automatic door shoot or weld chatter void CASW_Door::DoAutoDoorShootChatter(CASW_Marine *pMarine) { // check there's another marine nearby if (!m_bDoneDoorShout && ASWGameResource() && pMarine && pMarine->GetHealth() > 0) { CASW_Game_Resource *pGameResource = ASWGameResource(); // count how many are nearby and see if any have a welder int iNearby = 0; for (int i=0;iGetMaxMarineResources();i++) { CASW_Marine_Resource *pMR = pGameResource->GetMarineResource(i); CASW_Marine *pOtherMarine = pMR ? pMR->GetMarineEntity() : NULL; if (pOtherMarine && (GetAbsOrigin().DistTo(pOtherMarine->GetAbsOrigin()) < 600) && pOtherMarine->GetHealth() > 0) { iNearby++; } } if (iNearby >= 2) // changed to only need the 1 marine... { if (asw_debug_marine_chatter.GetBool()) Msg("Doing door shout hint\n"); if (pMarine->GetMarineSpeech()->Chatter(CHATTER_REQUEST_SHOOT_DOOR)) { m_bDoneDoorShout = true; } } else // no other marines nearby, do a shoot shout or a cut shown depending on if we have a welder { if (asw_debug_marine_chatter.GetBool()) Msg("Skipping door shout, only 1 marine nearby\n"); CASW_Weapon_Welder *pWelder = dynamic_cast(pMarine->GetASWWeapon(2)); if (pWelder && pMarine->GetMarineSpeech()->Chatter(CHATTER_CUTTING_DOOR)) { m_bDoCutShout = false; m_bDoneDoorShout = true; } else if (pMarine->GetMarineSpeech()->Chatter(CHATTER_REQUEST_SHOOT_DOOR)) { m_bDoneDoorShout = true; } } } } void CASW_Door::CheckForDoorShootChatter( const CTakeDamageInfo &info ) { if (m_bDoneDoorShout) return; if (m_takedamage != DAMAGE_YES || GetHealth() <= 0 || !m_bShootable) return; if (info.GetAttacker() && info.GetAttacker()->Classify() == CLASS_ASW_MARINE) { // reduce our block counter by the number of seconds that have passed since a marine last shot us in the front if (m_fLastMarineShootTime != 0 && m_fMarineShootCounter > 0) { if (asw_debug_marine_chatter.GetBool()) Msg("Reducing shoot counter by %f\n", (gpGlobals->curtime - m_fLastMarineShootTime) * 20.0f); m_fMarineShootCounter -= (gpGlobals->curtime - m_fLastMarineShootTime) * 20.0f; // counter ticks completely down after 30 seconds if (m_fMarineShootCounter < 0) m_fMarineShootCounter = 0; } m_fMarineShootCounter += info.GetDamage(); if (asw_debug_marine_chatter.GetBool()) Msg("m_fMarineShootCounter = %f\n", m_fMarineShootCounter); if (m_fMarineShootCounter > 100) // roughly 1 second of rifle firing on normal { CASW_Marine *pMarine = dynamic_cast(info.GetAttacker()); if (!pMarine) return; // check there's another marine nearby if ( ASWGameResource() ) { CASW_Game_Resource *pGameResource = ASWGameResource(); // count how many are nearby int iNearby = 0; for (int i=0;iGetMaxMarineResources();i++) { CASW_Marine_Resource *pMR = pGameResource->GetMarineResource(i); CASW_Marine *pOtherMarine = pMR ? pMR->GetMarineEntity() : NULL; if (pOtherMarine && (GetAbsOrigin().DistTo(pOtherMarine->GetAbsOrigin()) < 600) && pOtherMarine->GetHealth() > 0) iNearby++; } if (iNearby >= 2) { if (asw_debug_marine_chatter.GetBool()) Msg("Doing door shout hint\n"); if (pMarine->GetMarineSpeech()->Chatter(CHATTER_REQUEST_SHOOT_DOOR)) m_bDoneDoorShout = true; } else // only one marine nearby, pretend we did the shout so we don't waste time checking again { if (asw_debug_marine_chatter.GetBool()) Msg("Skipping door shout, only 1 marine nearby\n"); m_bDoneDoorShout = true; } } m_fMarineShootCounter = 0; } m_fLastMarineShootTime = gpGlobals->curtime; } } void CASW_Door::HandleAnimEvent(animevent_t *pEvent) { int nEvent = pEvent->Event(); if ( nEvent == AE_START_SCRIPTED_EFFECT) { m_iFallingStage++; //Msg("Door fallen!\n"); VPhysicsDestroyObject(); // crush anyone in the way for this stage FallCrush(); // spawn a blocker to cover the door's fallen position if (m_iFallingStage == 2) CASW_Fallen_Door_Padding::CreateFallenDoorPadding(this); //SetMoveType( MOVETYPE_NONE ); // adjust our collision bounds //Vector vecMaxs = Vector(60,185,21); //Vector vecMins = Vector(-60,42,0); //SetSolid( SOLID_BBOX ); //SetCollisionGroup( COLLISION_GROUP_NONE ); //SetCollisionBounds( vecMins, vecMaxs ); //VPhysicsInitShadow(false, false); //RemoveSolidFlags( FSOLID_CUSTOMRAYTEST | FSOLID_CUSTOMBOXTEST ); //VPhysicsInitFallenShadow(false, false); //CollisionProp()->SetSurroundingBoundsType( USE_HITBOXES ); return; } BaseClass::HandleAnimEvent( pEvent ); } // attempt to create a vphysics object on the floor in the place the door fall animation goes - doesn't work though IPhysicsObject *CASW_Door::VPhysicsInitFallenShadow( bool allowPhysicsMovement, bool allowPhysicsRotation, solid_t *pSolid ) { if ( !edict() || IsMarkedForDeletion() ) return NULL; // No physics if ( GetSolid() == SOLID_NONE ) return NULL; const Vector &origin = GetAbsOrigin(); QAngle angles = GetAbsAngles(); //angles[PITCH] += 90; Vector vecMaxs = Vector(60,185,21); Vector vecMins = Vector(-60,42,0); Vector absMin = Vector(-300, -300, -300); Vector absMax = Vector(300, 300, 300); CollisionProp()->SetSurroundingBoundsType( USE_SPECIFIED_BOUNDS, &absMin, &absMax ); //pPhysicsObject = PhysModelCreateOBB( this, vecMins, vecMaxs, origin, angles, false ); IPhysicsObject *pPhysicsObject = PhysModelCreate( this, GetModelIndex(), origin, angles, pSolid ); /* if ( GetSolid() == SOLID_BBOX ) { // adjust these so the game tracing epsilons match the physics minimum separation distance // this will shrink the vphysics version of the model by the difference in epsilons float radius = 0.25f - DIST_EPSILON; Vector mins = WorldAlignMins() + Vector(radius, radius, radius); Vector maxs = WorldAlignMaxs() - Vector(radius, radius, radius); pPhysicsObject = PhysModelCreateBox( this, mins, maxs, origin, false ); angles = vec3_angle; } else if ( GetSolid() == SOLID_OBB ) { pPhysicsObject = PhysModelCreateOBB( this, CollisionProp()->OBBMins(), CollisionProp()->OBBMaxs(), origin, angles, false ); } else { pPhysicsObject = PhysModelCreate( this, GetModelIndex(), origin, angles, pSolid ); } */ if ( !pPhysicsObject ) return NULL; VPhysicsSetObject( pPhysicsObject ); pPhysicsObject->UpdateShadow( origin, angles, false, 0 ); pPhysicsObject->SetShadow( 1e4, 1e4, allowPhysicsMovement, allowPhysicsRotation ); pPhysicsObject->UpdateShadow( origin, angles, false, 0 ); return pPhysicsObject; } extern ConVar ai_show_hull_attacks; void CASW_Door::FallCrush() { Vector vForward; QAngle ang = GetAbsAngles(); ang[PITCH] = 0; ang[ROLL] = 0; // if ( DoorNeedsFlip() ) // { // ang[YAW] += 180; // } if ( m_bFlipped ) { ang[YAW] += 180; } AngleVectors(ang, &vForward); // adjust the sweep of destruction by how far fallen over the door is float length = 80.0f; float start_offset = 99; if ( m_iFallingStage == 1 ) { length *= 0.333f; start_offset = 40; } if ( m_iFallingStage == 2 ) { length *= 0.666f; start_offset = 49; } Vector vStart = GetAbsOrigin() + vForward * start_offset; Vector vEnd = vStart + vForward * length; // compute a bounding box that encompasses the door, no matter its angle float wide = 60; float deep = 10; float tall = 140; Vector pre_top_left(-deep, -wide, 0); Vector pre_top_right(deep, -wide, 0); Vector pre_bottom_right(deep, wide, tall); Vector pre_bottom_left(-deep, wide, tall); Vector tl, tr, br, bl; matrix3x4_t fRotateMatrix; AngleMatrix(ang, fRotateMatrix); VectorRotate( pre_top_left, fRotateMatrix, tl); VectorRotate( pre_top_right, fRotateMatrix, tr); VectorRotate( pre_bottom_right, fRotateMatrix, br); VectorRotate( pre_bottom_left, fRotateMatrix, bl); Vector mins, maxs; mins.x = MIN(MIN(tl.x, tr.x), MIN(br.x, bl.x)); mins.y = MIN(MIN(tl.y, tr.y), MIN(br.y, bl.y)); mins.z = 0; maxs.x = MAX(MAX(tl.x, tr.x), MAX(br.x, bl.x)); maxs.y = MAX(MAX(tl.y, tr.y), MAX(br.y, bl.y)); maxs.z = tall; CTakeDamageInfo dmgInfo( this, this, 1000, DMG_SLASH ); dmgInfo.SetDamageForce( vForward * 100.0f ); dmgInfo.SetDamagePosition( vEnd ); CASW_Trace_Filter_Door_Crush traceFilter( this, COLLISION_GROUP_NONE, &dmgInfo, 1.0f, true ); trace_t trace; UTIL_TraceHull( vStart, vEnd, mins, maxs, MASK_SHOT_HULL, &traceFilter, &trace ); if ( ai_show_hull_attacks.GetBool() ) { NDebugOverlay::SweptBox(vStart, vEnd, mins, maxs, QAngle(0, 0, 0), 255,255, 0, 255, 4.0f); NDebugOverlay::Line(vStart + Vector(0,0, 30), vEnd + Vector(0,0, 30), 255, 255, 0, true, 3.9f); } } /* bool CASW_Door::ASWCreateFallenVPhysics() { // Create the object in the physics system bool asleep = HasSpawnFlags( SF_PHYSPROP_START_ASLEEP ) ? true : false; solid_t tmpSolid; PhysModelParseSolid( tmpSolid, this, GetModelIndex() ); PhysGetMassCenterOverride( this, modelinfo->GetVCollide( GetModelIndex() ), tmpSolid ); IPhysicsObject *pPhysicsObject = VPhysicsInitNormal( SOLID_VPHYSICS, 0, asleep, &tmpSolid ); if ( !pPhysicsObject ) { SetSolid( SOLID_NONE ); SetMoveType( MOVETYPE_NONE ); Warning("ERROR!: Can't create physics object for %s\n", STRING( GetModelName() ) ); } else { pPhysicsObject->EnableMotion( false ); } return true; }*/ // extent contains the volume encompassing open + closed states void CASW_Door::ComputeDoorExtent( Extent *extent, unsigned int extentType ) { if ( !extent ) return; // FIXMEL4DTOMAINMERGE needs implementation /* if ( extentType & DOOR_EXTENT_CLOSED ) { Extent closedExtent; CalculateDoorVolume( m_angRotationClosed, m_angRotationClosed, &extent->lo, &extent->hi ); if ( extentType & DOOR_EXTENT_OPEN ) { Extent openExtent; UTIL_ComputeAABBForBounds( m_vecForwardBoundsMin, m_vecForwardBoundsMax, m_vecBackBoundsMin, m_vecBackBoundsMax, &openExtent.lo, &openExtent.hi ); extent->Encompass( openExtent ); } } else if ( extentType & DOOR_EXTENT_OPEN ) { UTIL_ComputeAABBForBounds( m_vecForwardBoundsMin, m_vecForwardBoundsMax, m_vecBackBoundsMin, m_vecBackBoundsMax, &extent->lo, &extent->hi ); } */ // FIXMEL4DTOMAINMERGE hack hack Extent closedExtent; CalculateDoorVolume( vec3_origin, vec3_origin, &extent->lo, &extent->hi ); extent->lo += GetAbsOrigin(); extent->hi += GetAbsOrigin(); }