#include "cbase.h" #include "asw_rocket.h" #include "smoke_trail.h" #include "IEffects.h" #include "te_effect_dispatch.h" #include "explode.h" #include "fmtstr.h" #include "asw_gamerules.h" #include "asw_marine.h" #include "particle_parse.h" #include "asw_shareddefs.h" #include "asw_parasite.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define ASW_ROCKET_MODEL "models/swarm/MiniRocket/MiniRocket.mdl" extern int g_sModelIndexFireball; // (in combatweapon.cpp) holds the index for the smoke cloud PRECACHE_REGISTER( asw_rocket ); IMPLEMENT_SERVERCLASS_ST(CASW_Rocket, DT_ASW_Rocket) END_SEND_TABLE() BEGIN_DATADESC( CASW_Rocket ) DEFINE_FIELD( m_hHomingTarget, FIELD_EHANDLE ), //DEFINE_FIELD( m_hRocketTrail, FIELD_EHANDLE ), DEFINE_FIELD( m_flGracePeriodEndsAt, FIELD_TIME ), DEFINE_FIELD( m_flDamage, FIELD_FLOAT ), DEFINE_FIELD( m_bCreateDangerSounds, FIELD_BOOLEAN ), // Function Pointers DEFINE_FUNCTION( MissileTouch ), DEFINE_FUNCTION( AccelerateThink ), DEFINE_FUNCTION( IgniteThink ), DEFINE_FUNCTION( SeekThink ), END_DATADESC() LINK_ENTITY_TO_CLASS( asw_rocket, CASW_Rocket ); ConVar asw_rocket_min_speed("asw_rocket_min_speed", "280", FCVAR_CHEAT); ConVar asw_rocket_max_speed("asw_rocket_max_speed", "600", FCVAR_CHEAT); ConVar asw_rocket_acceleration("asw_rocket_acceleration", "60", FCVAR_CHEAT); ConVar asw_rocket_hover_thrust("asw_rocket_hover_thrust", "60", FCVAR_CHEAT); ConVar asw_rocket_hover_height("asw_rocket_hover_height", "10", FCVAR_CHEAT); ConVar asw_rocket_drag("asw_rocket_drag", "0.90", FCVAR_CHEAT); ConVar asw_rocket_lifetime("asw_rocket_lifetime", "3.0", FCVAR_CHEAT); ConVar asw_rocket_homing_range("asw_rocket_homing_range", "640000", FCVAR_CHEAT); ConVar asw_rocket_wobble_freq("asw_rocket_wobble_freq", "0.25", FCVAR_CHEAT); ConVar asw_rocket_wobble_amp("asw_rocket_wobble_amp", "90", FCVAR_CHEAT); ConVar asw_rocket_debug("asw_rocket_debug", "0", FCVAR_CHEAT); #define ASW_ROCKET_MIN_SPEED asw_rocket_min_speed.GetFloat() #define ASW_ROCKET_MAX_SPEED asw_rocket_max_speed.GetFloat() #define ASW_ROCKET_ACCELERATION asw_rocket_acceleration.GetFloat() #define ASW_ROCKET_DRAG asw_rocket_drag.GetFloat() #define ASW_ROCKET_LIFETIME asw_rocket_lifetime.GetFloat(); #define ASW_ROCKET_MAX_HOMING_RANGE asw_rocket_homing_range.GetFloat() #define ROCKET_HOVER_HEIGHT asw_rocket_hover_height.GetFloat() #define ASW_ROCKET_HOVER_THRUST asw_rocket_hover_thrust.GetFloat() CASW_Rocket::CASW_Rocket() : m_bFlyingWild(false) { m_bCreateDangerSounds = false; m_szFlightSound = NULL; // this sound doesn't exist any more: "ASWRocket.Ignite"; m_szDetonationSound = "ASWRocket.Explosion"; m_CreatorWeaponClass = (Class_T)CLASS_ASW_UNKNOWN; } CASW_Rocket::~CASW_Rocket() { if ( m_hHomingTarget.Get() ) { m_RocketAssigner.Deallocate( m_hHomingTarget.Get(), this ); } } void CASW_Rocket::Precache( void ) { if ( m_szFlightSound ) PrecacheScriptSound( m_szFlightSound ); // not used: PrecacheScriptSound("ASWRocket.Accelerate"); PrecacheScriptSound("ASWRocket.Explosion"); PrecacheModel( ASW_ROCKET_MODEL ); PrecacheParticleSystem( "rocket_trail_small" ); PrecacheParticleSystem( "explosion_air_small" ); } void CASW_Rocket::Spawn( void ) { Precache(); SetSolid( SOLID_BBOX ); SetModel(ASW_ROCKET_MODEL); UTIL_SetSize( this, -Vector(4,4,4), Vector(4,4,4) ); SetTouch( &CASW_Rocket::MissileTouch ); SetCollisionGroup( ASW_COLLISION_GROUP_PLAYER_MISSILE ); SetMoveType( MOVETYPE_FLYGRAVITY, MOVECOLLIDE_FLY_BOUNCE ); SetThink( &CASW_Rocket::IgniteThink ); SetNextThink( gpGlobals->curtime + 0.01f ); SetDamage( 200.0f ); AddEffects( EF_NOSHADOW ); m_takedamage = DAMAGE_NO; m_iHealth = m_iMaxHealth = 100; m_bloodColor = DONT_BLEED; m_flGracePeriodEndsAt = 0; m_flNextWobbleTime = gpGlobals->curtime + asw_rocket_wobble_freq.GetFloat(); m_fSpawnTime = gpGlobals->curtime; m_fDieTime = gpGlobals->curtime + ASW_ROCKET_LIFETIME; AddFlag( FL_OBJECT ); } void CASW_Rocket::Activate( void ) { BaseClass::Activate(); if ( m_szFlightSound ) EmitSound( m_szFlightSound ); } void CASW_Rocket::StopLoopingSounds( void ) { if ( m_szFlightSound ) StopSound( m_szFlightSound ); } void CASW_Rocket::Event_Killed( const CTakeDamageInfo &info ) { m_takedamage = DAMAGE_NO; } unsigned int CASW_Rocket::PhysicsSolidMaskForEntity( void ) const { return BaseClass::PhysicsSolidMaskForEntity() | CONTENTS_HITBOX; } int CASW_Rocket::OnTakeDamage_Alive( const CTakeDamageInfo &info ) { return 0; // rocket can't be damaged for now. This could be expanded into shooting rockets down, etc. later if we want } void CASW_Rocket::DumbFire( void ) { SetThink( NULL ); SetMoveType( MOVETYPE_FLY ); SetModel(ASW_ROCKET_MODEL); UTIL_SetSize( this, vec3_origin, vec3_origin ); } void CASW_Rocket::SetGracePeriod( float flGracePeriod ) { m_flGracePeriodEndsAt = gpGlobals->curtime + flGracePeriod; // Go non-solid until the grace period ends AddSolidFlags( FSOLID_NOT_SOLID ); } void CASW_Rocket::AccelerateThink( void ) { Vector vecForward; // !!!UNDONE - make this work exactly the same as HL1 RPG, lest we have looping sound bugs again! //EmitSound( "ASWRocket.Accelerate" ); // SetEffects( EF_LIGHT ); AngleVectors( GetLocalAngles(), &vecForward ); SetAbsVelocity( vecForward * ASW_ROCKET_MIN_SPEED ); SetThink( &CASW_Rocket::SeekThink ); SetNextThink( gpGlobals->curtime + 0.1f ); } void CASW_Rocket::DoExplosion( bool bHitWall ) { Vector vecExplosionPos = GetAbsOrigin(); CPASFilter filter( vecExplosionPos ); EmitSound( m_szDetonationSound ); DispatchParticleEffect( "explosion_air_small", GetAbsOrigin(), vec3_angle ); CTakeDamageInfo info( this, GetOwnerEntity(), GetDamage(), DMG_BLAST ); info.SetWeapon( m_hCreatorWeapon ); ASWGameRules()->RadiusDamage( info, GetAbsOrigin(), 50, CLASS_NONE, NULL ); } void CASW_Rocket::Explode( void ) { // Don't explode against the skybox. Just pretend that // the missile flies off into the distance. Vector forward; GetVectors( &forward, NULL, NULL ); trace_t tr; UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + forward * 16, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); m_takedamage = DAMAGE_NO; SetSolid( SOLID_NONE ); if( tr.fraction == 1.0 || !(tr.surface.flags & SURF_SKY) ) { bool bHitCreature = tr.m_pEnt && tr.m_pEnt->IsNPC(); DoExplosion(!bHitCreature); } //UTIL_Remove( this ); SetTouch( NULL ); SetMoveType( MOVETYPE_NONE ); SetSolid( SOLID_NONE ); SetNextThink( gpGlobals->curtime + 0.2f ); SetThink( &CASW_Rocket::SUB_Remove ); } void CASW_Rocket::MissileTouch( CBaseEntity *pOther ) { Assert( pOther ); // Don't touch triggers (but DO hit weapons) if ( pOther->IsSolidFlagSet(FSOLID_TRIGGER|FSOLID_VOLUME_CONTENTS) && pOther->GetCollisionGroup() != COLLISION_GROUP_WEAPON ) return; if (pOther == GetOwnerEntity()) return; // make sure we don't die on things we shouldn't if ( !ASWGameRules() || !ASWGameRules()->ShouldCollide( GetCollisionGroup(), pOther->GetCollisionGroup() ) ) return; if (asw_rocket_debug.GetBool()) Msg("Rocket exploding on %d:%s\n", pOther->entindex(), pOther->GetClassname()); //Msg("owner is %d:%s\n", GetOwnerEntity() ? GetOwnerEntity()->entindex() : -1, //GetOwnerEntity() ? GetOwnerEntity()->GetClassname() : "unknown"); Explode(); } void CASW_Rocket::IgniteThink( void ) { SetMoveType( MOVETYPE_FLY ); SetModel(ASW_ROCKET_MODEL); UTIL_SetSize( this, vec3_origin, vec3_origin ); RemoveSolidFlags( FSOLID_NOT_SOLID ); //TODO: Play opening sound Vector vecForward; AngleVectors( GetLocalAngles(), &vecForward ); SetAbsVelocity( vecForward * ASW_ROCKET_MIN_SPEED ); SetThink( &CASW_Rocket::SeekThink ); SetNextThink( gpGlobals->curtime ); } CBaseEntity * CASW_Rocket::FindPotentialTarget( void ) const { float bestdist = 0; CBaseEntity *bestent = NULL; Vector v_forward, v_right, v_up; AngleVectors( GetAbsAngles(), &v_forward, &v_right, &v_up ); // find the aimtarget nearest us int count = AimTarget_ListCount(); if ( count ) { CBaseEntity **pList = (CBaseEntity **)stackalloc( sizeof(CBaseEntity *) * count ); AimTarget_ListCopy( pList, count ); CTraceFilterSkipTwoEntities filter(this, GetOwnerEntity(), COLLISION_GROUP_NONE); for ( int i = 0; i < count; i++ ) { CBaseEntity *pEntity = pList[i]; if (!pEntity || !pEntity->IsAlive() || !pEntity->edict() || !pEntity->IsNPC() ) { //Msg("not alive or not an edict, skipping\n"); continue; } if (!pEntity || !pEntity->IsAlive() || !pEntity->edict() || !pEntity->IsNPC() ) { //Msg("not alive or not an edict, skipping\n"); continue; } // don't autoaim onto marines if (pEntity->Classify() == CLASS_ASW_MARINE) continue; if ( pEntity->Classify() == CLASS_ASW_PARASITE ) { CASW_Parasite *pParasite = static_cast< CASW_Parasite* >( pEntity ); if ( pParasite->m_bInfesting ) { continue; } } Vector center = pEntity->BodyTarget( GetAbsOrigin() ); Vector center_flat = center; center_flat.z = GetAbsOrigin().z; Vector dir = (center - GetAbsOrigin()); VectorNormalize( dir ); Vector dir_flat = (center_flat - GetAbsOrigin()); VectorNormalize( dir_flat ); // make sure it's in front of the rocket float dot = DotProduct (dir, v_forward ); //if (dot < 0) //{ //continue; //} float dist = (pEntity->GetAbsOrigin() - GetAbsOrigin()).LengthSqr(); if (dist > ASW_ROCKET_MAX_HOMING_RANGE) continue; // check another marine isn't between us and the target to reduce FF trace_t tr; UTIL_TraceLine(GetAbsOrigin(), pEntity->WorldSpaceCenter(), MASK_SHOT, &filter, &tr); if (tr.fraction < 1.0f && tr.m_pEnt != pEntity && tr.m_pEnt && tr.m_pEnt->Classify() == CLASS_ASW_MARINE) continue; // does this critter already have enough rockets to kill it? { CASW_DamageAllocationMgr::IndexType_t assignmentIndex = m_RocketAssigner.Find( pEntity ); if ( m_RocketAssigner.IsValid(assignmentIndex) ) { if ( m_RocketAssigner[assignmentIndex].m_flAccumulatedDamage > pEntity->GetHealth() ) { continue; } } } // check another marine isn't between us and the target to reduce FF UTIL_TraceLine(GetAbsOrigin(), pEntity->WorldSpaceCenter(), MASK_SHOT, &filter, &tr); if (tr.fraction < 1.0f && tr.m_pEnt != pEntity && tr.m_pEnt && tr.m_pEnt->Classify() == CLASS_ASW_MARINE) continue; // increase distance if dot isn't towards us dist += (1.0f - dot) * 150; // bias of x units when object is 90 degrees to the side if (bestdist == 0 || dist < bestdist) { bestdist = dist; bestent = pEntity; } } if ( bestent && asw_rocket_debug.GetBool() ) { Vector center = bestent->BodyTarget( GetAbsOrigin() ); Vector center_flat = center; center_flat.z = GetAbsOrigin().z; Vector dir = (center - GetAbsOrigin()); VectorNormalize( dir ); Msg( "Rocket[%d] starting homing in on %s(%d) dir = %f %f %f\n", entindex(), bestent->GetClassname(), bestent->entindex(), VectorExpand( dir ) ); } } return bestent; } void CASW_Rocket::SetTarget( CBaseEntity *pTarget ) { if ( m_hHomingTarget.Get() ) { // if I had an old target, strike me from its list m_RocketAssigner.Deallocate( m_RocketAssigner.Find(pTarget), this ); } m_hHomingTarget = pTarget; if ( pTarget ) { m_RocketAssigner.Allocate( m_RocketAssigner.Insert(pTarget), this, GetDamage() ); } } void CASW_Rocket::FindHomingPosition( Vector *pTarget ) { CBaseEntity *pHomingTarget = m_hHomingTarget.Get(); if ( !pHomingTarget ) { SetTarget( pHomingTarget = FindPotentialTarget() ); m_bFlyingWild = false; } if ( pHomingTarget ) { *pTarget = pHomingTarget->WorldSpaceCenter(); return; } else { // just fly straight if there's nothing to home in on Vector vecDir = GetAbsVelocity(); vecDir.z = 0; *pTarget = GetAbsOrigin() + vecDir * 200.0f; VectorAngles( vecDir, m_vWobbleAngles ); m_bFlyingWild = true; } } Vector CASW_Rocket::IntegrateRocketThrust( const Vector &vTargetDir, ///< direction of target float flDist ) ///< distance to target const { Vector vNewVelocity; const float flSimulateScale = IsSimulatingOnAlternateTicks() ? 2 : 1 ; // apply thrust in our desired direction Vector vecThrust = vTargetDir * ASW_ROCKET_ACCELERATION * flSimulateScale; if (asw_rocket_debug.GetInt() == 2) { Msg("vecThrust = %s\n", VecToString(vecThrust)); } vNewVelocity = GetAbsVelocity() + vecThrust; // apply drag float fDrag = ASW_ROCKET_DRAG - (0.1f * GetLifeFraction()); // increase drag as lifetime goes on (to help stop rockets spinning around their target) if (flDist < 300.0f && flDist > 0) fDrag -= (1.0f - (flDist / 300.0f)) * 0.1f; // reduce drag further as we get close vNewVelocity *= ASW_ROCKET_DRAG; // cap velocity to our min/max float speed = vNewVelocity.Length(); if ((speed > ASW_ROCKET_MAX_SPEED || speed < ASW_ROCKET_MIN_SPEED) && speed > 0) { vNewVelocity *= (ASW_ROCKET_MAX_SPEED / speed); } // thrust away from the ground if we get too low trace_t tr; UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, ROCKET_HOVER_HEIGHT ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); if ( tr.fraction != 1.0f ) { if ( vNewVelocity.z < 0 ) { vNewVelocity.z += tr.fraction * ASW_ROCKET_HOVER_THRUST; if ( asw_rocket_debug.GetBool() ) Msg( "Rocket[%d] hover thrusting %f vel.z is now %f\n", entindex(), tr.fraction * ASW_ROCKET_HOVER_THRUST, vNewVelocity.z ); if ( vNewVelocity.z > 0 ) { vNewVelocity.z = 0; } } } return vNewVelocity; } void CASW_Rocket::SeekThink( void ) { // If we have a grace period, go solid when it ends if ( m_flGracePeriodEndsAt ) { if ( m_flGracePeriodEndsAt < gpGlobals->curtime ) { RemoveSolidFlags( FSOLID_NOT_SOLID ); m_flGracePeriodEndsAt = 0; } } Vector vNewVelocity = GetAbsVelocity(); if ( m_bFlyingWild ) { // wobble crazily. Poll for a new target every quarter second, and if none is found, go // careering off. if ( gpGlobals->curtime >= m_flNextWobbleTime ) { Assert( !m_hHomingTarget.Get() ); CBaseEntity *pHomingTarget = FindPotentialTarget(); if ( pHomingTarget ) { SetTarget( pHomingTarget ); m_bFlyingWild = false; } else { // pick a new wobble direction /* m_vWobbleAngles = GetAbsAngles(); m_vWobbleAngles.y = m_vWobbleAngles.y + RandomFloat( -asw_rocket_wobble_amp.GetFloat(), asw_rocket_wobble_amp.GetFloat() ) ; if ( m_vWobbleAngles.y < 0 ) { m_vWobbleAngles.y = 360 + m_vWobbleAngles.y; } else if ( m_vWobbleAngles.y > 360 ) { m_vWobbleAngles.y = fmod( m_vWobbleAngles.y, 360 ); } */ m_vWobbleAngles = GetAbsAngles(); m_vWobbleAngles.y = fmodf( m_vWobbleAngles.y + RandomFloat( -asw_rocket_wobble_amp.GetFloat(), asw_rocket_wobble_amp.GetFloat() ), 360 ); m_flNextWobbleTime = gpGlobals->curtime + asw_rocket_wobble_freq.GetFloat(); } } } if ( !m_bFlyingWild ) { Vector targetPos; FindHomingPosition( &targetPos ); // find target direction Vector vTargetDir; VectorSubtract( targetPos, GetAbsOrigin(), vTargetDir ); float flDist = VectorNormalize( vTargetDir ); // find current direction Vector vDir = GetAbsVelocity(); //float flSpeed = VectorNormalize( vDir ); vNewVelocity = IntegrateRocketThrust( vTargetDir, flDist ); // face direction of movement QAngle finalAngles; VectorAngles( vNewVelocity, finalAngles ); SetAbsAngles( finalAngles ); // set to the new calculated velocity SetAbsVelocity( vNewVelocity ); } else // wobble crazily { #pragma message("TODO: straighten out this math") if ( gpGlobals->curtime >= m_flNextWobbleTime ) { // pick a new wobble direction m_vWobbleAngles = GetAbsAngles(); m_vWobbleAngles.y = fmodf( m_vWobbleAngles.y + RandomFloat( -asw_rocket_wobble_amp.GetFloat(), asw_rocket_wobble_amp.GetFloat() ), 360 ); m_flNextWobbleTime = gpGlobals->curtime + asw_rocket_wobble_freq.GetFloat(); } QAngle finalAngles = GetAbsAngles(); finalAngles.y = ApproachAngle( m_vWobbleAngles.y, finalAngles.y, 360.f * (gpGlobals->curtime - GetLastThink()) ); Vector forward; AngleVectors( finalAngles, &forward ); vNewVelocity = forward * FastSqrtEst( vNewVelocity.LengthSqr() ); if ( IsWallDodging() ) { ComputeWallDodge( vNewVelocity ); finalAngles.y = ApproachAngle( m_vWobbleAngles.y, finalAngles.y, 360.f * (gpGlobals->curtime - GetLastThink()) ); } vNewVelocity = IntegrateRocketThrust( forward, 0 ); // face direction of movement SetAbsAngles( finalAngles ); // set to the new calculated velocity SetAbsVelocity( vNewVelocity ); } // blow us up after our lifetime if (GetLifeFraction() >= 1.0f) { Explode(); } else { // Think as soon as possible SetNextThink( gpGlobals->curtime ); } } void CASW_Rocket::ComputeWallDodge( const Vector &vCurVel ) { // trace to see if I'll hit a wall in the next second trace_t tr; UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() + ( vCurVel * 0.8f ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); if ( tr.fraction < 1.0f ) { // find a vector perpendicular to the wall Vector perp = tr.plane.normal.Cross( Vector(0,0,1) ); Vector antiperp = -perp; // figure out whether to turn left or right! if ( antiperp.Dot(vCurVel) > perp.Dot(vCurVel) ) { perp = antiperp; } // push the direction a little further away from the wall perp += fsel( vCurVel.Dot(tr.plane.normal) , -0.1f , 0.1f ) * tr.plane.normal; // work out new angles based on this direction VectorAngles( perp, m_vWobbleAngles ); } } CASW_Rocket * CASW_Rocket::Create( float fDamage, const Vector &vecOrigin, const QAngle &vecAngles, CBaseCombatCharacter *pentOwner /*= NULL */, CBaseEntity * pCreatorWeapon /*= NULL*/, const char *className /*= "asw_rocket" */ ) { CASW_Rocket *pMissile = (CASW_Rocket *) CBaseEntity::Create( className, vecOrigin, vecAngles, pentOwner ); pMissile->SetDamage( fDamage ); pMissile->ChangeFaction( pentOwner->GetFaction() ); pMissile->Activate(); if (asw_rocket_debug.GetBool()) { Msg("Rocket ang=%s\n", VecToString(vecAngles)); pMissile->m_debugOverlays |= OVERLAY_TEXT_BIT; } Vector vecForward; AngleVectors( vecAngles, &vecForward ); pMissile->SetAbsVelocity( vecForward * ASW_ROCKET_MIN_SPEED ); // + Vector( 0,0, 128 ) pMissile->m_hCreatorWeapon.Set( pCreatorWeapon ); if( pCreatorWeapon ) pMissile->m_CreatorWeaponClass = pCreatorWeapon->Classify(); return pMissile; } float CASW_Rocket::GetLifeFraction() const { if (m_fDieTime <= m_fSpawnTime) return 0; return clamp( (gpGlobals->curtime - m_fSpawnTime) / (m_fDieTime - m_fSpawnTime), 0.0f, 1.0f); } void CASW_Rocket::DrawDebugGeometryOverlays() { if (m_hHomingTarget.Get()) { NDebugOverlay::Line(GetAbsOrigin(), m_hHomingTarget->WorldSpaceCenter(), 255, 0, 0, true, 0.1f); } Vector vecTarget; FindHomingPosition(&vecTarget); NDebugOverlay::Line(GetAbsOrigin(), vecTarget, 0, 255, 0, true, 0.1f); BaseClass::DrawDebugGeometryOverlays(); } bool CASW_Rocket::IsWallDodging() const { return m_bFlyingWild; } CASW_DamageAllocationMgr CASW_Rocket::m_RocketAssigner; CASW_DamageAllocationMgr::IndexType_t CASW_DamageAllocationMgr::Find( const EHANDLE &handle ) { for ( int i = m_Targets.Count() - 1; i >= 0 ; --i ) { if ( m_Targets[i].m_hTargeted == handle ) return (&m_Targets[i]); } // didn't find return InvalidIndex(); } CASW_DamageAllocationMgr::IndexType_t CASW_DamageAllocationMgr::Insert( CBaseEntity* pTarget ) { IndexType_t i = Find( pTarget ); if ( IsValid(i) ) { // AssertMsg1( false, "Tried to double-insert %s into a damage allocation manager\n", pTarget->GetDebugName() ); return i; } else { i = &m_Targets[m_Targets.AddToTail(tuple_t(pTarget))]; return i; } } void CASW_DamageAllocationMgr::Remove( const EHANDLE &handle ) { IndexType_t i = Find( handle ); if ( IsValid(i) ) { // strike all projectiles for this target ProjectilePool_t::IndexLocalType_t j = i->m_nProjectiles; while ( m_ProjectileLists.IsValidIndex(j) ) { ProjectilePool_t::IndexLocalType_t old = j; j = m_ProjectileLists.Next(j); m_ProjectileLists.Remove( old ); } int vectoridx = i - m_Targets.Base(); Assert( &m_Targets[vectoridx] == i ); m_Targets.FastRemove( vectoridx ); } else { AssertMsg1( false, "Tried to remove %s from a damage allocation manager whence it was absent.\n", handle->GetDebugName() ); } } float CASW_DamageAllocationMgr::Allocate( IndexType_t iTarget, CBaseEntity *pProjectile, float flDamage ) { if ( !IsValid( iTarget ) ) return 0; Rebuild( iTarget ); AssertMsg2( !IsProjectileForTarget( iTarget, pProjectile ), "Double-allocated %s to %s\n", pProjectile->GetDebugName(), iTarget->m_hTargeted->GetDebugName() ); tuple_t * RESTRICT ptarget = &Elem(iTarget); ProjectilePool_t::IndexLocalType_t projIdx = m_ProjectileLists.Alloc( true ); m_ProjectileLists[projIdx].m_hHandle = pProjectile; m_ProjectileLists[projIdx].m_flDamage = flDamage; if ( m_ProjectileLists.IsValidIndex(ptarget->m_nProjectiles) ) { m_ProjectileLists.LinkBefore( ptarget->m_nProjectiles, projIdx ); } ptarget->m_nProjectiles = projIdx; return ptarget->m_flAccumulatedDamage += flDamage; } float CASW_DamageAllocationMgr::Deallocate( IndexType_t iTarget, CBaseEntity *pProjectile ) { if ( !IsValid( iTarget ) ) return 0; Rebuild( iTarget ); tuple_t * RESTRICT ptarget = &Elem(iTarget); ProjectilePool_t::IndexLocalType_t projIdx = iTarget->m_nProjectiles; const EHANDLE hProj = pProjectile; // for faster comparison while ( m_ProjectileLists.IsValidIndex(projIdx) ) { if ( m_ProjectileLists[projIdx].m_hHandle == hProj ) { ptarget->m_flAccumulatedDamage -= m_ProjectileLists[projIdx].m_flDamage; ptarget->m_nProjectiles = m_ProjectileLists.Next(projIdx); m_ProjectileLists.Remove( projIdx ); return ptarget->m_flAccumulatedDamage; // better than break bc of load-hit-store } // clean up any dead projectiles (note the work to advance // the iterator while also deleting an element) if ( !m_ProjectileLists[projIdx].m_hHandle.Get() ) { ProjectilePool_t::IndexLocalType_t toRemove = projIdx; ptarget->m_flAccumulatedDamage -= m_ProjectileLists[projIdx].m_flDamage; ptarget->m_nProjectiles = projIdx = m_ProjectileLists.Next( projIdx ); m_ProjectileLists.Remove( toRemove ); } else { Assert( static_cast(m_ProjectileLists[projIdx].m_hHandle.Get())->GetTarget() == ptarget->m_hTargeted ); projIdx = m_ProjectileLists.Next( projIdx ); } } return ptarget->m_flAccumulatedDamage; } CASW_DamageAllocationMgr::ProjectilePool_t::IndexType_t CASW_DamageAllocationMgr::GetProjectileIndexInTarget( IndexType_t iTarget, CBaseEntity *pProjectile ) { for ( CASW_DamageAllocationMgr::ProjectilePool_t::IndexLocalType_t i = iTarget->m_nProjectiles; m_ProjectileLists.IsValidIndex( i ); i = m_ProjectileLists.Next(i) ) { if ( m_ProjectileLists[i].m_hHandle.Get() == pProjectile ) { return i; } } return m_ProjectileLists.InvalidIndex(); } void CASW_DamageAllocationMgr::Rebuild( IndexType_t iTarget ) { float flRecompedDamage = 0; for ( CASW_DamageAllocationMgr::ProjectilePool_t::IndexLocalType_t i = iTarget->m_nProjectiles; m_ProjectileLists.IsValidIndex( i ); i = m_ProjectileLists.Next(i) ) { // clean up any dead projectiles (note the work to advance // the iterator while also deleting an element) if ( !m_ProjectileLists[i].m_hHandle.Get() ) { ProjectilePool_t::IndexLocalType_t toRemove = i; iTarget->m_flAccumulatedDamage -= m_ProjectileLists[i].m_flDamage; iTarget->m_nProjectiles = i = m_ProjectileLists.Next( i ); m_ProjectileLists.Remove( toRemove ); } else { flRecompedDamage += m_ProjectileLists[i].m_flDamage; } } // Assert( flRecompedDamage == iTarget->m_flAccumulatedDamage ); iTarget->m_flAccumulatedDamage = flRecompedDamage; }