#include "cbase.h" #ifdef CLIENT_DLL #define CBaseFlex C_BaseFlex #define CASW_Alien_Goo C_ASW_Alien_Goo #define CASW_Grub_Sac C_ASW_Grub_Sac #include "c_asw_generic_emitter_entity.h" #include "c_asw_fx.h" #include "baseparticleentity.h" #include "asw_util_shared.h" #else #include "EntityFlame.h" #include "asw_grub.h" #include "asw_simple_grub.h" #include "asw_marine.h" #include "asw_player.h" #include "asw_fx_shared.h" #include "asw_util_shared.h" #include "asw_gamerules.h" #include "asw_mission_manager.h" #include "asw_entity_dissolve.h" #include "asw_marine_speech.h" #include "asw_burning.h" #include "te_effect_dispatch.h" #include "cvisibilitymonitor.h" #endif #include "asw_alien_goo_shared.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define ASW_GRUB_SAC_BURST_DISTANCE 180.0f #define ACID_BURN_INTERVAL 1.0f #define ASW_ACID_DAMAGE 10.0f IMPLEMENT_NETWORKCLASS_ALIASED( ASW_Alien_Goo, DT_ASW_Alien_Goo ) BEGIN_NETWORK_TABLE( CASW_Alien_Goo, DT_ASW_Alien_Goo ) #ifdef CLIENT_DLL // recvprops RecvPropFloat (RECVINFO(m_fPulseStrength)), RecvPropFloat (RECVINFO(m_fPulseSpeed)), RecvPropBool(RECVINFO(m_bOnFire)), #else // sendprops SendPropExclude( "DT_BaseFlex", "m_flexWeight" ), // don't send the weights from the server, we'll animate them clientside SendPropFloat (SENDINFO(m_fPulseStrength), 10, SPROP_UNSIGNED ), SendPropFloat (SENDINFO(m_fPulseSpeed), 10, SPROP_UNSIGNED ), SendPropBool(SENDINFO(m_bOnFire)), #endif END_NETWORK_TABLE() #ifdef GAME_DLL ConVar asw_goo_volume("asw_goo_volume", "1.0f", FCVAR_CHEAT, "Volume of the alien goo looping sound"); LINK_ENTITY_TO_CLASS( asw_alien_goo, CASW_Alien_Goo ); PRECACHE_WEAPON_REGISTER(asw_alien_goo); #endif #ifndef CLIENT_DLL //--------------------------------------------------------- // Save/Restore //--------------------------------------------------------- BEGIN_DATADESC( CASW_Alien_Goo ) DEFINE_KEYFIELD( m_fPulseStrength, FIELD_FLOAT, "PulseStrength" ), DEFINE_KEYFIELD( m_fPulseSpeed, FIELD_FLOAT, "PulseSpeed" ), DEFINE_KEYFIELD( m_BurningLinkName, FIELD_STRING, "BurningLinkName" ), DEFINE_KEYFIELD( m_fGrubSpawnAngle, FIELD_FLOAT, "GrubSpawnAngle" ), DEFINE_KEYFIELD( m_bHasAmbientSound, FIELD_BOOLEAN, "HasAmbientSound" ), DEFINE_KEYFIELD( m_bRequiredByObjective, FIELD_BOOLEAN, "RequiredByObjective" ), DEFINE_FIELD( m_bSpawnedGrubs, FIELD_BOOLEAN ), DEFINE_FIELD( m_bHasGrubs, FIELD_BOOLEAN ), DEFINE_FIELD( m_bOnFire, FIELD_BOOLEAN ), DEFINE_THINKFUNC(BurningLinkThink), DEFINE_THINKFUNC(InitThink), DEFINE_THINKFUNC(GrubSacThink), DEFINE_FUNCTION( GooTouch ), DEFINE_FUNCTION( GooAcidTouch ), DEFINE_INPUTFUNC( FIELD_VOID, "Burst", InputBurst ), DEFINE_OUTPUT( m_OnGooDestroyed, "OnGooDestroyed" ), END_DATADESC() float CASW_Alien_Goo::s_fNextSpottedChatterTime = 0; CUtlVector g_AlienGoo; #endif // not client CASW_Alien_Goo::CASW_Alien_Goo() { #ifdef CLIENT_DLL m_fPulseCycle = 0; m_bClientOnFire = false; m_pBurningEffect = NULL; #else m_bPlayingAmbientSound = false; m_bPlayingGooScream = false; m_fNextAcidBurnTime = 0; #endif m_fPulseStrength = 1.0f; m_fPulseSpeed = 1.0f; #ifndef CLIENT_DLL g_AlienGoo.AddToTail( this ); #endif } CASW_Alien_Goo::~CASW_Alien_Goo() { #ifdef CLIENT_DLL m_bOnFire = false; UpdateFireEmitters(); #endif #ifndef CLIENT_DLL g_AlienGoo.FindAndRemove( this ); #endif } #ifndef CLIENT_DLL void CASW_Alien_Goo::Spawn() { BaseClass::Spawn(); char *szModel = (char *)STRING( GetModelName() ); if (!szModel || !*szModel) { Warning( "%s at %.0f %.0f %0.f missing modelname\n", GetClassname(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z ); UTIL_Remove( this ); return; } if (FClassnameIs(this, "asw_grub_sac")) { m_bHasGrubs = true; m_fPulseStrength = 1.0f; m_fPulseSpeed = random->RandomFloat(1.5f, 3.0f); SetBlocksLOS(false); } PrecacheModel( szModel ); Precache(); SetModel( szModel ); SetMoveType( MOVETYPE_NONE ); SetSolid(SOLID_VPHYSICS); SetCollisionGroup( COLLISION_GROUP_NONE ); //COLLISION_GROUP_DEBRIS ); m_takedamage = DAMAGE_YES; m_iHealth = 100; m_iMaxHealth = m_iHealth; AddFlag( FL_STATICPROP ); VPhysicsInitStatic(); SetThink( &CASW_Alien_Goo::InitThink ); SetNextThink( gpGlobals->curtime + random->RandomFloat(5.0f, 10.0f)); if ( m_bRequiredByObjective ) { VisibilityMonitor_AddEntity( this, asw_visrange_generic.GetFloat() * 0.9f, NULL, NULL ); } } void CASW_Alien_Goo::InitThink() { if (m_bHasAmbientSound && !m_bPlayingAmbientSound) { if (!UTIL_ASW_MissionHasBriefing(STRING(gpGlobals->mapname))) { StartGooSound(); } } // if spawns grubs if (m_bHasGrubs) { SetTouch( &CASW_Alien_Goo::GooTouch ); } else { SetTouch( &CASW_Alien_Goo::GooAcidTouch ); } // all goo needs to think periodically, so marines can shout about it // check if we need to burst open thanks to nearby marines SetThink( &CASW_Alien_Goo::GrubSacThink ); SetNextThink( gpGlobals->curtime + 1.0f ); } void CASW_Alien_Goo::Event_Killed( const CTakeDamageInfo &info ) { m_OnGooDestroyed.FireOutput( this, this ); if (!m_bHasGrubs) { if (ASWGameRules() && ASWGameRules()->GetMissionManager()) ASWGameRules()->GetMissionManager()->GooKilled(this); } StopGooSound(); BaseClass::Event_Killed(info); } void CASW_Alien_Goo::Precache() { PrecacheModel( "models/aliens/biomass/biomasshelix.mdl" ); PrecacheModel( "models/aliens/biomass/biomassl.mdl" ); PrecacheModel( "models/aliens/biomass/biomasss.mdl" ); PrecacheModel( "models/aliens/biomass/biomassu.mdl" ); PrecacheScriptSound("ASWGoo.GooLoop"); PrecacheScriptSound("ASWGoo.GooScream"); PrecacheScriptSound("ASWGoo.GooDissolve"); PrecacheScriptSound("ASWFire.AcidBurn"); PrecacheParticleSystem( "biomass_dissolve" ); PrecacheParticleSystem( "acid_touch" ); PrecacheParticleSystem( "grubsack_death" ); UTIL_PrecacheOther( "asw_grub" ); BaseClass::Precache(); } int CASW_Alien_Goo::OnTakeDamage( const CTakeDamageInfo &info ) { CASW_Marine* pMarine = dynamic_cast(info.GetAttacker()); // if has grubs if ( m_bHasGrubs && HasSpawnFlags( ASW_SF_BURST_WHEN_DAMAGED ) ) { SpawnGrubs(); if ( pMarine) pMarine->HurtAlien(this, info); } // goo is only damaged by fire! if ( !( info.GetDamageType() & DMG_BURN ) && !( info.GetDamageType() & DMG_ENERGYBEAM ) ) return 0; // notify the marine that he's hurting this, so his accuracy doesn't drop if (info.GetAttacker() && info.GetAttacker()->Classify() == CLASS_ASW_MARINE) { if ( pMarine ) { pMarine->HurtJunkItem( this, info ); } } // if this damage would make us close to dying, then make us dissolve instead if ( m_iHealth - info.GetDamage() < 10.0f ) { if ( !IsDissolving() ) { //bool bRagdollCreated = Dissolve( NULL, gpGlobals->curtime, false, ENTITY_DISSOLVE_NORMAL ); //Msg("Dissolved = %d\n", bRagdollCreated); return 0; } else if ( info.GetDamage() != 10000.0 ) // when dissolving, only the dissolve damage will kill us { return 0; } } int result = BaseClass::OnTakeDamage( info ); if ( result > 0 ) { if ( info.GetDamageType() & DMG_BURN ) { Ignite( 30.0f ); CASW_Player *pPlayerAttacer = NULL; if ( pMarine ) { pPlayerAttacer = pMarine->GetCommander(); } IGameEvent * event = gameeventmanager->CreateEvent( "alien_ignited" ); if ( event ) { event->SetInt( "userid", ( pPlayerAttacer ? pPlayerAttacer->GetUserID() : 0 ) ); event->SetInt( "entindex", entindex() ); gameeventmanager->FireEventClientSide( event ); } } } return result; } void CASW_Alien_Goo::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner ) { if ( IsOnFire() || m_bHasGrubs ) { return; } AddFlag( FL_ONFIRE ); m_bOnFire = true; if ( ASWBurning() ) { ASWBurning()->BurnEntity(this, NULL, flFlameLifetime, 0.4f, 5.0f * 0.4f); // 5 dps, applied every 0.4 seconds } m_OnIgnite.FireOutput( this, this ); // wail StartScreaming(); // set up a delay to burn our linked entities SetThink( &CASW_Alien_Goo::BurningLinkThink ); SetNextThink( gpGlobals->curtime + 2.0f ); } void CASW_Alien_Goo::Extinguish() { m_bOnFire = false; if (ASWBurning()) ASWBurning()->Extinguish(this); RemoveFlag( FL_ONFIRE ); } // periodically find the nearest marine and check if we should burst open void CASW_Alien_Goo::GrubSacThink() { if (m_bHasAmbientSound && !m_bPlayingAmbientSound) { if (UTIL_ASW_MissionHasBriefing(STRING(gpGlobals->mapname)) && ASWGameRules() && ASWGameRules()->GetGameState() == ASW_GS_INGAME) StartGooSound(); } if (m_bHasGrubs) { float marine_distance = -1; if (UTIL_ASW_NearestMarine(GetAbsOrigin(), marine_distance )) // returns the nearest marine to this point { if (m_bHasGrubs && HasSpawnFlags(ASW_SF_BURST_WHEN_NEAR)) { if (marine_distance <= ASW_GRUB_SAC_BURST_DISTANCE) { SpawnGrubs(); return; } } // NOTE: Marines chattering about alien good disabled, as it sounded bad /* else if (marine_distance < 300 && gpGlobals->curtime > s_fNextSpottedChatterTime) { CASW_Marine *pSpottedMarine = UTIL_ASW_Marine_Can_Chatter_Spot(this, 300); if (pSpottedMarine) { pSpottedMarine->GetMarineSpeech()->Chatter(CHATTER_BIOMASS); s_fNextSpottedChatterTime = gpGlobals->curtime + 30.0f; } else s_fNextSpottedChatterTime = gpGlobals->curtime + 1.0f; }*/ } // rethink interval based on how near the marines are if (marine_distance == -1 || marine_distance > 4096) { SetNextThink( gpGlobals->curtime + 5.0f ); } else if (marine_distance > 1024) { SetNextThink( gpGlobals->curtime + 2.0f ); } else { SetNextThink( gpGlobals->curtime + 1.0f ); } } else { if (m_bHasAmbientSound && !m_bPlayingAmbientSound) { SetNextThink( gpGlobals->curtime + random->RandomFloat(2.0f, 5.0f) ); } } } void CASW_Alien_Goo::BurningLinkThink() { CBaseEntity *ent = NULL; while ( (ent = gEntList.NextEnt(ent)) != NULL ) { CASW_Alien_Goo *pGoo = dynamic_cast(ent); if (pGoo) { if ( (pGoo->GetBurningLinkName() != NULL_STRING && FStrEq(STRING(m_BurningLinkName), STRING(pGoo->GetBurningLinkName()))) && (ent->GetClassname()!=NULL && (FStrEq("asw_alien_goo", ent->GetClassname()) || FStrEq("asw_grub_sac", ent->GetClassname()) ))) { pGoo->Ignite(30.0f); } } } } void CASW_Alien_Goo::GooAcidTouch(CBaseEntity* pOther) { // if touched by a marine, acid burn him! if (pOther && pOther->Classify() == CLASS_ASW_MARINE) { if (m_fNextAcidBurnTime == 0 || gpGlobals->curtime > m_fNextAcidBurnTime) { m_fNextAcidBurnTime = gpGlobals->curtime + ACID_BURN_INTERVAL; CTakeDamageInfo info( this, this, ASW_ACID_DAMAGE, DMG_ACID ); Vector killDir = pOther->GetAbsOrigin() - GetAbsOrigin(); VectorNormalize( killDir ); info.SetDamageForce( killDir ); Vector vecDamagePos = pOther->GetAbsOrigin() -killDir * 32.0f; info.SetDamagePosition( vecDamagePos ); pOther->TakeDamage(info); EmitSound("ASWFire.AcidBurn"); CEffectData data; data.m_vOrigin = vecDamagePos; data.m_nOtherEntIndex = pOther->entindex(); DispatchEffect( "ASWAcidBurn", data ); } } } void CASW_Alien_Goo::GooTouch(CBaseEntity* pOther) { // egg will open if touched by a marine CASW_Marine* pMarine = CASW_Marine::AsMarine( pOther ); if (pMarine && m_bHasGrubs && HasSpawnFlags(ASW_SF_BURST_WHEN_TOUCHED)) { SetTouch( NULL ); SpawnGrubs(); } } bool CASW_Alien_Goo::RoomToSpawnGrub(const Vector& pos) { // Check to see if there's enough room to spawn trace_t tr; UTIL_TraceHull( pos, pos, Vector(-12,-12,0),Vector(12,12,0), MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); if ( tr.m_pEnt || tr.startsolid ) { return false; } return true; } // makes this bit of goo burst and spawn some grubs void CASW_Alien_Goo::SpawnGrubs() { if (m_bSpawnedGrubs) return; m_bSpawnedGrubs = true; AddSolidFlags( FSOLID_NOT_SOLID ); int num_grubs = random->RandomInt(4,5); if (m_fGrubSpawnAngle <= 0) m_fGrubSpawnAngle = 180.0f; //Vector mins = WorldAlignMins() + GetAbsOrigin(); //Vector maxs = WorldAlignMaxs() + GetAbsOrigin(); if (!CollisionProp()) return; // for some reason if we calculate these inside the loop, the random numbers all come out the same. Worrying. QAngle angGrubFacing[5]; angGrubFacing[0] = GetAbsAngles(); angGrubFacing[0].y += RandomFloat(-m_fGrubSpawnAngle, m_fGrubSpawnAngle); angGrubFacing[1] = GetAbsAngles(); angGrubFacing[1].y += RandomFloat(-m_fGrubSpawnAngle, m_fGrubSpawnAngle); angGrubFacing[2] = GetAbsAngles(); angGrubFacing[2].y += RandomFloat(-m_fGrubSpawnAngle, m_fGrubSpawnAngle); angGrubFacing[3] = GetAbsAngles(); angGrubFacing[3].y += RandomFloat(-m_fGrubSpawnAngle, m_fGrubSpawnAngle); angGrubFacing[4] = GetAbsAngles(); angGrubFacing[4].y += RandomFloat(-m_fGrubSpawnAngle, m_fGrubSpawnAngle); Vector vecSpawnPos[5]; CollisionProp()->RandomPointInBounds( Vector(0.1f, 0.1f, 0.1f), Vector( 0.9f, 0.9f, 0.9f ), &vecSpawnPos[0] ); CollisionProp()->RandomPointInBounds( Vector(0.1f, 0.1f, 0.1f), Vector( 0.9f, 0.9f, 0.9f ), &vecSpawnPos[1] ); CollisionProp()->RandomPointInBounds( Vector(0.1f, 0.1f, 0.1f), Vector( 0.9f, 0.9f, 0.9f ), &vecSpawnPos[2] ); CollisionProp()->RandomPointInBounds( Vector(0.1f, 0.1f, 0.1f), Vector( 0.9f, 0.9f, 0.9f ), &vecSpawnPos[3] ); CollisionProp()->RandomPointInBounds( Vector(0.1f, 0.1f, 0.1f), Vector( 0.9f, 0.9f, 0.9f ), &vecSpawnPos[4] ); Vector mins, maxs; CollisionProp()->WorldSpaceAABB( &mins, &mins ); for (int i=0;iRandomPointInBounds( Vector(0.1f, 0.1f, 0.1f), Vector( 0.9f, 0.9f, 0.9f ), &vecSpawnPos[i] ); count++; } if (count >= 5) continue; UTIL_ASW_DroneBleed( vecSpawnPos[i], Vector(0,0,1), 4 ); CASW_Simple_Grub* pGrub = dynamic_cast(CreateNoSpawn("asw_grub", vecSpawnPos[i], angGrubFacing[i], this)); if (pGrub) { ASWGameRules()->GrubSpawned(pGrub); pGrub->AddSpawnFlags(SF_NPC_FALL_TO_GROUND); // stop it teleporting to the ground pGrub->Spawn(); pGrub->m_fFallSpeed = 250; pGrub->PlayFallingAnimation(); } } EmitSound("ASW_Parasite.EggBurst"); UTIL_ASW_EggGibs( WorldSpaceCenter(), EGG_FLAG_GRUBSACK_DIE, entindex() ); trace_t ptr; Vector vecDir( 0, 0, -1.0f ); ptr.endpos = GetLocalOrigin(); ptr.endpos[2] += 8.0f; // make blood decal on the floor trace_t Bloodtr; Vector vecTraceDir; int k; for ( k = 0 ; k < 4 ; k++ ) { vecTraceDir = vecDir; vecTraceDir.x += random->RandomFloat( -0.8f, 0.8f ); vecTraceDir.y += random->RandomFloat( -0.8f, 0.8f ); vecTraceDir.z += random->RandomFloat( -0.8f, 0.8f ); AI_TraceLine( ptr.endpos, ptr.endpos + vecTraceDir * 172, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &Bloodtr); if ( Bloodtr.fraction != 1.0 ) { UTIL_BloodDecalTrace( &Bloodtr, BLOOD_COLOR_BRIGHTGREEN ); } } UTIL_Remove( this ); } // make this goo sac burst open and spit out grubs void CASW_Alien_Goo::InputBurst( inputdata_t &inputdata ) { if (m_bHasGrubs && HasSpawnFlags(ASW_SF_BURST_ON_INPUT)) SpawnGrubs(); } void CASW_Alien_Goo::StartGooSound() { m_bPlayingAmbientSound = true; UTIL_EmitAmbientSound(GetSoundSourceIndex(), GetAbsOrigin(), "ASWGoo.GooLoop", random->RandomFloat(0.9f, 1.0f) * asw_goo_volume.GetFloat(), SNDLVL_85dB, SND_CHANGE_VOL, random->RandomInt( 95, 105 )); } void CASW_Alien_Goo::StartScreaming() { if (!m_bPlayingGooScream) { StopGooSound(); m_bPlayingGooScream = true; UTIL_EmitAmbientSound(GetSoundSourceIndex(), GetAbsOrigin(), "ASWGoo.GooScream", random->RandomFloat(0.9f, 1.0f) * asw_goo_volume.GetFloat(), SNDLVL_95dB, SND_CHANGE_VOL, random->RandomInt( 95, 105 )); } } void CASW_Alien_Goo::StopLoopingSounds() { StopGooSound(); } void CASW_Alien_Goo::StopGooSound() { if (m_bPlayingGooScream) UTIL_EmitAmbientSound(GetSoundSourceIndex(), GetAbsOrigin(), "ASWGoo.GooLoop", 0, SNDLVL_NONE, SND_STOP, 0); else UTIL_EmitAmbientSound(GetSoundSourceIndex(), GetAbsOrigin(), "ASWGoo.GooLoop", 0, SNDLVL_NONE, SND_STOP, 0); } bool CASW_Alien_Goo::Dissolve( const char *pMaterialName, float flStartTime, bool bNPCOnly, int nDissolveType ) { // Right now this prevents stuff we don't want to catch on fire from catching on fire. if( bNPCOnly && !(GetFlags() & FL_NPC) ) return false; // Can't dissolve twice if ( IsDissolving() ) return false; bool bRagdollCreated = false; CASW_Entity_Dissolve *pDissolve = CASW_Entity_Dissolve::Create( this, pMaterialName, flStartTime, nDissolveType, &bRagdollCreated ); if (pDissolve) { SetEffectEntity( pDissolve ); AddFlag( FL_DISSOLVING ); // m_flDissolveStartTime = flStartTime; EmitSound( "ASWGoo.GooDissolve" ); } return bRagdollCreated; } #else // make all our flex controllers sine in and out, each one time offset a bit to // create a rippling pulse in the mesh void CASW_Alien_Goo::SetupWeights( const matrix3x4_t *pBoneToWorld, int nFlexWeightCount, float *pFlexWeights, float *pFlexDelayedWeights ) { m_fPulseCycle += gpGlobals->frametime * m_fPulseSpeed; float interval = 1.5f / nFlexWeightCount; for (int i = 0; i < nFlexWeightCount; i++) { pFlexWeights[i] = sin(m_fPulseCycle + i * interval); if (pFlexWeights[i] < 0) pFlexWeights[i] = -pFlexWeights[i]; pFlexWeights[i] *= m_fPulseStrength; } if ( pFlexDelayedWeights ) { memset( pFlexDelayedWeights, 0, nFlexWeightCount * sizeof(float) ); } } void CASW_Alien_Goo::OnDataChanged( DataUpdateType_t type ) { BaseClass::OnDataChanged( type ); UpdateFireEmitters(); } void CASW_Alien_Goo::UpdateFireEmitters() { bool bOnFire = (m_bOnFire && !IsEffectActive(EF_NODRAW)); if (bOnFire != m_bClientOnFire) { m_bClientOnFire = bOnFire; if (m_bClientOnFire) { if ( !m_pBurningEffect ) { m_pBurningEffect = UTIL_ASW_CreateFireEffect( this ); } EmitSound( "ASWFire.BurningFlesh" ); } else { if ( m_pBurningEffect ) { ParticleProp()->StopEmission( m_pBurningEffect ); m_pBurningEffect = NULL; } StopSound("ASWFire.BurningFlesh"); if ( C_BaseEntity::IsAbsQueriesValid() ) EmitSound("ASWFire.StopBurning"); } } } void CASW_Alien_Goo::UpdateOnRemove() { BaseClass::UpdateOnRemove(); m_bOnFire = false; UpdateFireEmitters(); } #endif // not client /// ========================== grub sac version ======================= #ifndef CLIENT_DLL LINK_ENTITY_TO_CLASS( asw_grub_sac, CASW_Grub_Sac ); #endif IMPLEMENT_NETWORKCLASS_ALIASED( ASW_Grub_Sac, DT_ASW_Grub_Sac ) BEGIN_NETWORK_TABLE( CASW_Grub_Sac, DT_ASW_Grub_Sac ) #ifdef CLIENT_DLL // recvprops #else // sendprops #endif END_NETWORK_TABLE() CASW_Grub_Sac::CASW_Grub_Sac() { } CASW_Grub_Sac::~CASW_Grub_Sac() { }