You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
830 lines
24 KiB
830 lines
24 KiB
#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<float>( (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<CASW_Rocket *>(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; |
|
} |
|
|
|
|