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.
4489 lines
128 KiB
4489 lines
128 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//============================================================================= |
|
|
|
#include "cbase.h" |
|
#include "func_tank.h" |
|
#include "Sprite.h" |
|
#include "EnvLaser.h" |
|
#include "basecombatweapon.h" |
|
#include "explode.h" |
|
#include "eventqueue.h" |
|
#include "gamerules.h" |
|
#include "ammodef.h" |
|
#include "in_buttons.h" |
|
#include "soundent.h" |
|
#include "ndebugoverlay.h" |
|
#include "grenade_beam.h" |
|
#include "vstdlib/random.h" |
|
#include "engine/IEngineSound.h" |
|
#include "physics_cannister.h" |
|
#include "decals.h" |
|
#include "shake.h" |
|
#include "particle_smokegrenade.h" |
|
#include "player.h" |
|
#include "entitylist.h" |
|
#include "IEffects.h" |
|
#include "ai_basenpc.h" |
|
#include "ai_behavior_functank.h" |
|
#include "weapon_rpg.h" |
|
#include "effects.h" |
|
#include "iservervehicle.h" |
|
#include "soundenvelope.h" |
|
#include "effect_dispatch_data.h" |
|
#include "te_effect_dispatch.h" |
|
#include "props.h" |
|
#include "rumble_shared.h" |
|
#include "particle_parse.h" |
|
// NVNT turret recoil |
|
#include "haptics/haptic_utils.h" |
|
|
|
#ifdef HL2_DLL |
|
#include "hl2_player.h" |
|
#endif //HL2_DLL |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
extern Vector PointOnLineNearestPoint(const Vector& vStartPos, const Vector& vEndPos, const Vector& vPoint); |
|
|
|
ConVar mortar_visualize("mortar_visualize", "0" ); |
|
|
|
BEGIN_DATADESC( CFuncTank ) |
|
DEFINE_KEYFIELD( m_yawRate, FIELD_FLOAT, "yawrate" ), |
|
DEFINE_KEYFIELD( m_yawRange, FIELD_FLOAT, "yawrange" ), |
|
DEFINE_KEYFIELD( m_yawTolerance, FIELD_FLOAT, "yawtolerance" ), |
|
DEFINE_KEYFIELD( m_pitchRate, FIELD_FLOAT, "pitchrate" ), |
|
DEFINE_KEYFIELD( m_pitchRange, FIELD_FLOAT, "pitchrange" ), |
|
DEFINE_KEYFIELD( m_pitchTolerance, FIELD_FLOAT, "pitchtolerance" ), |
|
DEFINE_KEYFIELD( m_fireRate, FIELD_FLOAT, "firerate" ), |
|
DEFINE_FIELD( m_fireTime, FIELD_TIME ), |
|
DEFINE_KEYFIELD( m_persist, FIELD_FLOAT, "persistence" ), |
|
DEFINE_KEYFIELD( m_persist2, FIELD_FLOAT, "persistence2" ), |
|
DEFINE_KEYFIELD( m_minRange, FIELD_FLOAT, "minRange" ), |
|
DEFINE_KEYFIELD( m_maxRange, FIELD_FLOAT, "maxRange" ), |
|
DEFINE_FIELD( m_flMinRange2, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flMaxRange2, FIELD_FLOAT ), |
|
DEFINE_KEYFIELD( m_iAmmoCount, FIELD_INTEGER, "ammo_count" ), |
|
DEFINE_KEYFIELD( m_spriteScale, FIELD_FLOAT, "spritescale" ), |
|
DEFINE_KEYFIELD( m_iszSpriteSmoke, FIELD_STRING, "spritesmoke" ), |
|
DEFINE_KEYFIELD( m_iszSpriteFlash, FIELD_STRING, "spriteflash" ), |
|
DEFINE_KEYFIELD( m_bulletType, FIELD_INTEGER, "bullet" ), |
|
DEFINE_FIELD( m_nBulletCount, FIELD_INTEGER ), |
|
DEFINE_KEYFIELD( m_spread, FIELD_INTEGER, "firespread" ), |
|
DEFINE_KEYFIELD( m_iBulletDamage, FIELD_INTEGER, "bullet_damage" ), |
|
DEFINE_KEYFIELD( m_iBulletDamageVsPlayer, FIELD_INTEGER, "bullet_damage_vs_player" ), |
|
DEFINE_KEYFIELD( m_iszMaster, FIELD_STRING, "master" ), |
|
|
|
#ifdef HL2_EPISODIC |
|
DEFINE_KEYFIELD( m_iszAmmoType, FIELD_STRING, "ammotype" ), |
|
DEFINE_FIELD( m_iAmmoType, FIELD_INTEGER ), |
|
#else |
|
DEFINE_FIELD( m_iSmallAmmoType, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_iMediumAmmoType, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_iLargeAmmoType, FIELD_INTEGER ), |
|
#endif // HL2_EPISODIC |
|
|
|
DEFINE_KEYFIELD( m_soundStartRotate, FIELD_SOUNDNAME, "rotatestartsound" ), |
|
DEFINE_KEYFIELD( m_soundStopRotate, FIELD_SOUNDNAME, "rotatestopsound" ), |
|
DEFINE_KEYFIELD( m_soundLoopRotate, FIELD_SOUNDNAME, "rotatesound" ), |
|
DEFINE_KEYFIELD( m_flPlayerGracePeriod, FIELD_FLOAT, "playergraceperiod" ), |
|
DEFINE_KEYFIELD( m_flIgnoreGraceUpto, FIELD_FLOAT, "ignoregraceupto" ), |
|
DEFINE_KEYFIELD( m_flPlayerLockTimeBeforeFire, FIELD_FLOAT, "playerlocktimebeforefire" ), |
|
DEFINE_FIELD( m_flLastSawNonPlayer, FIELD_TIME ), |
|
|
|
DEFINE_FIELD( m_yawCenter, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_yawCenterWorld, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_pitchCenter, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_pitchCenterWorld, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_fireLast, FIELD_TIME ), |
|
DEFINE_FIELD( m_lastSightTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_barrelPos, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_sightOrigin, FIELD_POSITION_VECTOR ), |
|
DEFINE_FIELD( m_hFuncTankTarget, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_hController, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_vecControllerUsePos, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_flNextAttack, FIELD_TIME ), |
|
DEFINE_FIELD( m_targetEntityName, FIELD_STRING ), |
|
DEFINE_FIELD( m_hTarget, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_vTargetPosition, FIELD_POSITION_VECTOR ), |
|
DEFINE_FIELD( m_vecNPCIdleTarget, FIELD_POSITION_VECTOR ), |
|
DEFINE_FIELD( m_persist2burst, FIELD_FLOAT), |
|
//DEFINE_FIELD( m_parentMatrix, FIELD_MATRIX ), // DON'T SAVE |
|
DEFINE_FIELD( m_hControlVolume, FIELD_EHANDLE ), |
|
DEFINE_KEYFIELD( m_iszControlVolume, FIELD_STRING, "control_volume" ), |
|
DEFINE_FIELD( m_flNextControllerSearch, FIELD_TIME ), |
|
DEFINE_FIELD( m_bShouldFindNPCs, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_bNPCInRoute, FIELD_BOOLEAN ), |
|
DEFINE_KEYFIELD( m_iszNPCManPoint, FIELD_STRING, "npc_man_point" ), |
|
DEFINE_FIELD( m_bReadyToFire, FIELD_BOOLEAN ), |
|
|
|
DEFINE_KEYFIELD( m_bPerformLeading, FIELD_BOOLEAN, "LeadTarget" ), |
|
DEFINE_FIELD( m_flStartLeadFactor, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flStartLeadFactorTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_flNextLeadFactor, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_flNextLeadFactorTime, FIELD_TIME ), |
|
|
|
// Used for when the gun is attached to another entity |
|
DEFINE_KEYFIELD( m_iszBaseAttachment, FIELD_STRING, "gun_base_attach" ), |
|
DEFINE_KEYFIELD( m_iszBarrelAttachment, FIELD_STRING, "gun_barrel_attach" ), |
|
// DEFINE_FIELD( m_nBarrelAttachment, FIELD_INTEGER ), |
|
|
|
// Used when the gun is actually a part of the parent entity, and pose params aim it |
|
DEFINE_KEYFIELD( m_iszYawPoseParam, FIELD_STRING, "gun_yaw_pose_param" ), |
|
DEFINE_KEYFIELD( m_iszPitchPoseParam, FIELD_STRING, "gun_pitch_pose_param" ), |
|
DEFINE_KEYFIELD( m_flYawPoseCenter, FIELD_FLOAT, "gun_yaw_pose_center" ), |
|
DEFINE_KEYFIELD( m_flPitchPoseCenter, FIELD_FLOAT, "gun_pitch_pose_center" ), |
|
DEFINE_FIELD( m_bUsePoseParameters, FIELD_BOOLEAN ), |
|
|
|
DEFINE_KEYFIELD( m_iEffectHandling, FIELD_INTEGER, "effecthandling" ), |
|
|
|
// Inputs |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ), |
|
DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFireRate", InputSetFireRate ), |
|
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetDamage", InputSetDamage ), |
|
DEFINE_INPUTFUNC( FIELD_VECTOR, "SetTargetPosition", InputSetTargetPosition ), |
|
DEFINE_INPUTFUNC( FIELD_VECTOR, "SetTargetDir", InputSetTargetDir ), |
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetTargetEntityName", InputSetTargetEntityName ), |
|
DEFINE_INPUTFUNC( FIELD_EHANDLE, "SetTargetEntity", InputSetTargetEntity ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "ClearTargetEntity", InputClearTargetEntity ), |
|
DEFINE_INPUTFUNC( FIELD_STRING, "FindNPCToManTank", InputFindNPCToManTank ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "StopFindingNPCs", InputStopFindingNPCs ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "StartFindingNPCs", InputStartFindingNPCs ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "ForceNPCOff", InputForceNPCOff ), |
|
DEFINE_INPUTFUNC( FIELD_FLOAT, "SetMaxRange", InputSetMaxRange ), |
|
|
|
// Outputs |
|
DEFINE_OUTPUT(m_OnFire, "OnFire"), |
|
DEFINE_OUTPUT(m_OnLoseTarget, "OnLoseTarget"), |
|
DEFINE_OUTPUT(m_OnAquireTarget, "OnAquireTarget"), |
|
DEFINE_OUTPUT(m_OnAmmoDepleted, "OnAmmoDepleted"), |
|
DEFINE_OUTPUT(m_OnGotController, "OnGotController"), |
|
DEFINE_OUTPUT(m_OnLostController, "OnLostController"), |
|
DEFINE_OUTPUT(m_OnGotPlayerController, "OnGotPlayerController"), |
|
DEFINE_OUTPUT(m_OnLostPlayerController, "OnLostPlayerController"), |
|
DEFINE_OUTPUT(m_OnReadyToFire, "OnReadyToFire"), |
|
END_DATADESC() |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CFuncTank::CFuncTank() |
|
{ |
|
m_nBulletCount = 0; |
|
|
|
m_bNPCInRoute = false; |
|
m_flNextControllerSearch = 0; |
|
m_bShouldFindNPCs = true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CFuncTank::~CFuncTank( void ) |
|
{ |
|
if ( m_soundLoopRotate != NULL_STRING && ( m_spawnflags & SF_TANK_SOUNDON ) ) |
|
{ |
|
StopSound( entindex(), CHAN_STATIC, STRING(m_soundLoopRotate) ); |
|
} |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
inline bool CFuncTank::CanFire( void ) |
|
{ |
|
float flTimeDelay = gpGlobals->curtime - m_lastSightTime; |
|
|
|
// Fire when can't see enemy if time is less that persistence time |
|
if ( flTimeDelay <= m_persist ) |
|
return true; |
|
|
|
// Fire when I'm in a persistence2 burst |
|
if ( flTimeDelay <= m_persist2burst ) |
|
return true; |
|
|
|
// If less than persistence2, occasionally do another burst |
|
if ( flTimeDelay <= m_persist2 ) |
|
{ |
|
if ( random->RandomInt( 0, 30 ) == 0 ) |
|
{ |
|
m_persist2burst = flTimeDelay + 0.5f; |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: Input handler for activating the tank. |
|
//------------------------------------------------------------------------------ |
|
void CFuncTank::InputActivate( inputdata_t &inputdata ) |
|
{ |
|
TankActivate(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::TankActivate( void ) |
|
{ |
|
m_spawnflags |= SF_TANK_ACTIVE; |
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
m_fireLast = gpGlobals->curtime; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Input handler for deactivating the tank. |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::InputDeactivate( inputdata_t &inputdata ) |
|
{ |
|
TankDeactivate(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::TankDeactivate( void ) |
|
{ |
|
m_spawnflags &= ~SF_TANK_ACTIVE; |
|
m_fireLast = 0; |
|
StopRotSound(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Input handler for changing the name of the tank's target entity. |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::InputSetTargetEntityName( inputdata_t &inputdata ) |
|
{ |
|
m_targetEntityName = inputdata.value.StringID(); |
|
m_hTarget = FindTarget( m_targetEntityName, inputdata.pActivator ); |
|
|
|
// No longer aim at target position if have one |
|
m_spawnflags &= ~SF_TANK_AIM_AT_POS; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Input handler for setting a new target entity by ehandle. |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::InputSetTargetEntity( inputdata_t &inputdata ) |
|
{ |
|
if ( inputdata.value.Entity() != NULL ) |
|
{ |
|
m_targetEntityName = inputdata.value.Entity()->GetEntityName(); |
|
} |
|
else |
|
{ |
|
m_targetEntityName = NULL_STRING; |
|
} |
|
m_hTarget = inputdata.value.Entity(); |
|
|
|
// No longer aim at target position if have one |
|
m_spawnflags &= ~SF_TANK_AIM_AT_POS; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Input handler for clearing the tank's target entity |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::InputClearTargetEntity( inputdata_t &inputdata ) |
|
{ |
|
m_targetEntityName = NULL_STRING; |
|
m_hTarget = NULL; |
|
|
|
// No longer aim at target position if have one |
|
m_spawnflags &= ~SF_TANK_AIM_AT_POS; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Input handler for setting the rate of fire in shots per second. |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::InputSetFireRate( inputdata_t &inputdata ) |
|
{ |
|
m_fireRate = inputdata.value.Float(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Input handler for setting the damage |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::InputSetDamage( inputdata_t &inputdata ) |
|
{ |
|
m_iBulletDamage = inputdata.value.Int(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Input handler for setting the target as a position. |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::InputSetTargetPosition( inputdata_t &inputdata ) |
|
{ |
|
m_spawnflags |= SF_TANK_AIM_AT_POS; |
|
m_hTarget = NULL; |
|
|
|
inputdata.value.Vector3D( m_vTargetPosition ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Input handler for setting the target as a position. |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::InputSetTargetDir( inputdata_t &inputdata ) |
|
{ |
|
m_spawnflags |= SF_TANK_AIM_AT_POS; |
|
m_hTarget = NULL; |
|
|
|
Vector vecTargetDir; |
|
inputdata.value.Vector3D( vecTargetDir ); |
|
m_vTargetPosition = GetAbsOrigin() + m_barrelPos.LengthSqr() * vecTargetDir; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Input handler for telling the func_tank to find an NPC to man it. |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::InputFindNPCToManTank( inputdata_t &inputdata ) |
|
{ |
|
// Verify the func_tank is controllable and available. |
|
if ( !IsNPCControllable() && !IsNPCSetController() ) |
|
return; |
|
|
|
// If we have a controller already - don't look for one. |
|
if ( HasController() ) |
|
return; |
|
|
|
// NPC assigned to man the func_tank? |
|
CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, inputdata.value.StringID() ); |
|
if ( pEntity ) |
|
{ |
|
CAI_BaseNPC *pNPC = pEntity->MyNPCPointer(); |
|
if ( pNPC ) |
|
{ |
|
// Verify the npc has the func_tank controller behavior. |
|
CAI_FuncTankBehavior *pBehavior; |
|
if ( pNPC->GetBehavior( &pBehavior ) ) |
|
{ |
|
m_hController = pNPC; |
|
pBehavior->SetFuncTank( this ); |
|
NPC_SetInRoute( true ); |
|
return; |
|
} |
|
} |
|
} |
|
|
|
// No controller? Find a nearby NPC who can man this func_tank. |
|
NPC_FindController(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::InputStopFindingNPCs( inputdata_t &inputdata ) |
|
{ |
|
m_bShouldFindNPCs = false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::InputStartFindingNPCs( inputdata_t &inputdata ) |
|
{ |
|
m_bShouldFindNPCs = true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::InputForceNPCOff( inputdata_t &inputdata ) |
|
{ |
|
// Interrupt any npc in route (ally or not). |
|
if ( NPC_InRoute() ) |
|
{ |
|
// Interrupt the npc's route. |
|
NPC_InterruptRoute(); |
|
} |
|
|
|
// If we don't have a controller - then the gun should be free. |
|
if ( !m_hController ) |
|
return; |
|
|
|
CAI_BaseNPC *pNPC = m_hController->MyNPCPointer(); |
|
if ( !pNPC ) |
|
return; |
|
|
|
CAI_FuncTankBehavior *pBehavior; |
|
if ( pNPC->GetBehavior( &pBehavior ) ) |
|
{ |
|
pBehavior->Dismount(); |
|
} |
|
|
|
m_hController = NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::InputSetMaxRange( inputdata_t &inputdata ) |
|
{ |
|
m_maxRange = inputdata.value.Float(); |
|
m_flMaxRange2 = m_maxRange * m_maxRange; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Find the closest NPC with the func_tank behavior. |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::NPC_FindController( void ) |
|
{ |
|
// Not NPC controllable or controllable on by specified npc's return. |
|
if ( !IsNPCControllable() || IsNPCSetController() ) |
|
return; |
|
|
|
// Initialize for finding closest NPC. |
|
CAI_BaseNPC *pClosestNPC = NULL; |
|
float flClosestDist2 = ( FUNCTANK_DISTANCE_MAX * FUNCTANK_DISTANCE_MAX ); |
|
float flMinDistToEnemy2 = ( FUNCTANK_DISTANCE_MIN_TO_ENEMY * FUNCTANK_DISTANCE_MIN_TO_ENEMY ); |
|
CAI_FuncTankBehavior *pClosestBehavior = NULL; |
|
|
|
// Get the mount position. |
|
Vector vecMountPos; |
|
NPC_FindManPoint( vecMountPos ); |
|
|
|
// Search through the AI list for the closest NPC with the func_tank behavior. |
|
CAI_BaseNPC **ppAIs = g_AI_Manager.AccessAIs(); |
|
int nAICount = g_AI_Manager.NumAIs(); |
|
for ( int iAI = 0; iAI < nAICount; ++iAI ) |
|
{ |
|
CAI_BaseNPC *pNPC = ppAIs[iAI]; |
|
if ( !pNPC ) |
|
continue; |
|
|
|
if ( !pNPC->IsAlive() ) |
|
continue; |
|
|
|
if ( pNPC->IsInAScript() ) |
|
continue; |
|
|
|
CAI_FuncTankBehavior *pBehavior; |
|
if ( pNPC->GetBehavior( &pBehavior ) ) |
|
{ |
|
// Don't mount the func_tank if your "enemy" is within X feet or it or the npc. |
|
CBaseEntity *pEnemy = pNPC->GetEnemy(); |
|
|
|
if ( pEnemy ) |
|
{ |
|
if ( !IsEntityInViewCone(pEnemy) ) |
|
{ |
|
// Don't mount the tank if the tank can't be aimed at the enemy. |
|
continue; |
|
} |
|
|
|
float flDist2 = ( pEnemy->GetAbsOrigin() - pNPC->GetAbsOrigin() ).LengthSqr(); |
|
if ( flDist2 < flMinDistToEnemy2 ) |
|
continue; |
|
|
|
flDist2 = ( vecMountPos - pEnemy->GetAbsOrigin() ).LengthSqr(); |
|
if ( flDist2 < flMinDistToEnemy2 ) |
|
continue; |
|
|
|
if ( !pNPC->FVisible( vecMountPos + pNPC->GetViewOffset() ) ) |
|
continue; |
|
} |
|
|
|
trace_t tr; |
|
UTIL_TraceEntity( pNPC, vecMountPos, vecMountPos, MASK_NPCSOLID, this, pNPC->GetCollisionGroup(), &tr ); |
|
if( tr.startsolid || tr.fraction < 1.0 ) |
|
{ |
|
// Don't mount the tank if someone/something is located on the control point. |
|
continue; |
|
} |
|
|
|
if ( !pBehavior->HasFuncTank() && !pBehavior->IsBusy() ) |
|
{ |
|
float flDist2 = ( vecMountPos - pNPC->GetAbsOrigin() ).LengthSqr(); |
|
if ( flDist2 < flClosestDist2 ) |
|
{ |
|
pClosestNPC = pNPC; |
|
pClosestBehavior = pBehavior; |
|
flClosestDist2 = flDist2; |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Set the closest NPC as controller. |
|
if ( pClosestNPC ) |
|
{ |
|
m_hController = pClosestNPC; |
|
pClosestBehavior->SetFuncTank( this ); |
|
NPC_SetInRoute( true ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Draw any debug text overlays |
|
// Output : Current text offset from the top |
|
//----------------------------------------------------------------------------- |
|
int CFuncTank::DrawDebugTextOverlays(void) |
|
{ |
|
int text_offset = BaseClass::DrawDebugTextOverlays(); |
|
|
|
if (m_debugOverlays & OVERLAY_TEXT_BIT) |
|
{ |
|
// -------------- |
|
// State |
|
// -------------- |
|
char tempstr[255]; |
|
if (IsActive()) |
|
{ |
|
Q_strncpy(tempstr,"State: Active",sizeof(tempstr)); |
|
} |
|
else |
|
{ |
|
Q_strncpy(tempstr,"State: Inactive",sizeof(tempstr)); |
|
} |
|
EntityText(text_offset,tempstr,0); |
|
text_offset++; |
|
|
|
// ------------------- |
|
// Print Firing Speed |
|
// -------------------- |
|
Q_snprintf(tempstr,sizeof(tempstr),"Fire Rate: %f",m_fireRate); |
|
|
|
EntityText(text_offset,tempstr,0); |
|
text_offset++; |
|
|
|
// -------------- |
|
// Print Target |
|
// -------------- |
|
if (m_hTarget!=NULL) |
|
{ |
|
Q_snprintf(tempstr,sizeof(tempstr),"Target: %s",m_hTarget->GetDebugName()); |
|
} |
|
else |
|
{ |
|
Q_snprintf(tempstr,sizeof(tempstr),"Target: - "); |
|
} |
|
EntityText(text_offset,tempstr,0); |
|
text_offset++; |
|
|
|
// -------------- |
|
// Target Pos |
|
// -------------- |
|
if (m_spawnflags & SF_TANK_AIM_AT_POS) |
|
{ |
|
Q_snprintf(tempstr,sizeof(tempstr),"Aim Pos: %3.0f %3.0f %3.0f",m_vTargetPosition.x,m_vTargetPosition.y,m_vTargetPosition.z); |
|
} |
|
else |
|
{ |
|
Q_snprintf(tempstr,sizeof(tempstr),"Aim Pos: - "); |
|
} |
|
EntityText(text_offset,tempstr,0); |
|
text_offset++; |
|
|
|
} |
|
return text_offset; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Override base class to add display of fly direction |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::DrawDebugGeometryOverlays(void) |
|
{ |
|
// Center |
|
QAngle angCenter; |
|
Vector vecForward; |
|
angCenter = QAngle( 0, YawCenterWorld(), 0 ); |
|
AngleVectors( angCenter, &vecForward ); |
|
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + (vecForward * 64), 255,255,255, true, 0.1); |
|
|
|
// Draw the yaw ranges |
|
angCenter = QAngle( 0, YawCenterWorld() + m_yawRange, 0 ); |
|
AngleVectors( angCenter, &vecForward ); |
|
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + (vecForward * 128), 0,255,0, true, 0.1); |
|
angCenter = QAngle( 0, YawCenterWorld() - m_yawRange, 0 ); |
|
AngleVectors( angCenter, &vecForward ); |
|
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + (vecForward * 128), 0,255,0, true, 0.1); |
|
|
|
// Draw the pitch ranges |
|
angCenter = QAngle( PitchCenterWorld() + m_pitchRange, 0, 0 ); |
|
AngleVectors( angCenter, &vecForward ); |
|
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + (vecForward * 128), 255,0,0, true, 0.1); |
|
angCenter = QAngle( PitchCenterWorld() - m_pitchRange, 0, 0 ); |
|
AngleVectors( angCenter, &vecForward ); |
|
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + (vecForward * 128), 255,0,0, true, 0.1); |
|
|
|
BaseClass::DrawDebugGeometryOverlays(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : pAttacker - |
|
// flDamage - |
|
// vecDir - |
|
// ptr - |
|
// bitsDamageType - |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::TraceAttack( CBaseEntity *pAttacker, float flDamage, const Vector &vecDir, trace_t *ptr, int bitsDamageType) |
|
{ |
|
if (m_spawnflags & SF_TANK_DAMAGE_KICK) |
|
{ |
|
// Deflect the func_tank |
|
// Only adjust yaw for now |
|
if (pAttacker) |
|
{ |
|
Vector vFromAttacker = (pAttacker->EyePosition()-GetAbsOrigin()); |
|
vFromAttacker.z = 0; |
|
VectorNormalize(vFromAttacker); |
|
|
|
Vector vFromAttacker2 = (ptr->endpos-GetAbsOrigin()); |
|
vFromAttacker2.z = 0; |
|
VectorNormalize(vFromAttacker2); |
|
|
|
|
|
Vector vCrossProduct; |
|
CrossProduct(vFromAttacker,vFromAttacker2, vCrossProduct); |
|
|
|
QAngle angles; |
|
angles = GetLocalAngles(); |
|
if (vCrossProduct.z > 0) |
|
{ |
|
angles.y += 10; |
|
} |
|
else |
|
{ |
|
angles.y -= 10; |
|
} |
|
|
|
// Limit against range in y |
|
if ( angles.y > m_yawCenter + m_yawRange ) |
|
{ |
|
angles.y = m_yawCenter + m_yawRange; |
|
} |
|
else if ( angles.y < (m_yawCenter - m_yawRange) ) |
|
{ |
|
angles.y = (m_yawCenter - m_yawRange); |
|
} |
|
|
|
SetLocalAngles( angles ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : targetName - |
|
// pActivator - |
|
//----------------------------------------------------------------------------- |
|
CBaseEntity *CFuncTank::FindTarget( string_t targetName, CBaseEntity *pActivator ) |
|
{ |
|
return gEntList.FindEntityGenericNearest( STRING( targetName ), GetAbsOrigin(), 0, this, pActivator ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Caches entity key values until spawn is called. |
|
// Input : szKeyName - |
|
// szValue - |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
bool CFuncTank::KeyValue( const char *szKeyName, const char *szValue ) |
|
{ |
|
if (FStrEq(szKeyName, "barrel")) |
|
{ |
|
m_barrelPos.x = atof(szValue); |
|
return true; |
|
} |
|
|
|
if (FStrEq(szKeyName, "barrely")) |
|
{ |
|
m_barrelPos.y = atof(szValue); |
|
return true; |
|
} |
|
|
|
if (FStrEq(szKeyName, "barrelz")) |
|
{ |
|
m_barrelPos.z = atof(szValue); |
|
return true; |
|
} |
|
|
|
return BaseClass::KeyValue( szKeyName, szValue ); |
|
} |
|
|
|
|
|
static Vector gTankSpread[] = |
|
{ |
|
Vector( 0, 0, 0 ), // perfect |
|
Vector( 0.025, 0.025, 0.025 ), // small cone |
|
Vector( 0.05, 0.05, 0.05 ), // medium cone |
|
Vector( 0.1, 0.1, 0.1 ), // large cone |
|
Vector( 0.25, 0.25, 0.25 ), // extra-large cone |
|
}; |
|
#define MAX_FIRING_SPREADS ARRAYSIZE(gTankSpread) |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::Spawn( void ) |
|
{ |
|
Precache(); |
|
|
|
#ifdef HL2_EPISODIC |
|
m_iAmmoType = GetAmmoDef()->Index( STRING( m_iszAmmoType ) ); |
|
#else |
|
m_iSmallAmmoType = GetAmmoDef()->Index("Pistol"); |
|
m_iMediumAmmoType = GetAmmoDef()->Index("SMG1"); |
|
m_iLargeAmmoType = GetAmmoDef()->Index("AR2"); |
|
#endif // HL2_EPISODIC |
|
|
|
SetMoveType( MOVETYPE_PUSH ); // so it doesn't get pushed by anything |
|
SetSolid( SOLID_VPHYSICS ); |
|
SetModel( STRING( GetModelName() ) ); |
|
AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID ); |
|
|
|
if ( HasSpawnFlags(SF_TANK_NOTSOLID) ) |
|
{ |
|
AddSolidFlags( FSOLID_NOT_SOLID ); |
|
} |
|
|
|
m_hControlVolume = NULL; |
|
|
|
if ( GetParent() && GetParent()->GetBaseAnimating() ) |
|
{ |
|
CBaseAnimating *pAnim = GetParent()->GetBaseAnimating(); |
|
if ( m_iszBaseAttachment != NULL_STRING ) |
|
{ |
|
int nAttachment = pAnim->LookupAttachment( STRING( m_iszBaseAttachment ) ); |
|
if ( nAttachment != 0 ) |
|
{ |
|
SetParent( pAnim, nAttachment ); |
|
SetLocalOrigin( vec3_origin ); |
|
SetLocalAngles( vec3_angle ); |
|
} |
|
} |
|
|
|
m_bUsePoseParameters = (m_iszYawPoseParam != NULL_STRING) && (m_iszPitchPoseParam != NULL_STRING); |
|
|
|
if ( m_iszBarrelAttachment != NULL_STRING ) |
|
{ |
|
if ( m_bUsePoseParameters ) |
|
{ |
|
pAnim->SetPoseParameter( STRING( m_iszYawPoseParam ), 0 ); |
|
pAnim->SetPoseParameter( STRING( m_iszPitchPoseParam ), 0 ); |
|
pAnim->InvalidateBoneCache(); |
|
} |
|
|
|
m_nBarrelAttachment = pAnim->LookupAttachment( STRING(m_iszBarrelAttachment) ); |
|
|
|
Vector vecWorldBarrelPos; |
|
QAngle worldBarrelAngle; |
|
pAnim->GetAttachment( m_nBarrelAttachment, vecWorldBarrelPos, worldBarrelAngle ); |
|
VectorITransform( vecWorldBarrelPos, EntityToWorldTransform( ), m_barrelPos ); |
|
} |
|
|
|
if ( m_bUsePoseParameters ) |
|
{ |
|
// In this case, we're relying on the parent to have the gun model |
|
AddEffects( EF_NODRAW ); |
|
QAngle localAngles( m_flPitchPoseCenter, m_flYawPoseCenter, 0 ); |
|
SetLocalAngles( localAngles ); |
|
SetSolid( SOLID_NONE ); |
|
SetMoveType( MOVETYPE_NOCLIP ); |
|
|
|
// If our parent is a prop_dynamic, make it use hitboxes for renderbox |
|
CDynamicProp *pProp = dynamic_cast<CDynamicProp*>(GetParent()); |
|
if ( pProp ) |
|
{ |
|
pProp->m_bUseHitboxesForRenderBox = true; |
|
} |
|
} |
|
} |
|
|
|
// For smoothing out leading |
|
m_flStartLeadFactor = 1.0f; |
|
m_flNextLeadFactor = 1.0f; |
|
m_flStartLeadFactorTime = gpGlobals->curtime; |
|
m_flNextLeadFactorTime = gpGlobals->curtime + 1.0f; |
|
|
|
m_yawCenter = GetLocalAngles().y; |
|
m_yawCenterWorld = GetAbsAngles().y; |
|
m_pitchCenter = GetLocalAngles().x; |
|
m_pitchCenterWorld = GetAbsAngles().y; |
|
m_vTargetPosition = vec3_origin; |
|
|
|
if ( IsActive() || (IsControllable() && !HasController()) ) |
|
{ |
|
// Think to find controllers. |
|
SetNextThink( gpGlobals->curtime + 1.0f ); |
|
m_flNextControllerSearch = gpGlobals->curtime + 1.0f; |
|
} |
|
|
|
UpdateMatrix(); |
|
|
|
m_sightOrigin = WorldBarrelPosition(); // Point at the end of the barrel |
|
|
|
if ( m_spread > MAX_FIRING_SPREADS ) |
|
{ |
|
m_spread = 0; |
|
} |
|
|
|
// No longer aim at target position if have one |
|
m_spawnflags &= ~SF_TANK_AIM_AT_POS; |
|
|
|
if (m_spawnflags & SF_TANK_DAMAGE_KICK) |
|
{ |
|
m_takedamage = DAMAGE_YES; |
|
} |
|
|
|
// UNDONE: Do this? |
|
//m_targetEntityName = m_target; |
|
if ( GetSolid() != SOLID_NONE ) |
|
{ |
|
CreateVPhysics(); |
|
} |
|
|
|
// Setup squared min/max range. |
|
m_flMinRange2 = m_minRange * m_minRange; |
|
m_flMaxRange2 = m_maxRange * m_maxRange; |
|
m_flIgnoreGraceUpto *= m_flIgnoreGraceUpto; |
|
|
|
m_flLastSawNonPlayer = 0; |
|
|
|
if( IsActive() ) |
|
{ |
|
m_OnReadyToFire.FireOutput( this, this ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::Activate( void ) |
|
{ |
|
BaseClass::Activate(); |
|
|
|
CBaseEntity *pParent = gEntList.FindEntityByName( NULL, m_iParent ); |
|
|
|
if ((pParent != NULL) && (pParent->edict() != NULL)) |
|
{ |
|
SetParent( pParent ); |
|
} |
|
|
|
if ( GetParent() && GetParent()->GetBaseAnimating() ) |
|
{ |
|
CBaseAnimating *pAnim = GetParent()->GetBaseAnimating(); |
|
if ( m_iszBaseAttachment != NULL_STRING ) |
|
{ |
|
int nAttachment = pAnim->LookupAttachment( STRING( m_iszBaseAttachment ) ); |
|
if ( nAttachment != 0 ) |
|
{ |
|
SetParent( pAnim, nAttachment ); |
|
SetLocalOrigin( vec3_origin ); |
|
SetLocalAngles( vec3_angle ); |
|
} |
|
} |
|
|
|
m_bUsePoseParameters = (m_iszYawPoseParam != NULL_STRING) && (m_iszPitchPoseParam != NULL_STRING); |
|
|
|
if ( m_iszBarrelAttachment != NULL_STRING ) |
|
{ |
|
if ( m_bUsePoseParameters ) |
|
{ |
|
pAnim->SetPoseParameter( STRING( m_iszYawPoseParam ), 0 ); |
|
pAnim->SetPoseParameter( STRING( m_iszPitchPoseParam ), 0 ); |
|
pAnim->InvalidateBoneCache(); |
|
} |
|
|
|
m_nBarrelAttachment = pAnim->LookupAttachment( STRING(m_iszBarrelAttachment) ); |
|
|
|
Vector vecWorldBarrelPos; |
|
QAngle worldBarrelAngle; |
|
pAnim->GetAttachment( m_nBarrelAttachment, vecWorldBarrelPos, worldBarrelAngle ); |
|
VectorITransform( vecWorldBarrelPos, EntityToWorldTransform( ), m_barrelPos ); |
|
} |
|
|
|
if ( m_bUsePoseParameters ) |
|
{ |
|
// In this case, we're relying on the parent to have the gun model |
|
AddEffects( EF_NODRAW ); |
|
QAngle localAngles( m_flPitchPoseCenter, m_flYawPoseCenter, 0 ); |
|
SetLocalAngles( localAngles ); |
|
SetSolid( SOLID_NONE ); |
|
SetMoveType( MOVETYPE_NOCLIP ); |
|
|
|
// If our parent is a prop_dynamic, make it use hitboxes for renderbox |
|
CDynamicProp *pProp = dynamic_cast<CDynamicProp*>(GetParent()); |
|
if ( pProp ) |
|
{ |
|
pProp->m_bUseHitboxesForRenderBox = true; |
|
} |
|
} |
|
} |
|
|
|
// Necessary for save/load |
|
if ( (m_iszBarrelAttachment != NULL_STRING) && (m_nBarrelAttachment == 0) ) |
|
{ |
|
if ( GetParent() && GetParent()->GetBaseAnimating() ) |
|
{ |
|
CBaseAnimating *pAnim = GetParent()->GetBaseAnimating(); |
|
m_nBarrelAttachment = pAnim->LookupAttachment( STRING(m_iszBarrelAttachment) ); |
|
} |
|
} |
|
} |
|
|
|
bool CFuncTank::CreateVPhysics() |
|
{ |
|
VPhysicsInitShadow( false, false ); |
|
return true; |
|
} |
|
|
|
|
|
void CFuncTank::Precache( void ) |
|
{ |
|
if ( m_iszSpriteSmoke != NULL_STRING ) |
|
PrecacheModel( STRING(m_iszSpriteSmoke) ); |
|
if ( m_iszSpriteFlash != NULL_STRING ) |
|
PrecacheModel( STRING(m_iszSpriteFlash) ); |
|
|
|
if ( m_soundStartRotate != NULL_STRING ) |
|
PrecacheScriptSound( STRING(m_soundStartRotate) ); |
|
if ( m_soundStopRotate != NULL_STRING ) |
|
PrecacheScriptSound( STRING(m_soundStopRotate) ); |
|
if ( m_soundLoopRotate != NULL_STRING ) |
|
PrecacheScriptSound( STRING(m_soundLoopRotate) ); |
|
|
|
PrecacheScriptSound( "Func_Tank.BeginUse" ); |
|
|
|
// Precache the combine cannon |
|
if ( m_iEffectHandling == EH_COMBINE_CANNON ) |
|
{ |
|
PrecacheScriptSound( "NPC_Combine_Cannon.FireBullet" ); |
|
} |
|
} |
|
|
|
void CFuncTank::UpdateOnRemove( void ) |
|
{ |
|
if ( HasController() ) |
|
{ |
|
StopControl(); |
|
} |
|
BaseClass::UpdateOnRemove(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Barrel position |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::UpdateMatrix( void ) |
|
{ |
|
m_parentMatrix.InitFromEntity( GetParent(), GetParentAttachment() ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Barrel position |
|
//----------------------------------------------------------------------------- |
|
Vector CFuncTank::WorldBarrelPosition( void ) |
|
{ |
|
if ( (m_nBarrelAttachment == 0) || !GetParent() ) |
|
{ |
|
EntityMatrix tmp; |
|
tmp.InitFromEntity( this ); |
|
return tmp.LocalToWorld( m_barrelPos ); |
|
} |
|
|
|
Vector vecOrigin; |
|
QAngle vecAngles; |
|
CBaseAnimating *pAnim = GetParent()->GetBaseAnimating(); |
|
pAnim->GetAttachment( m_nBarrelAttachment, vecOrigin, vecAngles ); |
|
return vecOrigin; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Make the parent's pose parameters match the func_tank |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::PhysicsSimulate( void ) |
|
{ |
|
BaseClass::PhysicsSimulate(); |
|
|
|
if ( m_bUsePoseParameters && GetParent() ) |
|
{ |
|
const QAngle &angles = GetLocalAngles(); |
|
CBaseAnimating *pAnim = GetParent()->GetBaseAnimating(); |
|
pAnim->SetPoseParameter( STRING( m_iszYawPoseParam ), angles.y ); |
|
pAnim->SetPoseParameter( STRING( m_iszPitchPoseParam ), angles.x ); |
|
pAnim->StudioFrameAdvance(); |
|
} |
|
} |
|
|
|
//============================================================================= |
|
// |
|
// TANK CONTROLLING |
|
// |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CFuncTank::OnControls( CBaseEntity *pTest ) |
|
{ |
|
// Is the tank controllable. |
|
if ( !IsControllable() ) |
|
return false; |
|
|
|
if ( !m_hControlVolume ) |
|
{ |
|
// Find our control volume |
|
if ( m_iszControlVolume != NULL_STRING ) |
|
{ |
|
m_hControlVolume = dynamic_cast<CBaseTrigger*>( gEntList.FindEntityByName( NULL, m_iszControlVolume ) ); |
|
} |
|
|
|
if (( !m_hControlVolume ) && IsControllable() ) |
|
{ |
|
Msg( "ERROR: Couldn't find control volume for player-controllable func_tank %s.\n", STRING(GetEntityName()) ); |
|
return false; |
|
} |
|
} |
|
|
|
if ( m_hControlVolume->IsTouching( pTest ) ) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CFuncTank::StartControl( CBaseCombatCharacter *pController ) |
|
{ |
|
// Check to see if we have a controller. |
|
if ( HasController() && GetController() != pController ) |
|
return false; |
|
|
|
// Team only or disabled? |
|
if ( m_iszMaster != NULL_STRING ) |
|
{ |
|
if ( !UTIL_IsMasterTriggered( m_iszMaster, pController ) ) |
|
return false; |
|
} |
|
|
|
// Set func_tank as manned by player/npc. |
|
m_hController = pController; |
|
if ( pController->IsPlayer() ) |
|
{ |
|
m_spawnflags |= SF_TANK_PLAYER; |
|
|
|
CBasePlayer *pPlayer = static_cast<CBasePlayer*>( m_hController.Get() ); |
|
pPlayer->m_Local.m_iHideHUD |= HIDEHUD_WEAPONSELECTION; |
|
} |
|
else |
|
{ |
|
m_spawnflags |= SF_TANK_NPC; |
|
NPC_SetInRoute( false ); |
|
} |
|
|
|
// Holster player/npc weapon |
|
if ( m_hController->GetActiveWeapon() ) |
|
{ |
|
m_hController->GetActiveWeapon()->Holster(); |
|
} |
|
|
|
// Set the controller's position to be the use position. |
|
m_vecControllerUsePos = m_hController->GetLocalOrigin(); |
|
|
|
EmitSound( "Func_Tank.BeginUse" ); |
|
|
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
|
|
// Let the map maker know a controller has been found |
|
if ( m_hController->IsPlayer() ) |
|
{ |
|
m_OnGotPlayerController.FireOutput( this, this ); |
|
} |
|
else |
|
{ |
|
m_OnGotController.FireOutput( this, this ); |
|
} |
|
|
|
OnStartControlled(); |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// TODO: bring back the controllers current weapon |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::StopControl() |
|
{ |
|
// Do we have a controller? |
|
if ( !m_hController ) |
|
return; |
|
|
|
OnStopControlled(); |
|
|
|
// Arm player/npc weapon. |
|
if ( m_hController->GetActiveWeapon() ) |
|
{ |
|
m_hController->GetActiveWeapon()->Deploy(); |
|
} |
|
|
|
if ( m_hController->IsPlayer() ) |
|
{ |
|
CBasePlayer *pPlayer = static_cast<CBasePlayer*>( m_hController.Get() ); |
|
pPlayer->m_Local.m_iHideHUD &= ~HIDEHUD_WEAPONSELECTION; |
|
} |
|
|
|
// Stop thinking. |
|
SetNextThink( TICK_NEVER_THINK ); |
|
|
|
// Let the map maker know a controller has been lost. |
|
if ( m_hController->IsPlayer() ) |
|
{ |
|
m_OnLostPlayerController.FireOutput( this, this ); |
|
} |
|
else |
|
{ |
|
m_OnLostController.FireOutput( this, this ); |
|
} |
|
|
|
// Reset the func_tank as unmanned (player/npc). |
|
if ( m_hController->IsPlayer() ) |
|
{ |
|
m_spawnflags &= ~SF_TANK_PLAYER; |
|
} |
|
else |
|
{ |
|
m_spawnflags &= ~SF_TANK_NPC; |
|
} |
|
|
|
m_hController = NULL; |
|
|
|
// Set think, if the func_tank can think on its own. |
|
if ( IsActive() || (IsControllable() && !HasController()) ) |
|
{ |
|
// Delay the think to find controllers a bit |
|
#ifdef FUNCTANK_AUTOUSE |
|
m_flNextControllerSearch = gpGlobals->curtime + 1.0f; |
|
#else |
|
m_flNextControllerSearch = gpGlobals->curtime + 5.0f; |
|
#endif//FUNCTANK_AUTOUSE |
|
|
|
SetNextThink( m_flNextControllerSearch ); |
|
} |
|
|
|
SetLocalAngularVelocity( vec3_angle ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Called each frame by the player's ItemPostFrame |
|
//----------------------------------------------------------------------------- |
|
|
|
// NVNT turret recoil |
|
ConVar hap_turret_mag("hap_turret_mag", "5", 0); |
|
|
|
void CFuncTank::ControllerPostFrame( void ) |
|
{ |
|
// Make sure we have a contoller. |
|
Assert( m_hController != NULL ); |
|
|
|
// Control the firing rate. |
|
if ( gpGlobals->curtime < m_flNextAttack ) |
|
return; |
|
|
|
if ( !IsPlayerManned() ) |
|
return; |
|
|
|
CBasePlayer *pPlayer = static_cast<CBasePlayer*>( m_hController.Get() ); |
|
if ( ( pPlayer->m_nButtons & IN_ATTACK ) == 0 ) |
|
return; |
|
|
|
Vector forward; |
|
AngleVectors( GetAbsAngles(), &forward ); |
|
m_fireLast = gpGlobals->curtime - (1/m_fireRate) - 0.01; // to make sure the gun doesn't fire too many bullets |
|
|
|
int bulletCount = (gpGlobals->curtime - m_fireLast) * m_fireRate; |
|
|
|
if( HasSpawnFlags( SF_TANK_AIM_ASSISTANCE ) ) |
|
{ |
|
// Trace out a hull and if it hits something, adjust the shot to hit that thing. |
|
trace_t tr; |
|
Vector start = WorldBarrelPosition(); |
|
Vector dir = forward; |
|
|
|
UTIL_TraceHull( start, start + forward * 8192, -Vector(8,8,8), Vector(8,8,8), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); |
|
|
|
if( tr.m_pEnt && tr.m_pEnt->m_takedamage != DAMAGE_NO && (tr.m_pEnt->GetFlags() & FL_AIMTARGET) ) |
|
{ |
|
forward = tr.m_pEnt->WorldSpaceCenter() - start; |
|
VectorNormalize( forward ); |
|
} |
|
} |
|
|
|
Fire( bulletCount, WorldBarrelPosition(), forward, pPlayer, false ); |
|
|
|
#if defined( WIN32 ) && !defined( _X360 ) |
|
// NVNT apply a punch on the player each time fired |
|
HapticPunch(pPlayer,0,0,hap_turret_mag.GetFloat()); |
|
#endif |
|
// HACKHACK -- make some noise (that the AI can hear) |
|
CSoundEnt::InsertSound( SOUND_COMBAT, WorldSpaceCenter(), FUNCTANK_FIREVOLUME, 0.2 ); |
|
|
|
if( m_iAmmoCount > -1 ) |
|
{ |
|
if( !(m_iAmmoCount % 10) ) |
|
{ |
|
Msg("Ammo Remaining: %d\n", m_iAmmoCount ); |
|
} |
|
|
|
if( --m_iAmmoCount == 0 ) |
|
{ |
|
// Kick the player off the gun, and make myself not usable. |
|
m_spawnflags &= ~SF_TANK_CANCONTROL; |
|
StopControl(); |
|
return; |
|
} |
|
} |
|
|
|
SetNextAttack( gpGlobals->curtime + (1/m_fireRate) ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CFuncTank::HasController( void ) |
|
{ |
|
return (m_hController != NULL); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : CBaseCombatCharacter |
|
//----------------------------------------------------------------------------- |
|
CBaseCombatCharacter *CFuncTank::GetController( void ) |
|
{ |
|
return m_hController; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CFuncTank::NPC_FindManPoint( Vector &vecPos ) |
|
{ |
|
if ( m_iszNPCManPoint != NULL_STRING ) |
|
{ |
|
CBaseEntity *pEntity = gEntList.FindEntityByName( NULL, m_iszNPCManPoint ); |
|
if ( pEntity ) |
|
{ |
|
vecPos = pEntity->GetAbsOrigin(); |
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: The NPC manning this gun just saw a player for the first time since he left cover |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::NPC_JustSawPlayer( CBaseEntity *pTarget ) |
|
{ |
|
SetNextAttack( gpGlobals->curtime + m_flPlayerLockTimeBeforeFire ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::NPC_Fire( void ) |
|
{ |
|
// Control the firing rate. |
|
if ( gpGlobals->curtime < m_flNextAttack ) |
|
return; |
|
|
|
// Check for a valid npc controller. |
|
if ( !m_hController ) |
|
return; |
|
|
|
CAI_BaseNPC *pNPC = m_hController->MyNPCPointer(); |
|
if ( !pNPC ) |
|
return; |
|
|
|
// Setup for next round of firing. |
|
if ( m_nBulletCount == 0 ) |
|
{ |
|
m_nBulletCount = GetRandomBurst(); |
|
m_fireTime = 1.0f; |
|
} |
|
|
|
// m_fireLast looks like it is only needed for Active non-controlled func_tank. |
|
// m_fireLast = gpGlobals->curtime - (1/m_fireRate) - 0.01; // to make sure the gun doesn't fire too many bullets |
|
|
|
Vector vecBarrelEnd = WorldBarrelPosition(); |
|
Vector vecForward; |
|
AngleVectors( GetAbsAngles(), &vecForward ); |
|
|
|
if ( (pNPC->CapabilitiesGet() & bits_CAP_NO_HIT_SQUADMATES) && pNPC->IsInSquad() ) |
|
{ |
|
// Avoid shooting squadmates. |
|
if ( pNPC->IsSquadmateInSpread( vecBarrelEnd, vecBarrelEnd + vecForward * 2048, gTankSpread[m_spread].x, 8*12 ) ) |
|
{ |
|
return; |
|
} |
|
} |
|
|
|
if ( !HasSpawnFlags( SF_TANK_ALLOW_PLAYER_HITS ) && (pNPC->CapabilitiesGet() & bits_CAP_NO_HIT_PLAYER) ) |
|
{ |
|
// Avoid shooting player. |
|
if ( pNPC->PlayerInSpread( vecBarrelEnd, vecBarrelEnd + vecForward * 2048, gTankSpread[m_spread].x, 8*12 ) ) |
|
{ |
|
return; |
|
} |
|
} |
|
|
|
bool bIgnoreSpread = false; |
|
|
|
CBaseEntity *pEnemy = pNPC->GetEnemy(); |
|
if ( HasSpawnFlags( SF_TANK_HACKPLAYERHIT ) && pEnemy && pEnemy->IsPlayer() ) |
|
{ |
|
// Every third shot should be fired directly at the player |
|
if ( m_nBulletCount%2 == 0 ) |
|
{ |
|
Vector vecBodyTarget = pEnemy->BodyTarget( vecBarrelEnd, false ); |
|
vecForward = (vecBodyTarget - vecBarrelEnd); |
|
VectorNormalize( vecForward ); |
|
bIgnoreSpread = true; |
|
} |
|
} |
|
|
|
// Fire the bullet(s). |
|
Fire( 1, vecBarrelEnd, vecForward, m_hController, bIgnoreSpread ); |
|
--m_nBulletCount; |
|
|
|
// Check ammo counts and dismount when empty. |
|
if( m_iAmmoCount > -1 ) |
|
{ |
|
if( --m_iAmmoCount == 0 ) |
|
{ |
|
// Disable the func_tank. |
|
m_spawnflags &= ~SF_TANK_CANCONTROL; |
|
|
|
// Remove the npc. |
|
StopControl(); |
|
return; |
|
} |
|
} |
|
|
|
float flFireTime = GetRandomFireTime(); |
|
if ( m_nBulletCount != 0 ) |
|
{ |
|
m_fireTime -= flFireTime; |
|
SetNextAttack( gpGlobals->curtime + flFireTime ); |
|
} |
|
else |
|
{ |
|
SetNextAttack( gpGlobals->curtime + m_fireTime ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CFuncTank::NPC_HasEnemy( void ) |
|
{ |
|
if ( !IsNPCManned() ) |
|
return false; |
|
|
|
CAI_BaseNPC *pNPC = m_hController->MyNPCPointer(); |
|
Assert( pNPC ); |
|
|
|
return ( pNPC->GetEnemy() != NULL ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::NPC_InterruptRoute( void ) |
|
{ |
|
if ( !m_hController ) |
|
return; |
|
|
|
CAI_BaseNPC *pNPC = m_hController->MyNPCPointer(); |
|
if ( !pNPC ) |
|
return; |
|
|
|
CAI_FuncTankBehavior *pBehavior; |
|
if ( pNPC->GetBehavior( &pBehavior ) ) |
|
{ |
|
pBehavior->SetFuncTank( NULL ); |
|
} |
|
|
|
// Reset the npc controller. |
|
m_hController = NULL; |
|
|
|
// No NPC's in route. |
|
NPC_SetInRoute( false ); |
|
|
|
// Delay the think to find controllers a bit |
|
m_flNextControllerSearch = gpGlobals->curtime + 5.0f; |
|
|
|
if ( !HasController() ) |
|
{ |
|
// Start thinking to find controllers again |
|
SetNextThink( m_flNextControllerSearch ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CFuncTank::NPC_InterruptController( void ) |
|
{ |
|
// If we don't have a controller - then the gun should be free. |
|
if ( !m_hController ) |
|
return true; |
|
|
|
CAI_BaseNPC *pNPC = m_hController->MyNPCPointer(); |
|
if ( !pNPC || !pNPC->IsPlayerAlly() ) |
|
return false; |
|
|
|
CAI_FuncTankBehavior *pBehavior; |
|
if ( pNPC->GetBehavior( &pBehavior ) ) |
|
{ |
|
pBehavior->Dismount(); |
|
} |
|
|
|
m_hController = NULL; |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : float |
|
//----------------------------------------------------------------------------- |
|
float CFuncTank::GetRandomFireTime( void ) |
|
{ |
|
Assert( m_fireRate != 0 ); |
|
float flOOFireRate = 1.0f / m_fireRate; |
|
float flOOFireRateBy2 = flOOFireRate * 0.5f; |
|
float flOOFireRateBy4 = flOOFireRate * 0.25f; |
|
return random->RandomFloat( flOOFireRateBy4, flOOFireRateBy2 ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int CFuncTank::GetRandomBurst( void ) |
|
{ |
|
return random->RandomInt( m_fireRate-2, m_fireRate+2 ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pActivator - |
|
// *pCaller - |
|
// useType - |
|
// value - |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) |
|
{ |
|
if ( !IsControllable() ) |
|
return; |
|
|
|
// player controlled turret |
|
CBasePlayer *pPlayer = ToBasePlayer( pActivator ); |
|
if ( !pPlayer ) |
|
return; |
|
|
|
if ( value == 2 && useType == USE_SET ) |
|
{ |
|
ControllerPostFrame(); |
|
} |
|
else if ( m_hController != pPlayer && useType != USE_OFF ) |
|
{ |
|
// The player must be within the func_tank controls |
|
if ( !m_hControlVolume ) |
|
{ |
|
// Find our control volume |
|
if ( m_iszControlVolume != NULL_STRING ) |
|
{ |
|
m_hControlVolume = dynamic_cast<CBaseTrigger*>( gEntList.FindEntityByName( NULL, m_iszControlVolume ) ); |
|
} |
|
|
|
if (( !m_hControlVolume ) && IsControllable() ) |
|
{ |
|
Msg( "ERROR: Couldn't find control volume for player-controllable func_tank %s.\n", STRING(GetEntityName()) ); |
|
return; |
|
} |
|
} |
|
|
|
if ( !m_hControlVolume->IsTouching( pPlayer ) ) |
|
return; |
|
|
|
// Interrupt any npc in route (ally or not). |
|
if ( NPC_InRoute() ) |
|
{ |
|
// Interrupt the npc's route. |
|
NPC_InterruptRoute(); |
|
} |
|
|
|
// Interrupt NPC - if possible (they must be allies). |
|
if ( IsNPCControllable() && HasController() ) |
|
{ |
|
if ( !NPC_InterruptController() ) |
|
return; |
|
} |
|
|
|
pPlayer->SetUseEntity( this ); |
|
StartControl( pPlayer ); |
|
} |
|
else |
|
{ |
|
StopControl(); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : range - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CFuncTank::InRange( float range ) |
|
{ |
|
if ( range < m_minRange ) |
|
return FALSE; |
|
if ( (m_maxRange > 0) && (range > m_maxRange) ) |
|
return FALSE; |
|
|
|
return TRUE; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CFuncTank::InRange2( float flRange2 ) |
|
{ |
|
if ( flRange2 < m_flMinRange2 ) |
|
return false; |
|
|
|
if ( ( m_flMaxRange2 > 0.0f ) && ( flRange2 > m_flMaxRange2 ) ) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::Think( void ) |
|
{ |
|
FuncTankPreThink(); |
|
|
|
m_hFuncTankTarget = NULL; |
|
|
|
// Look for a new controller? |
|
if ( IsControllable() && !HasController() && (m_flNextControllerSearch <= gpGlobals->curtime) ) |
|
{ |
|
if ( m_bShouldFindNPCs && gpGlobals->curtime > 5.0f ) |
|
{ |
|
// Check for in route and timer. |
|
if ( !NPC_InRoute() ) |
|
{ |
|
NPC_FindController(); |
|
} |
|
} |
|
|
|
#ifdef FUNCTANK_AUTOUSE |
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex(1); |
|
bool bThinkFast = false; |
|
|
|
if( pPlayer ) |
|
{ |
|
if ( !m_hControlVolume ) |
|
{ |
|
// Find our control volume |
|
if ( m_iszControlVolume != NULL_STRING ) |
|
{ |
|
m_hControlVolume = dynamic_cast<CBaseTrigger*>( gEntList.FindEntityByName( NULL, m_iszControlVolume ) ); |
|
} |
|
|
|
if (( !m_hControlVolume ) && IsControllable() ) |
|
{ |
|
Msg( "ERROR: Couldn't find control volume for player-controllable func_tank %s.\n", STRING(GetEntityName()) ); |
|
return; |
|
} |
|
} |
|
|
|
if ( m_hControlVolume ) |
|
{ |
|
if( m_hControlVolume->IsTouching( pPlayer ) && pPlayer->FInViewCone(WorldSpaceCenter()) ) |
|
{ |
|
// If my control volume is touching a player that's facing the mounted gun, automatically use the gun. |
|
// !!!BUGBUG - this only works in cases where the player can see the gun whilst standing in the control |
|
// volume. (This works just fine for all func_tanks mounted on combine walls and small barriers) |
|
variant_t emptyVariant; |
|
AcceptInput( "Use", pPlayer, pPlayer, emptyVariant, USE_TOGGLE ); |
|
} |
|
else |
|
{ |
|
// If the player is nearby, think faster for snappier response to XBox auto mounting |
|
float flDistSqr = GetAbsOrigin().DistToSqr( pPlayer->GetAbsOrigin() ); |
|
|
|
if( flDistSqr <= Square(360) ) |
|
{ |
|
bThinkFast = true; |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Keep thinking, in case they turn NPC finding back on |
|
if ( !HasController() ) |
|
{ |
|
if( bThinkFast ) |
|
{ |
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
} |
|
else |
|
{ |
|
SetNextThink( gpGlobals->curtime + 2.0f ); |
|
} |
|
} |
|
|
|
if( bThinkFast ) |
|
{ |
|
m_flNextControllerSearch = gpGlobals->curtime + 0.1f; |
|
} |
|
else |
|
{ |
|
m_flNextControllerSearch = gpGlobals->curtime + 2.0f; |
|
} |
|
#else |
|
// Keep thinking, in case they turn NPC finding back on |
|
if ( !HasController() ) |
|
{ |
|
SetNextThink( gpGlobals->curtime + 2.0f ); |
|
} |
|
|
|
m_flNextControllerSearch = gpGlobals->curtime + 2.0f; |
|
#endif//FUNCTANK_AUTOUSE |
|
} |
|
|
|
// refresh the matrix |
|
UpdateMatrix(); |
|
|
|
SetLocalAngularVelocity( vec3_angle ); |
|
TrackTarget(); |
|
|
|
if ( fabs(GetLocalAngularVelocity().x) > 1 || fabs(GetLocalAngularVelocity().y) > 1 ) |
|
{ |
|
StartRotSound(); |
|
} |
|
else |
|
{ |
|
StopRotSound(); |
|
} |
|
|
|
FuncTankPostThink(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Aim the offset barrel at a position in parent space |
|
// Input : parentTarget - the position of the target in parent space |
|
// Output : Vector - angles in local space |
|
//----------------------------------------------------------------------------- |
|
QAngle CFuncTank::AimBarrelAt( const Vector &parentTarget ) |
|
{ |
|
Vector target = parentTarget - GetLocalOrigin(); |
|
float quadTarget = target.LengthSqr(); |
|
float quadTargetXY = target.x*target.x + target.y*target.y; |
|
|
|
// Target is too close! Can't aim at it |
|
if ( quadTarget <= m_barrelPos.LengthSqr() ) |
|
{ |
|
return GetLocalAngles(); |
|
} |
|
else |
|
{ |
|
// We're trying to aim the offset barrel at an arbitrary point. |
|
// To calculate this, I think of the target as being on a sphere with |
|
// it's center at the origin of the gun. |
|
// The rotation we need is the opposite of the rotation that moves the target |
|
// along the surface of that sphere to intersect with the gun's shooting direction |
|
// To calculate that rotation, we simply calculate the intersection of the ray |
|
// coming out of the barrel with the target sphere (that's the new target position) |
|
// and use atan2() to get angles |
|
|
|
// angles from target pos to center |
|
float targetToCenterYaw = atan2( target.y, target.x ); |
|
float centerToGunYaw = atan2( m_barrelPos.y, sqrt( quadTarget - (m_barrelPos.y*m_barrelPos.y) ) ); |
|
|
|
float targetToCenterPitch = atan2( target.z, sqrt( quadTargetXY ) ); |
|
float centerToGunPitch = atan2( -m_barrelPos.z, sqrt( quadTarget - (m_barrelPos.z*m_barrelPos.z) ) ); |
|
return QAngle( -RAD2DEG(targetToCenterPitch+centerToGunPitch), RAD2DEG( targetToCenterYaw + centerToGunYaw ), 0 ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Aim the tank at the player crosshair |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::CalcPlayerCrosshairTarget( Vector *pVecTarget ) |
|
{ |
|
// Get the player. |
|
CBasePlayer *pPlayer = static_cast<CBasePlayer*>( m_hController.Get() ); |
|
|
|
// Tank aims at player's crosshair. |
|
Vector vecStart, vecDir; |
|
trace_t tr; |
|
|
|
vecStart = pPlayer->EyePosition(); |
|
|
|
if ( !IsX360() ) |
|
{ |
|
vecDir = pPlayer->EyeDirection3D(); |
|
} |
|
else |
|
{ |
|
// Use autoaim as the eye dir. |
|
vecDir = pPlayer->GetAutoaimVector( AUTOAIM_SCALE_DEFAULT ); |
|
} |
|
|
|
// Make sure to start the trace outside of the player's bbox! |
|
UTIL_TraceLine( vecStart + vecDir * 24, vecStart + vecDir * 8192, MASK_BLOCKLOS_AND_NPCS, this, COLLISION_GROUP_NONE, &tr ); |
|
|
|
*pVecTarget = tr.endpos; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Aim the tank at the player crosshair |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::AimBarrelAtPlayerCrosshair( QAngle *pAngles ) |
|
{ |
|
Vector vecTarget; |
|
CalcPlayerCrosshairTarget( &vecTarget ); |
|
*pAngles = AimBarrelAt( m_parentMatrix.WorldToLocal( vecTarget ) ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Aim the tank at the NPC's enemy |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::CalcNPCEnemyTarget( Vector *pVecTarget ) |
|
{ |
|
Vector vecTarget; |
|
CAI_BaseNPC *pNPC = m_hController->MyNPCPointer(); |
|
|
|
// Aim the barrel at the npc's enemy, or where the npc is looking. |
|
CBaseEntity *pEnemy = pNPC->GetEnemy(); |
|
if ( pEnemy ) |
|
{ |
|
// Clear the idle target |
|
*pVecTarget = pEnemy->BodyTarget( GetAbsOrigin(), false ); |
|
m_vecNPCIdleTarget = *pVecTarget; |
|
} |
|
else |
|
{ |
|
if ( m_vecNPCIdleTarget != vec3_origin ) |
|
{ |
|
*pVecTarget = m_vecNPCIdleTarget; |
|
} |
|
else |
|
{ |
|
Vector vecForward; |
|
QAngle angCenter( 0, m_yawCenterWorld, 0 ); |
|
AngleVectors( angCenter, &vecForward ); |
|
trace_t tr; |
|
Vector vecBarrel = GetAbsOrigin() + m_barrelPos; |
|
UTIL_TraceLine( vecBarrel, vecBarrel + vecForward * 8192, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); |
|
*pVecTarget = tr.endpos; |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Aim the tank at the NPC's enemy |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::AimBarrelAtNPCEnemy( QAngle *pAngles ) |
|
{ |
|
Vector vecTarget; |
|
CalcNPCEnemyTarget( &vecTarget ); |
|
*pAngles = AimBarrelAt( m_parentMatrix.WorldToLocal( vecTarget ) ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns true if the desired angles are out of range |
|
//----------------------------------------------------------------------------- |
|
bool CFuncTank::RotateTankToAngles( const QAngle &angles, float *pDistX, float *pDistY ) |
|
{ |
|
bool bClamped = false; |
|
|
|
// Force the angles to be relative to the center position |
|
float offsetY = UTIL_AngleDistance( angles.y, m_yawCenter ); |
|
float offsetX = UTIL_AngleDistance( angles.x, m_pitchCenter ); |
|
|
|
float flActualYaw = m_yawCenter + offsetY; |
|
float flActualPitch = m_pitchCenter + offsetX; |
|
|
|
if ( ( fabs( offsetY ) > m_yawRange + m_yawTolerance ) || |
|
( fabs( offsetX ) > m_pitchRange + m_pitchTolerance ) ) |
|
{ |
|
// Limit against range in x |
|
flActualYaw = clamp( flActualYaw, m_yawCenter - m_yawRange, m_yawCenter + m_yawRange ); |
|
flActualPitch = clamp( flActualPitch, m_pitchCenter - m_pitchRange, m_pitchCenter + m_pitchRange ); |
|
|
|
bClamped = true; |
|
} |
|
|
|
// Get at the angular vel |
|
QAngle vecAngVel = GetLocalAngularVelocity(); |
|
|
|
// Move toward target at rate or less |
|
float distY = UTIL_AngleDistance( flActualYaw, GetLocalAngles().y ); |
|
vecAngVel.y = distY * 10; |
|
vecAngVel.y = clamp( vecAngVel.y, -m_yawRate, m_yawRate ); |
|
|
|
// Move toward target at rate or less |
|
float distX = UTIL_AngleDistance( flActualPitch, GetLocalAngles().x ); |
|
vecAngVel.x = distX * 10; |
|
vecAngVel.x = clamp( vecAngVel.x, -m_pitchRate, m_pitchRate ); |
|
|
|
// How exciting! We're done |
|
SetLocalAngularVelocity( vecAngVel ); |
|
|
|
if ( pDistX && pDistY ) |
|
{ |
|
*pDistX = distX; |
|
*pDistY = distY; |
|
} |
|
|
|
return bClamped; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// We lost our target! |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::LostTarget( void ) |
|
{ |
|
if (m_fireLast != 0) |
|
{ |
|
m_OnLoseTarget.FireOutput(this, this); |
|
m_fireLast = 0; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::ComputeLeadingPosition( const Vector &vecShootPosition, CBaseEntity *pTarget, Vector *pLeadPosition ) |
|
{ |
|
Vector vecTarget = pTarget->BodyTarget( vecShootPosition, false ); |
|
float flShotSpeed = GetShotSpeed(); |
|
if ( flShotSpeed == 0 ) |
|
{ |
|
*pLeadPosition = vecTarget; |
|
return; |
|
} |
|
|
|
Vector vecVelocity = pTarget->GetSmoothedVelocity(); |
|
vecVelocity.z = 0.0f; |
|
float flTargetSpeed = VectorNormalize( vecVelocity ); |
|
|
|
// Guesstimate... |
|
if ( m_flNextLeadFactorTime < gpGlobals->curtime ) |
|
{ |
|
m_flStartLeadFactor = m_flNextLeadFactor; |
|
m_flStartLeadFactorTime = gpGlobals->curtime; |
|
m_flNextLeadFactor = random->RandomFloat( 0.8f, 1.3f ); |
|
m_flNextLeadFactorTime = gpGlobals->curtime + random->RandomFloat( 2.0f, 4.0f ); |
|
} |
|
|
|
float flFactor = (gpGlobals->curtime - m_flStartLeadFactorTime) / (m_flNextLeadFactorTime - m_flStartLeadFactorTime); |
|
float flLeadFactor = SimpleSplineRemapVal( flFactor, 0.0f, 1.0f, m_flStartLeadFactor, m_flNextLeadFactor ); |
|
flTargetSpeed *= flLeadFactor; |
|
|
|
Vector vecDelta; |
|
VectorSubtract( vecShootPosition, vecTarget, vecDelta ); |
|
float flTargetToShooter = VectorNormalize( vecDelta ); |
|
float flCosTheta = DotProduct( vecDelta, vecVelocity ); |
|
|
|
// Law of cosines... z^2 = x^2 + y^2 - 2xy cos Theta |
|
// where z = flShooterToPredictedTargetPosition = flShotSpeed * predicted time |
|
// x = flTargetSpeed * predicted time |
|
// y = flTargetToShooter |
|
// solve for predicted time using at^2 + bt + c = 0, t = (-b +/- sqrt( b^2 - 4ac )) / 2a |
|
float a = flTargetSpeed * flTargetSpeed - flShotSpeed * flShotSpeed; |
|
float b = -2.0f * flTargetToShooter * flCosTheta * flTargetSpeed; |
|
float c = flTargetToShooter * flTargetToShooter; |
|
|
|
float flDiscrim = b*b - 4*a*c; |
|
if (flDiscrim < 0) |
|
{ |
|
*pLeadPosition = vecTarget; |
|
return; |
|
} |
|
|
|
flDiscrim = sqrt(flDiscrim); |
|
float t = (-b + flDiscrim) / (2.0f * a); |
|
float t2 = (-b - flDiscrim) / (2.0f * a); |
|
if ( t < t2 ) |
|
{ |
|
t = t2; |
|
} |
|
|
|
if ( t <= 0.0f ) |
|
{ |
|
*pLeadPosition = vecTarget; |
|
return; |
|
} |
|
|
|
VectorMA( vecTarget, flTargetSpeed * t, vecVelocity, *pLeadPosition ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::AimFuncTankAtTarget( void ) |
|
{ |
|
// Get world target position |
|
CBaseEntity *pTarget = NULL; |
|
trace_t tr; |
|
QAngle angles; |
|
bool bUpdateTime = false; |
|
|
|
CBaseEntity *pTargetVehicle = NULL; |
|
Vector barrelEnd = WorldBarrelPosition(); |
|
Vector worldTargetPosition; |
|
if (m_spawnflags & SF_TANK_AIM_AT_POS) |
|
{ |
|
worldTargetPosition = m_vTargetPosition; |
|
} |
|
else |
|
{ |
|
CBaseEntity *pEntity = (CBaseEntity *)m_hTarget; |
|
if ( !pEntity || ( pEntity->GetFlags() & FL_NOTARGET ) ) |
|
{ |
|
if( m_targetEntityName != NULL_STRING ) |
|
{ |
|
m_hTarget = FindTarget( m_targetEntityName, NULL ); |
|
} |
|
|
|
LostTarget(); |
|
return; |
|
} |
|
|
|
pTarget = pEntity; |
|
|
|
// Calculate angle needed to aim at target |
|
worldTargetPosition = pEntity->EyePosition(); |
|
if ( pEntity->IsPlayer() ) |
|
{ |
|
CBasePlayer *pPlayer = assert_cast<CBasePlayer*>(pEntity); |
|
pTargetVehicle = pPlayer->GetVehicleEntity(); |
|
if ( pTargetVehicle ) |
|
{ |
|
worldTargetPosition = pTargetVehicle->BodyTarget( GetAbsOrigin(), false ); |
|
} |
|
} |
|
} |
|
|
|
float range2 = worldTargetPosition.DistToSqr( barrelEnd ); |
|
if ( !InRange2( range2 ) ) |
|
{ |
|
if ( m_hTarget ) |
|
{ |
|
m_hTarget = NULL; |
|
LostTarget(); |
|
} |
|
return; |
|
} |
|
|
|
Vector vecAimOrigin = m_sightOrigin; |
|
if (m_spawnflags & SF_TANK_AIM_AT_POS) |
|
{ |
|
bUpdateTime = true; |
|
m_sightOrigin = m_vTargetPosition; |
|
vecAimOrigin = m_sightOrigin; |
|
} |
|
else |
|
{ |
|
if ( m_spawnflags & SF_TANK_LINEOFSIGHT ) |
|
{ |
|
AI_TraceLOS( barrelEnd, worldTargetPosition, this, &tr ); |
|
} |
|
else |
|
{ |
|
tr.fraction = 1.0f; |
|
tr.m_pEnt = pTarget; |
|
} |
|
|
|
// No line of sight, don't track |
|
if ( tr.fraction == 1.0 || tr.m_pEnt == pTarget || (pTargetVehicle && (tr.m_pEnt == pTargetVehicle)) ) |
|
{ |
|
if ( InRange2( range2 ) && pTarget && pTarget->IsAlive() ) |
|
{ |
|
bUpdateTime = true; |
|
|
|
// Sight position is BodyTarget with no noise (so gun doesn't bob up and down) |
|
CBaseEntity *pInstance = pTargetVehicle ? pTargetVehicle : pTarget; |
|
m_hFuncTankTarget = pInstance; |
|
|
|
m_sightOrigin = pInstance->BodyTarget( GetAbsOrigin(), false ); |
|
if ( m_bPerformLeading ) |
|
{ |
|
ComputeLeadingPosition( barrelEnd, pInstance, &vecAimOrigin ); |
|
} |
|
else |
|
{ |
|
vecAimOrigin = m_sightOrigin; |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Convert targetPosition to parent |
|
Vector vecLocalOrigin = m_parentMatrix.WorldToLocal( vecAimOrigin ); |
|
angles = AimBarrelAt( vecLocalOrigin ); |
|
|
|
// FIXME: These need to be the clamped angles |
|
float distX, distY; |
|
bool bClamped = RotateTankToAngles( angles, &distX, &distY ); |
|
if ( bClamped ) |
|
{ |
|
bUpdateTime = false; |
|
} |
|
|
|
if ( bUpdateTime ) |
|
{ |
|
if( (gpGlobals->curtime - m_lastSightTime >= 1.0) && (gpGlobals->curtime > m_flNextAttack) ) |
|
{ |
|
// Enemy was hidden for a while, and I COULD fire right now. Instead, tack a delay on. |
|
m_flNextAttack = gpGlobals->curtime + 0.5; |
|
} |
|
|
|
m_lastSightTime = gpGlobals->curtime; |
|
m_persist2burst = 0; |
|
} |
|
|
|
SetMoveDoneTime( 0.1 ); |
|
|
|
if ( CanFire() && ( ( (fabs(distX) <= m_pitchTolerance) && (fabs(distY) <= m_yawTolerance) ) || (m_spawnflags & SF_TANK_LINEOFSIGHT) ) ) |
|
{ |
|
bool fire = false; |
|
Vector forward; |
|
AngleVectors( GetLocalAngles(), &forward ); |
|
forward = m_parentMatrix.ApplyRotation( forward ); |
|
|
|
if ( m_spawnflags & SF_TANK_LINEOFSIGHT ) |
|
{ |
|
AI_TraceLine( barrelEnd, pTarget->WorldSpaceCenter(), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); |
|
|
|
if ( tr.fraction == 1.0f || (tr.m_pEnt && tr.m_pEnt == pTarget) ) |
|
{ |
|
fire = true; |
|
} |
|
} |
|
else |
|
{ |
|
fire = true; |
|
} |
|
|
|
if ( fire ) |
|
{ |
|
if (m_fireLast == 0) |
|
{ |
|
m_OnAquireTarget.FireOutput(this, this); |
|
} |
|
FiringSequence( barrelEnd, forward, this ); |
|
} |
|
else |
|
{ |
|
LostTarget(); |
|
} |
|
} |
|
else |
|
{ |
|
LostTarget(); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::TrackTarget( void ) |
|
{ |
|
QAngle angles; |
|
|
|
if( !m_bReadyToFire && m_flNextAttack <= gpGlobals->curtime ) |
|
{ |
|
m_OnReadyToFire.FireOutput( this, this ); |
|
m_bReadyToFire = true; |
|
} |
|
|
|
if ( IsPlayerManned() ) |
|
{ |
|
AimBarrelAtPlayerCrosshair( &angles ); |
|
RotateTankToAngles( angles ); |
|
SetNextThink( gpGlobals->curtime + 0.05f ); |
|
SetMoveDoneTime( 0.1 ); |
|
return; |
|
} |
|
|
|
if ( IsNPCManned() ) |
|
{ |
|
AimBarrelAtNPCEnemy( &angles ); |
|
RotateTankToAngles( angles ); |
|
SetNextThink( gpGlobals->curtime + 0.05f ); |
|
SetMoveDoneTime( 0.1 ); |
|
return; |
|
} |
|
|
|
if ( !IsActive() ) |
|
{ |
|
// If we're not active, but we're controllable, we need to keep thinking |
|
if ( IsControllable() && !HasController() ) |
|
{ |
|
// Think to find controllers. |
|
SetNextThink( m_flNextControllerSearch ); |
|
} |
|
return; |
|
} |
|
|
|
// Clean room for unnecessarily complicated old code |
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
AimFuncTankAtTarget(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Start of firing sequence. By default, just fire now. |
|
// Input : &barrelEnd - |
|
// &forward - |
|
// *pAttacker - |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::FiringSequence( const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker ) |
|
{ |
|
if ( m_fireLast != 0 ) |
|
{ |
|
int bulletCount = (gpGlobals->curtime - m_fireLast) * m_fireRate; |
|
|
|
if ( bulletCount > 0 ) |
|
{ |
|
// NOTE: Set m_fireLast first so that Fire can adjust it |
|
m_fireLast = gpGlobals->curtime; |
|
Fire( bulletCount, barrelEnd, forward, pAttacker, false ); |
|
} |
|
} |
|
else |
|
{ |
|
m_fireLast = gpGlobals->curtime; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::DoMuzzleFlash( void ) |
|
{ |
|
// If we're parented to something, make it play the muzzleflash |
|
if ( m_bUsePoseParameters && GetParent() ) |
|
{ |
|
CBaseAnimating *pAnim = GetParent()->GetBaseAnimating(); |
|
pAnim->DoMuzzleFlash(); |
|
|
|
// Do the AR2 muzzle flash |
|
if ( m_iEffectHandling == EH_COMBINE_CANNON ) |
|
{ |
|
CEffectData data; |
|
data.m_nAttachmentIndex = m_nBarrelAttachment; |
|
data.m_nEntIndex = pAnim->entindex(); |
|
|
|
// FIXME: Create a custom entry here! |
|
DispatchEffect( "ChopperMuzzleFlash", data ); |
|
} |
|
else |
|
{ |
|
CEffectData data; |
|
data.m_nEntIndex = pAnim->entindex(); |
|
data.m_nAttachmentIndex = m_nBarrelAttachment; |
|
data.m_flScale = 1.0f; |
|
data.m_fFlags = MUZZLEFLASH_COMBINE; |
|
|
|
DispatchEffect( "MuzzleFlash", data ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : const char |
|
//----------------------------------------------------------------------------- |
|
const char *CFuncTank::GetTracerType( void ) |
|
{ |
|
switch( m_iEffectHandling ) |
|
{ |
|
case EH_AR2: |
|
return "AR2Tracer"; |
|
|
|
case EH_COMBINE_CANNON: |
|
return "HelicopterTracer"; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Fire targets and spawn sprites. |
|
// Input : bulletCount - |
|
// barrelEnd - |
|
// forward - |
|
// pAttacker - |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ) |
|
{ |
|
// If we have a specific effect handler, apply it's effects |
|
if ( m_iEffectHandling == EH_AR2 ) |
|
{ |
|
DoMuzzleFlash(); |
|
|
|
// Play the AR2 sound |
|
EmitSound( "Weapon_functank.Single" ); |
|
} |
|
else if ( m_iEffectHandling == EH_COMBINE_CANNON ) |
|
{ |
|
DoMuzzleFlash(); |
|
|
|
// Play the cannon sound |
|
EmitSound( "NPC_Combine_Cannon.FireBullet" ); |
|
} |
|
else |
|
{ |
|
if ( m_iszSpriteSmoke != NULL_STRING ) |
|
{ |
|
CSprite *pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteSmoke), barrelEnd, TRUE ); |
|
pSprite->AnimateAndDie( random->RandomFloat( 15.0, 20.0 ) ); |
|
pSprite->SetTransparency( kRenderTransAlpha, m_clrRender->r, m_clrRender->g, m_clrRender->b, 255, kRenderFxNone ); |
|
|
|
Vector vecVelocity( 0, 0, random->RandomFloat(40, 80) ); |
|
pSprite->SetAbsVelocity( vecVelocity ); |
|
pSprite->SetScale( m_spriteScale ); |
|
} |
|
if ( m_iszSpriteFlash != NULL_STRING ) |
|
{ |
|
CSprite *pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteFlash), barrelEnd, TRUE ); |
|
pSprite->AnimateAndDie( 5 ); |
|
pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); |
|
pSprite->SetScale( m_spriteScale ); |
|
} |
|
} |
|
|
|
if( pAttacker && pAttacker->IsPlayer() ) |
|
{ |
|
if ( IsX360() ) |
|
{ |
|
UTIL_PlayerByIndex(1)->RumbleEffect( RUMBLE_AR2, 0, RUMBLE_FLAG_RESTART | RUMBLE_FLAG_RANDOM_AMPLITUDE ); |
|
} |
|
else |
|
{ |
|
CSoundEnt::InsertSound( SOUND_MOVE_AWAY, barrelEnd + forward * 32.0f, 32.0f, 0.2f, pAttacker, SOUNDENT_CHANNEL_WEAPON ); |
|
} |
|
} |
|
|
|
|
|
m_OnFire.FireOutput(this, this); |
|
m_bReadyToFire = false; |
|
} |
|
|
|
|
|
void CFuncTank::TankTrace( const Vector &vecStart, const Vector &vecForward, const Vector &vecSpread, trace_t &tr ) |
|
{ |
|
Vector forward, right, up; |
|
|
|
AngleVectors( GetAbsAngles(), &forward, &right, &up ); |
|
// get circular gaussian spread |
|
float x, y, z; |
|
do { |
|
x = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5); |
|
y = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5); |
|
z = x*x+y*y; |
|
} while (z > 1); |
|
Vector vecDir = vecForward + |
|
x * vecSpread.x * right + |
|
y * vecSpread.y * up; |
|
Vector vecEnd; |
|
|
|
vecEnd = vecStart + vecDir * MAX_TRACE_LENGTH; |
|
UTIL_TraceLine( vecStart, vecEnd, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr ); |
|
} |
|
|
|
|
|
void CFuncTank::StartRotSound( void ) |
|
{ |
|
if ( m_spawnflags & SF_TANK_SOUNDON ) |
|
return; |
|
m_spawnflags |= SF_TANK_SOUNDON; |
|
|
|
if ( m_soundLoopRotate != NULL_STRING ) |
|
{ |
|
CPASAttenuationFilter filter( this ); |
|
filter.MakeReliable(); |
|
|
|
EmitSound_t ep; |
|
ep.m_nChannel = CHAN_STATIC; |
|
ep.m_pSoundName = (char*)STRING(m_soundLoopRotate); |
|
ep.m_flVolume = 0.85; |
|
ep.m_SoundLevel = SNDLVL_NORM; |
|
|
|
EmitSound( filter, entindex(), ep ); |
|
} |
|
|
|
if ( m_soundStartRotate != NULL_STRING ) |
|
{ |
|
CPASAttenuationFilter filter( this ); |
|
|
|
EmitSound_t ep; |
|
ep.m_nChannel = CHAN_BODY; |
|
ep.m_pSoundName = (char*)STRING(m_soundStartRotate); |
|
ep.m_flVolume = 1.0f; |
|
ep.m_SoundLevel = SNDLVL_NORM; |
|
|
|
EmitSound( filter, entindex(), ep ); |
|
} |
|
} |
|
|
|
|
|
void CFuncTank::StopRotSound( void ) |
|
{ |
|
if ( m_spawnflags & SF_TANK_SOUNDON ) |
|
{ |
|
if ( m_soundLoopRotate != NULL_STRING ) |
|
{ |
|
StopSound( entindex(), CHAN_STATIC, (char*)STRING(m_soundLoopRotate) ); |
|
} |
|
if ( m_soundStopRotate != NULL_STRING ) |
|
{ |
|
CPASAttenuationFilter filter( this ); |
|
|
|
EmitSound_t ep; |
|
ep.m_nChannel = CHAN_BODY; |
|
ep.m_pSoundName = (char*)STRING(m_soundStopRotate); |
|
ep.m_flVolume = 1.0f; |
|
ep.m_SoundLevel = SNDLVL_NORM; |
|
|
|
EmitSound( filter, entindex(), ep ); |
|
} |
|
} |
|
m_spawnflags &= ~SF_TANK_SOUNDON; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CFuncTank::IsEntityInViewCone( CBaseEntity *pEntity ) |
|
{ |
|
// First check to see if the enemy is in range. |
|
Vector vecBarrelEnd = WorldBarrelPosition(); |
|
float flRange2 = ( pEntity->GetAbsOrigin() - vecBarrelEnd ).LengthSqr(); |
|
|
|
if( !(GetSpawnFlags() & SF_TANK_IGNORE_RANGE_IN_VIEWCONE) ) |
|
{ |
|
if ( !InRange2( flRange2 ) ) |
|
return false; |
|
} |
|
|
|
// If we're trying to shoot at a player, and we've seen a non-player recently, check the grace period |
|
if ( m_flPlayerGracePeriod && pEntity->IsPlayer() && (gpGlobals->curtime - m_flLastSawNonPlayer) < m_flPlayerGracePeriod ) |
|
{ |
|
// Grace period is ignored under a certain distance |
|
if ( flRange2 > m_flIgnoreGraceUpto ) |
|
return false; |
|
} |
|
|
|
// Check to see if the entity center lies within the yaw and pitch constraints. |
|
// This isn't horribly accurate, but should do for now. |
|
QAngle angGun; |
|
angGun = AimBarrelAt( m_parentMatrix.WorldToLocal( pEntity->GetAbsOrigin() ) ); |
|
|
|
// Force the angles to be relative to the center position |
|
float flOffsetY = UTIL_AngleDistance( angGun.y, m_yawCenter ); |
|
float flOffsetX = UTIL_AngleDistance( angGun.x, m_pitchCenter ); |
|
angGun.y = m_yawCenter + flOffsetY; |
|
angGun.x = m_pitchCenter + flOffsetX; |
|
|
|
if ( ( fabs( flOffsetY ) > m_yawRange + m_yawTolerance ) || ( fabs( flOffsetX ) > m_pitchRange + m_pitchTolerance ) ) |
|
return false; |
|
|
|
// Remember the last time we saw a non-player |
|
if ( !pEntity->IsPlayer() ) |
|
{ |
|
m_flLastSawNonPlayer = gpGlobals->curtime; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Return true if this func tank can see the enemy |
|
//----------------------------------------------------------------------------- |
|
bool CFuncTank::HasLOSTo( CBaseEntity *pEntity ) |
|
{ |
|
if ( !pEntity ) |
|
return false; |
|
|
|
// Get the barrel position |
|
Vector vecBarrelEnd = WorldBarrelPosition(); |
|
Vector vecTarget = pEntity->BodyTarget( GetAbsOrigin(), false ); |
|
trace_t tr; |
|
|
|
// Ignore the func_tank and any prop it's parented to |
|
CTraceFilterSkipTwoEntities traceFilter( this, GetParent(), COLLISION_GROUP_NONE ); |
|
|
|
// UNDONE: Should this hit BLOCKLOS brushes? |
|
AI_TraceLine( vecBarrelEnd, vecTarget, MASK_BLOCKLOS_AND_NPCS, &traceFilter, &tr ); |
|
|
|
CBaseEntity *pHitEntity = tr.m_pEnt; |
|
|
|
// Is entity in a vehicle? if so, verify vehicle is target and return if so (so npc shoots at vehicle) |
|
CBaseCombatCharacter *pCCEntity = pEntity->MyCombatCharacterPointer(); |
|
if ( pCCEntity != NULL && pCCEntity->IsInAVehicle() ) |
|
{ |
|
// Ok, player in vehicle, check if vehicle is target we're looking at, fire if it is |
|
// Also, check to see if the owner of the entity is the vehicle, in which case it's valid too. |
|
// This catches vehicles that use bone followers. |
|
CBaseEntity *pVehicle = pCCEntity->GetVehicle()->GetVehicleEnt(); |
|
if ( pHitEntity == pVehicle || ( pHitEntity != NULL && pHitEntity->GetOwnerEntity() == pVehicle ) ) |
|
return true; |
|
} |
|
|
|
return ( tr.fraction == 1.0 || tr.m_pEnt == pEntity ); |
|
} |
|
|
|
// ############################################################################# |
|
// CFuncTankGun |
|
// ############################################################################# |
|
class CFuncTankGun : public CFuncTank |
|
{ |
|
public: |
|
DECLARE_CLASS( CFuncTankGun, CFuncTank ); |
|
|
|
void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ); |
|
}; |
|
LINK_ENTITY_TO_CLASS( func_tank, CFuncTankGun ); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFuncTankGun::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ) |
|
{ |
|
int i; |
|
|
|
FireBulletsInfo_t info; |
|
info.m_iShots = 1; |
|
info.m_vecSrc = barrelEnd; |
|
info.m_vecDirShooting = forward; |
|
if ( bIgnoreSpread ) |
|
{ |
|
info.m_vecSpread = gTankSpread[0]; |
|
} |
|
else |
|
{ |
|
info.m_vecSpread = gTankSpread[m_spread]; |
|
} |
|
|
|
info.m_flDistance = MAX_TRACE_LENGTH; |
|
info.m_iTracerFreq = 1; |
|
info.m_flDamage = m_iBulletDamage; |
|
info.m_iPlayerDamage = m_iBulletDamageVsPlayer; |
|
info.m_pAttacker = pAttacker; |
|
info.m_pAdditionalIgnoreEnt = GetParent(); |
|
|
|
#ifdef HL2_EPISODIC |
|
if ( m_iAmmoType != -1 ) |
|
{ |
|
for ( i = 0; i < bulletCount; i++ ) |
|
{ |
|
info.m_iAmmoType = m_iAmmoType; |
|
FireBullets( info ); |
|
} |
|
} |
|
#else |
|
for ( i = 0; i < bulletCount; i++ ) |
|
{ |
|
switch( m_bulletType ) |
|
{ |
|
case TANK_BULLET_SMALL: |
|
info.m_iAmmoType = m_iSmallAmmoType; |
|
FireBullets( info ); |
|
break; |
|
|
|
case TANK_BULLET_MEDIUM: |
|
info.m_iAmmoType = m_iMediumAmmoType; |
|
FireBullets( info ); |
|
break; |
|
|
|
case TANK_BULLET_LARGE: |
|
info.m_iAmmoType = m_iLargeAmmoType; |
|
FireBullets( info ); |
|
break; |
|
|
|
default: |
|
case TANK_BULLET_NONE: |
|
break; |
|
} |
|
} |
|
#endif // HL2_EPISODIC |
|
|
|
CFuncTank::Fire( bulletCount, barrelEnd, forward, pAttacker, bIgnoreSpread ); |
|
} |
|
|
|
// ############################################################################# |
|
// CFuncTankPulseLaser |
|
// ############################################################################# |
|
class CFuncTankPulseLaser : public CFuncTankGun |
|
{ |
|
public: |
|
DECLARE_CLASS( CFuncTankPulseLaser, CFuncTankGun ); |
|
DECLARE_DATADESC(); |
|
|
|
void Precache(); |
|
void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ); |
|
|
|
float m_flPulseSpeed; |
|
float m_flPulseWidth; |
|
color32 m_flPulseColor; |
|
float m_flPulseLife; |
|
float m_flPulseLag; |
|
string_t m_sPulseFireSound; |
|
}; |
|
LINK_ENTITY_TO_CLASS( func_tankpulselaser, CFuncTankPulseLaser ); |
|
|
|
BEGIN_DATADESC( CFuncTankPulseLaser ) |
|
|
|
DEFINE_KEYFIELD( m_flPulseSpeed, FIELD_FLOAT, "PulseSpeed" ), |
|
DEFINE_KEYFIELD( m_flPulseWidth, FIELD_FLOAT, "PulseWidth" ), |
|
DEFINE_KEYFIELD( m_flPulseColor, FIELD_COLOR32, "PulseColor" ), |
|
DEFINE_KEYFIELD( m_flPulseLife, FIELD_FLOAT, "PulseLife" ), |
|
DEFINE_KEYFIELD( m_flPulseLag, FIELD_FLOAT, "PulseLag" ), |
|
DEFINE_KEYFIELD( m_sPulseFireSound, FIELD_SOUNDNAME, "PulseFireSound" ), |
|
|
|
END_DATADESC() |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
void CFuncTankPulseLaser::Precache(void) |
|
{ |
|
UTIL_PrecacheOther( "grenade_beam" ); |
|
|
|
if ( m_sPulseFireSound != NULL_STRING ) |
|
{ |
|
PrecacheScriptSound( STRING(m_sPulseFireSound) ); |
|
} |
|
BaseClass::Precache(); |
|
} |
|
//------------------------------------------------------------------------------ |
|
// Purpose : |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
void CFuncTankPulseLaser::Fire( int bulletCount, const Vector &barrelEnd, const Vector &vecForward, CBaseEntity *pAttacker, bool bIgnoreSpread ) |
|
{ |
|
// -------------------------------------------------- |
|
// Get direction vectors for spread |
|
// -------------------------------------------------- |
|
Vector vecUp = Vector(0,0,1); |
|
Vector vecRight; |
|
CrossProduct ( vecForward, vecUp, vecRight ); |
|
CrossProduct ( vecForward, -vecRight, vecUp ); |
|
|
|
for ( int i = 0; i < bulletCount; i++ ) |
|
{ |
|
// get circular gaussian spread |
|
float x, y, z; |
|
do { |
|
x = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5); |
|
y = random->RandomFloat(-0.5,0.5) + random->RandomFloat(-0.5,0.5); |
|
z = x*x+y*y; |
|
} while (z > 1); |
|
|
|
Vector vecDir = vecForward + x * gTankSpread[m_spread].x * vecRight + y * gTankSpread[m_spread].y * vecUp; |
|
|
|
CGrenadeBeam *pPulse = CGrenadeBeam::Create( pAttacker, barrelEnd); |
|
pPulse->Format(m_flPulseColor, m_flPulseWidth); |
|
pPulse->Shoot(vecDir,m_flPulseSpeed,m_flPulseLife,m_flPulseLag,m_iBulletDamage); |
|
|
|
if ( m_sPulseFireSound != NULL_STRING ) |
|
{ |
|
CPASAttenuationFilter filter( this, 0.6f ); |
|
|
|
EmitSound_t ep; |
|
ep.m_nChannel = CHAN_WEAPON; |
|
ep.m_pSoundName = (char*)STRING(m_sPulseFireSound); |
|
ep.m_flVolume = 1.0f; |
|
ep.m_SoundLevel = SNDLVL_85dB; |
|
|
|
EmitSound( filter, entindex(), ep ); |
|
} |
|
|
|
} |
|
CFuncTank::Fire( bulletCount, barrelEnd, vecForward, pAttacker, bIgnoreSpread ); |
|
} |
|
|
|
// ############################################################################# |
|
// CFuncTankLaser |
|
// ############################################################################# |
|
class CFuncTankLaser : public CFuncTank |
|
{ |
|
DECLARE_CLASS( CFuncTankLaser, CFuncTank ); |
|
public: |
|
void Activate( void ); |
|
void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ); |
|
void Think( void ); |
|
CEnvLaser *GetLaser( void ); |
|
|
|
DECLARE_DATADESC(); |
|
|
|
private: |
|
CEnvLaser *m_pLaser; |
|
float m_laserTime; |
|
string_t m_iszLaserName; |
|
}; |
|
LINK_ENTITY_TO_CLASS( func_tanklaser, CFuncTankLaser ); |
|
|
|
BEGIN_DATADESC( CFuncTankLaser ) |
|
|
|
DEFINE_KEYFIELD( m_iszLaserName, FIELD_STRING, "laserentity" ), |
|
|
|
DEFINE_FIELD( m_pLaser, FIELD_CLASSPTR ), |
|
DEFINE_FIELD( m_laserTime, FIELD_TIME ), |
|
|
|
END_DATADESC() |
|
|
|
|
|
void CFuncTankLaser::Activate( void ) |
|
{ |
|
BaseClass::Activate(); |
|
|
|
if ( !GetLaser() ) |
|
{ |
|
UTIL_Remove(this); |
|
Warning( "Laser tank with no env_laser!\n" ); |
|
} |
|
else |
|
{ |
|
m_pLaser->TurnOff(); |
|
} |
|
} |
|
|
|
|
|
CEnvLaser *CFuncTankLaser::GetLaser( void ) |
|
{ |
|
if ( m_pLaser ) |
|
return m_pLaser; |
|
|
|
CBaseEntity *pLaser = gEntList.FindEntityByName( NULL, m_iszLaserName ); |
|
while ( pLaser ) |
|
{ |
|
// Found the landmark |
|
if ( FClassnameIs( pLaser, "env_laser" ) ) |
|
{ |
|
m_pLaser = (CEnvLaser *)pLaser; |
|
break; |
|
} |
|
else |
|
{ |
|
pLaser = gEntList.FindEntityByName( pLaser, m_iszLaserName ); |
|
} |
|
} |
|
|
|
return m_pLaser; |
|
} |
|
|
|
|
|
void CFuncTankLaser::Think( void ) |
|
{ |
|
if ( m_pLaser && (gpGlobals->curtime > m_laserTime) ) |
|
m_pLaser->TurnOff(); |
|
|
|
CFuncTank::Think(); |
|
} |
|
|
|
|
|
void CFuncTankLaser::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ) |
|
{ |
|
int i; |
|
trace_t tr; |
|
|
|
if ( GetLaser() ) |
|
{ |
|
for ( i = 0; i < bulletCount; i++ ) |
|
{ |
|
m_pLaser->SetLocalOrigin( barrelEnd ); |
|
TankTrace( barrelEnd, forward, gTankSpread[m_spread], tr ); |
|
|
|
m_laserTime = gpGlobals->curtime; |
|
m_pLaser->TurnOn(); |
|
m_pLaser->SetFireTime( gpGlobals->curtime - 1.0 ); |
|
m_pLaser->FireAtPoint( tr ); |
|
m_pLaser->SetNextThink( TICK_NEVER_THINK ); |
|
} |
|
CFuncTank::Fire( bulletCount, barrelEnd, forward, this, bIgnoreSpread ); |
|
} |
|
} |
|
|
|
class CFuncTankRocket : public CFuncTank |
|
{ |
|
public: |
|
DECLARE_CLASS( CFuncTankRocket, CFuncTank ); |
|
|
|
void Precache( void ); |
|
void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ); |
|
virtual float GetShotSpeed() { return m_flRocketSpeed; } |
|
|
|
protected: |
|
float m_flRocketSpeed; |
|
|
|
DECLARE_DATADESC(); |
|
}; |
|
|
|
BEGIN_DATADESC( CFuncTankRocket ) |
|
|
|
DEFINE_KEYFIELD( m_flRocketSpeed, FIELD_FLOAT, "rocketspeed" ), |
|
|
|
END_DATADESC() |
|
|
|
LINK_ENTITY_TO_CLASS( func_tankrocket, CFuncTankRocket ); |
|
|
|
void CFuncTankRocket::Precache( void ) |
|
{ |
|
UTIL_PrecacheOther( "rpg_missile" ); |
|
CFuncTank::Precache(); |
|
} |
|
|
|
void CFuncTankRocket::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ) |
|
{ |
|
CMissile *pRocket = (CMissile *) CBaseEntity::Create( "rpg_missile", barrelEnd, GetAbsAngles(), this ); |
|
|
|
pRocket->DumbFire(); |
|
pRocket->SetNextThink( gpGlobals->curtime + 0.1f ); |
|
pRocket->SetAbsVelocity( forward * m_flRocketSpeed ); |
|
if ( GetController() && GetController()->IsPlayer() ) |
|
{ |
|
pRocket->SetDamage( m_iBulletDamage ); |
|
} |
|
else |
|
{ |
|
pRocket->SetDamage( m_iBulletDamageVsPlayer ); |
|
} |
|
|
|
CFuncTank::Fire( bulletCount, barrelEnd, forward, this, bIgnoreSpread ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Airboat gun |
|
//----------------------------------------------------------------------------- |
|
class CFuncTankAirboatGun : public CFuncTank |
|
{ |
|
public: |
|
DECLARE_CLASS( CFuncTankAirboatGun, CFuncTank ); |
|
DECLARE_DATADESC(); |
|
|
|
void Precache( void ); |
|
virtual void Spawn(); |
|
virtual void Activate(); |
|
virtual void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ); |
|
virtual void ControllerPostFrame(); |
|
virtual void OnStopControlled(); |
|
virtual const char *GetTracerType( void ); |
|
virtual Vector WorldBarrelPosition( void ); |
|
virtual void DoImpactEffect( trace_t &tr, int nDamageType ); |
|
|
|
private: |
|
void CreateSounds(); |
|
void DestroySounds(); |
|
void DoMuzzleFlash( ); |
|
void StartFiring(); |
|
void StopFiring(); |
|
|
|
CSoundPatch *m_pGunFiringSound; |
|
float m_flNextHeavyShotTime; |
|
bool m_bIsFiring; |
|
|
|
string_t m_iszAirboatGunModel; |
|
CHandle<CBaseAnimating> m_hAirboatGunModel; |
|
int m_nGunBarrelAttachment; |
|
float m_flLastImpactEffectTime; |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Save/load: |
|
//----------------------------------------------------------------------------- |
|
BEGIN_DATADESC( CFuncTankAirboatGun ) |
|
|
|
DEFINE_SOUNDPATCH( m_pGunFiringSound ), |
|
DEFINE_FIELD( m_flNextHeavyShotTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_bIsFiring, FIELD_BOOLEAN ), |
|
DEFINE_KEYFIELD( m_iszAirboatGunModel, FIELD_STRING, "airboat_gun_model" ), |
|
// DEFINE_FIELD( m_hAirboatGunModel, FIELD_EHANDLE ), |
|
// DEFINE_FIELD( m_nGunBarrelAttachment, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_flLastImpactEffectTime, FIELD_TIME ), |
|
|
|
END_DATADESC() |
|
|
|
LINK_ENTITY_TO_CLASS( func_tankairboatgun, CFuncTankAirboatGun ); |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Precache: |
|
//----------------------------------------------------------------------------- |
|
void CFuncTankAirboatGun::Precache( void ) |
|
{ |
|
BaseClass::Precache(); |
|
PrecacheScriptSound( "Airboat.FireGunLoop" ); |
|
PrecacheScriptSound( "Airboat.FireGunRevDown"); |
|
CreateSounds(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Precache: |
|
//----------------------------------------------------------------------------- |
|
void CFuncTankAirboatGun::Spawn( void ) |
|
{ |
|
BaseClass::Spawn(); |
|
m_flNextHeavyShotTime = 0.0f; |
|
m_bIsFiring = false; |
|
m_flLastImpactEffectTime = -1; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Attachment indices |
|
//----------------------------------------------------------------------------- |
|
void CFuncTankAirboatGun::Activate() |
|
{ |
|
BaseClass::Activate(); |
|
|
|
if ( m_iszAirboatGunModel != NULL_STRING ) |
|
{ |
|
m_hAirboatGunModel = dynamic_cast<CBaseAnimating*>( gEntList.FindEntityByName( NULL, m_iszAirboatGunModel ) ); |
|
if ( m_hAirboatGunModel ) |
|
{ |
|
m_nGunBarrelAttachment = m_hAirboatGunModel->LookupAttachment( "muzzle" ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Create/destroy looping sounds |
|
//----------------------------------------------------------------------------- |
|
void CFuncTankAirboatGun::CreateSounds() |
|
{ |
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); |
|
|
|
CPASAttenuationFilter filter( this ); |
|
if (!m_pGunFiringSound) |
|
{ |
|
m_pGunFiringSound = controller.SoundCreate( filter, entindex(), "Airboat.FireGunLoop" ); |
|
controller.Play( m_pGunFiringSound, 0, 100 ); |
|
} |
|
} |
|
|
|
void CFuncTankAirboatGun::DestroySounds() |
|
{ |
|
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); |
|
|
|
controller.SoundDestroy( m_pGunFiringSound ); |
|
m_pGunFiringSound = NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Stop Firing |
|
//----------------------------------------------------------------------------- |
|
void CFuncTankAirboatGun::StartFiring() |
|
{ |
|
if ( !m_bIsFiring ) |
|
{ |
|
CSoundEnvelopeController *pController = &CSoundEnvelopeController::GetController(); |
|
float flVolume = pController->SoundGetVolume( m_pGunFiringSound ); |
|
pController->SoundChangeVolume( m_pGunFiringSound, 1.0f, 0.1f * (1.0f - flVolume) ); |
|
m_bIsFiring = true; |
|
} |
|
} |
|
|
|
void CFuncTankAirboatGun::StopFiring() |
|
{ |
|
if ( m_bIsFiring ) |
|
{ |
|
CSoundEnvelopeController *pController = &CSoundEnvelopeController::GetController(); |
|
float flVolume = pController->SoundGetVolume( m_pGunFiringSound ); |
|
pController->SoundChangeVolume( m_pGunFiringSound, 0.0f, 0.1f * flVolume ); |
|
EmitSound( "Airboat.FireGunRevDown" ); |
|
m_bIsFiring = false; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Maintains airboat gun sounds |
|
//----------------------------------------------------------------------------- |
|
void CFuncTankAirboatGun::ControllerPostFrame( void ) |
|
{ |
|
if ( IsPlayerManned() ) |
|
{ |
|
CBasePlayer *pPlayer = static_cast<CBasePlayer*>( GetController() ); |
|
if ( pPlayer->m_nButtons & IN_ATTACK ) |
|
{ |
|
StartFiring(); |
|
} |
|
else |
|
{ |
|
StopFiring(); |
|
} |
|
} |
|
|
|
BaseClass::ControllerPostFrame(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Stop controlled |
|
//----------------------------------------------------------------------------- |
|
void CFuncTankAirboatGun::OnStopControlled() |
|
{ |
|
StopFiring(); |
|
BaseClass::OnStopControlled(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Barrel position |
|
//----------------------------------------------------------------------------- |
|
Vector CFuncTankAirboatGun::WorldBarrelPosition( void ) |
|
{ |
|
if ( !m_hAirboatGunModel || (m_nGunBarrelAttachment == 0) ) |
|
{ |
|
return BaseClass::WorldBarrelPosition(); |
|
} |
|
|
|
Vector vecOrigin; |
|
m_hAirboatGunModel->GetAttachment( m_nGunBarrelAttachment, vecOrigin ); |
|
return vecOrigin; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
const char *CFuncTankAirboatGun::GetTracerType( void ) |
|
{ |
|
if ( gpGlobals->curtime >= m_flNextHeavyShotTime ) |
|
return "AirboatGunHeavyTracer"; |
|
|
|
return "AirboatGunTracer"; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFuncTankAirboatGun::DoMuzzleFlash( void ) |
|
{ |
|
if ( m_hAirboatGunModel && (m_nGunBarrelAttachment != 0) ) |
|
{ |
|
CEffectData data; |
|
data.m_nEntIndex = m_hAirboatGunModel->entindex(); |
|
data.m_nAttachmentIndex = m_nGunBarrelAttachment; |
|
data.m_flScale = 1.0f; |
|
DispatchEffect( "AirboatMuzzleFlash", data ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Allows the shooter to change the impact effect of his bullets |
|
//----------------------------------------------------------------------------- |
|
void CFuncTankAirboatGun::DoImpactEffect( trace_t &tr, int nDamageType ) |
|
{ |
|
// The airboat spits out so much crap that we need to do cheaper versions |
|
// of the impact effects. Also, we need to do less of them. |
|
if ( m_flLastImpactEffectTime == gpGlobals->curtime ) |
|
return; |
|
|
|
m_flLastImpactEffectTime = gpGlobals->curtime; |
|
UTIL_ImpactTrace( &tr, nDamageType, "AirboatGunImpact" ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Fires bullets |
|
//----------------------------------------------------------------------------- |
|
#define AIRBOAT_GUN_HEAVY_SHOT_INTERVAL 0.2f |
|
|
|
void CFuncTankAirboatGun::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ) |
|
{ |
|
CAmmoDef *pAmmoDef = GetAmmoDef(); |
|
int ammoType = pAmmoDef->Index( "AirboatGun" ); |
|
|
|
FireBulletsInfo_t info; |
|
info.m_vecSrc = barrelEnd; |
|
info.m_vecDirShooting = forward; |
|
info.m_flDistance = 4096; |
|
info.m_iAmmoType = ammoType; |
|
|
|
if ( gpGlobals->curtime >= m_flNextHeavyShotTime ) |
|
{ |
|
info.m_iShots = 1; |
|
info.m_vecSpread = VECTOR_CONE_PRECALCULATED; |
|
info.m_flDamageForceScale = 1000.0f; |
|
} |
|
else |
|
{ |
|
info.m_iShots = 2; |
|
info.m_vecSpread = VECTOR_CONE_5DEGREES; |
|
} |
|
|
|
FireBullets( info ); |
|
|
|
DoMuzzleFlash(); |
|
|
|
// NOTE: This must occur after FireBullets |
|
if ( gpGlobals->curtime >= m_flNextHeavyShotTime ) |
|
{ |
|
m_flNextHeavyShotTime = gpGlobals->curtime + AIRBOAT_GUN_HEAVY_SHOT_INTERVAL; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// APC Rocket |
|
//----------------------------------------------------------------------------- |
|
#define DEATH_VOLLEY_MISSILE_COUNT 10 |
|
#define DEATH_VOLLEY_MIN_FIRE_RATE 3 |
|
#define DEATH_VOLLEY_MAX_FIRE_RATE 6 |
|
|
|
class CFuncTankAPCRocket : public CFuncTank |
|
{ |
|
public: |
|
DECLARE_CLASS( CFuncTankAPCRocket, CFuncTank ); |
|
|
|
void Precache( void ); |
|
virtual void Spawn(); |
|
virtual void UpdateOnRemove(); |
|
void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ); |
|
virtual void Think(); |
|
virtual float GetShotSpeed() { return m_flRocketSpeed; } |
|
|
|
protected: |
|
void InputDeathVolley( inputdata_t &inputdata ); |
|
void FireDying( const Vector &barrelEnd ); |
|
|
|
EHANDLE m_hLaserDot; |
|
float m_flRocketSpeed; |
|
int m_nSide; |
|
int m_nBurstCount; |
|
bool m_bDying; |
|
|
|
DECLARE_DATADESC(); |
|
}; |
|
|
|
|
|
BEGIN_DATADESC( CFuncTankAPCRocket ) |
|
|
|
DEFINE_KEYFIELD( m_flRocketSpeed, FIELD_FLOAT, "rocketspeed" ), |
|
DEFINE_FIELD( m_hLaserDot, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_nSide, FIELD_INTEGER ), |
|
DEFINE_KEYFIELD( m_nBurstCount, FIELD_INTEGER, "burstcount" ), |
|
DEFINE_FIELD( m_bDying, FIELD_BOOLEAN ), |
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "DeathVolley", InputDeathVolley ), |
|
|
|
END_DATADESC() |
|
|
|
LINK_ENTITY_TO_CLASS( func_tankapcrocket, CFuncTankAPCRocket ); |
|
|
|
void CFuncTankAPCRocket::Precache( void ) |
|
{ |
|
UTIL_PrecacheOther( "apc_missile" ); |
|
|
|
PrecacheScriptSound( "PropAPC.FireCannon" ); |
|
|
|
CFuncTank::Precache(); |
|
} |
|
|
|
void CFuncTankAPCRocket::Spawn( void ) |
|
{ |
|
BaseClass::Spawn(); |
|
AddEffects( EF_NODRAW ); |
|
m_nSide = 0; |
|
m_bDying = false; |
|
m_hLaserDot = CreateLaserDot( GetAbsOrigin(), this, false ); |
|
m_nBulletCount = m_nBurstCount; |
|
SetSolid( SOLID_NONE ); |
|
SetLocalVelocity( vec3_origin ); |
|
} |
|
|
|
void CFuncTankAPCRocket::UpdateOnRemove( void ) |
|
{ |
|
if ( m_hLaserDot ) |
|
{ |
|
UTIL_Remove( m_hLaserDot ); |
|
m_hLaserDot = NULL; |
|
} |
|
BaseClass::UpdateOnRemove(); |
|
} |
|
|
|
void CFuncTankAPCRocket::FireDying( const Vector &barrelEnd ) |
|
{ |
|
Vector vecDir; |
|
vecDir.Random( -1.0f, 1.0f ); |
|
if ( vecDir.z < 0.0f ) |
|
{ |
|
vecDir.z *= -1.0f; |
|
} |
|
|
|
VectorNormalize( vecDir ); |
|
|
|
Vector vecVelocity; |
|
VectorMultiply( vecDir, m_flRocketSpeed * random->RandomFloat( 0.75f, 1.25f ), vecVelocity ); |
|
|
|
QAngle angles; |
|
VectorAngles( vecDir, angles ); |
|
|
|
CAPCMissile *pRocket = (CAPCMissile *) CAPCMissile::Create( barrelEnd, angles, vecVelocity, this ); |
|
float flDeathTime = random->RandomFloat( 0.3f, 0.5f ); |
|
if ( random->RandomFloat( 0.0f, 1.0f ) < 0.3f ) |
|
{ |
|
pRocket->ExplodeDelay( flDeathTime ); |
|
} |
|
else |
|
{ |
|
pRocket->AugerDelay( flDeathTime ); |
|
} |
|
|
|
// Make erratic firing |
|
m_fireRate = random->RandomFloat( DEATH_VOLLEY_MIN_FIRE_RATE, DEATH_VOLLEY_MAX_FIRE_RATE ); |
|
if ( --m_nBulletCount <= 0 ) |
|
{ |
|
UTIL_Remove( this ); |
|
} |
|
} |
|
|
|
void CFuncTankAPCRocket::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ) |
|
{ |
|
static float s_pSide[] = { 0.966, 0.866, 0.5, -0.5, -0.866, -0.966 }; |
|
|
|
Vector vecDir; |
|
CrossProduct( Vector( 0, 0, 1 ), forward, vecDir ); |
|
vecDir.z = 1.0f; |
|
vecDir.x *= s_pSide[m_nSide]; |
|
vecDir.y *= s_pSide[m_nSide]; |
|
if ( ++m_nSide >= 6 ) |
|
{ |
|
m_nSide = 0; |
|
} |
|
|
|
VectorNormalize( vecDir ); |
|
|
|
Vector vecVelocity; |
|
VectorMultiply( vecDir, m_flRocketSpeed, vecVelocity ); |
|
|
|
QAngle angles; |
|
VectorAngles( vecDir, angles ); |
|
|
|
CAPCMissile *pRocket = (CAPCMissile *) CAPCMissile::Create( barrelEnd, angles, vecVelocity, this ); |
|
pRocket->IgniteDelay(); |
|
|
|
CFuncTank::Fire( bulletCount, barrelEnd, forward, this, bIgnoreSpread ); |
|
|
|
if ( --m_nBulletCount <= 0 ) |
|
{ |
|
m_nBulletCount = m_nBurstCount; |
|
|
|
// This will cause it to wait for a little while before shooting |
|
m_fireLast += random->RandomFloat( 2.0f, 3.0f ); |
|
} |
|
EmitSound( "PropAPC.FireCannon" ); |
|
} |
|
|
|
void CFuncTankAPCRocket::Think() |
|
{ |
|
// Inert if we're carried... |
|
if ( GetMoveParent() && GetMoveParent()->GetMoveParent() ) |
|
{ |
|
SetNextThink( gpGlobals->curtime + 0.5f ); |
|
return; |
|
} |
|
|
|
BaseClass::Think(); |
|
m_hLaserDot->SetAbsOrigin( m_sightOrigin ); |
|
SetLaserDotTarget( m_hLaserDot, m_hFuncTankTarget ); |
|
EnableLaserDot( m_hLaserDot, m_hFuncTankTarget != NULL ); |
|
|
|
if ( m_bDying ) |
|
{ |
|
FireDying( WorldBarrelPosition() ); |
|
return; |
|
} |
|
} |
|
|
|
|
|
void CFuncTankAPCRocket::InputDeathVolley( inputdata_t &inputdata ) |
|
{ |
|
if ( !m_bDying ) |
|
{ |
|
m_fireRate = random->RandomFloat( DEATH_VOLLEY_MIN_FIRE_RATE, DEATH_VOLLEY_MAX_FIRE_RATE ); |
|
SetNextAttack( gpGlobals->curtime + (1.0f / m_fireRate ) ); |
|
m_nBulletCount = DEATH_VOLLEY_MISSILE_COUNT; |
|
m_bDying = true; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Mortar shell |
|
//----------------------------------------------------------------------------- |
|
class CMortarShell : public CBaseEntity |
|
{ |
|
public: |
|
DECLARE_CLASS( CMortarShell, CBaseEntity ); |
|
|
|
static CMortarShell *Create( const Vector &vecStart, const Vector &vecTarget, const Vector &vecShotDir, float flImpactDelay, float flWarnDelay, string_t warnSound ); |
|
|
|
void Spawn( void ); |
|
void Precache( void ); |
|
void Impact( void ); |
|
void Warn( void ); |
|
void FlyThink( void ); |
|
void FadeThink( void ); |
|
int UpdateTransmitState( void ); |
|
|
|
private: |
|
|
|
void FixUpImpactPoint( const Vector &initialPos, const Vector &initialNormal, Vector *endPos, Vector *endNormal ); |
|
|
|
float m_flFadeTime; |
|
float m_flImpactTime; |
|
float m_flWarnTime; |
|
float m_flNPCWarnTime; |
|
string_t m_warnSound; |
|
int m_iSpriteTexture; |
|
bool m_bHasWarned; |
|
Vector m_vecFiredFrom; |
|
Vector m_vecFlyDir; |
|
float m_flSpawnedTime; |
|
|
|
CHandle<CBeam> m_pBeamEffect[4]; |
|
|
|
CNetworkVar( float, m_flLifespan ); |
|
CNetworkVar( float, m_flRadius ); |
|
CNetworkVar( Vector, m_vecSurfaceNormal ); |
|
|
|
DECLARE_DATADESC(); |
|
DECLARE_SERVERCLASS(); |
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS( mortarshell, CMortarShell ); |
|
|
|
BEGIN_DATADESC( CMortarShell ) |
|
DEFINE_FIELD( m_flImpactTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_flFadeTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_flWarnTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_flNPCWarnTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_warnSound, FIELD_STRING ), |
|
DEFINE_FIELD( m_iSpriteTexture, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_bHasWarned, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_flLifespan, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_vecFiredFrom, FIELD_POSITION_VECTOR ), |
|
DEFINE_FIELD( m_vecFlyDir, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_flSpawnedTime, FIELD_TIME ), |
|
DEFINE_AUTO_ARRAY( m_pBeamEffect, FIELD_EHANDLE), |
|
DEFINE_FIELD( m_flRadius, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_vecSurfaceNormal, FIELD_VECTOR ), |
|
|
|
DEFINE_FUNCTION( FlyThink ), |
|
DEFINE_FUNCTION( FadeThink ), |
|
END_DATADESC() |
|
|
|
IMPLEMENT_SERVERCLASS_ST( CMortarShell, DT_MortarShell ) |
|
SendPropFloat( SENDINFO( m_flLifespan ), -1, SPROP_NOSCALE ), |
|
SendPropFloat( SENDINFO( m_flRadius ), -1, SPROP_NOSCALE ), |
|
SendPropVector( SENDINFO( m_vecSurfaceNormal ), 0, SPROP_NORMAL ), |
|
END_SEND_TABLE() |
|
|
|
#define MORTAR_TEST_RADIUS 16.0f |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &initialPos - |
|
// *endPos - |
|
// *endNormal - |
|
//----------------------------------------------------------------------------- |
|
void CMortarShell::FixUpImpactPoint( const Vector &initialPos, const Vector &initialNormal, Vector *endPos, Vector *endNormal ) |
|
{ |
|
Vector vecStartOffset; |
|
|
|
vecStartOffset = initialPos + ( initialNormal * 1.0f ); |
|
|
|
trace_t tr; |
|
UTIL_TraceLine( vecStartOffset, vecStartOffset - Vector( 0, 0, 256 ), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); |
|
|
|
if ( tr.fraction < 1.0f ) |
|
{ |
|
if ( endPos ) |
|
{ |
|
*endPos = tr.endpos + ( initialNormal * 16.0f ); |
|
} |
|
|
|
if ( endNormal ) |
|
{ |
|
*endNormal = tr.plane.normal; |
|
} |
|
} |
|
else |
|
{ |
|
if ( endPos ) |
|
{ |
|
*endPos = initialPos; |
|
} |
|
|
|
if ( endNormal ) |
|
{ |
|
*endNormal = initialNormal; |
|
} |
|
} |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
#define MORTAR_BLAST_DAMAGE 50 |
|
#define MORTAR_BLAST_HEIGHT 7500 |
|
|
|
CMortarShell *CMortarShell::Create( const Vector &vecStart, const Vector &vecTarget, const Vector &vecShotDir, float flImpactDelay, float flWarnDelay, string_t warnSound ) |
|
{ |
|
CMortarShell *pShell = (CMortarShell *)CreateEntityByName("mortarshell" ); |
|
|
|
// Place the mortar shell at the target location so that it can make the sound and explode. |
|
trace_t tr; |
|
UTIL_TraceLine( vecTarget, vecTarget + ( vecShotDir * 128.0f ), MASK_SOLID_BRUSHONLY, pShell, COLLISION_GROUP_NONE, &tr ); |
|
|
|
Vector targetPos, targetNormal; |
|
pShell->FixUpImpactPoint( tr.endpos, tr.plane.normal, &targetPos, &targetNormal ); |
|
|
|
UTIL_SetOrigin( pShell, targetPos ); |
|
|
|
Vector vecStartSkew, vecEndSkew; |
|
|
|
vecStartSkew = targetPos - vecStart; |
|
vecStartSkew[2] = 0.0f; |
|
float skewLength = VectorNormalize( vecStartSkew ); |
|
|
|
vecEndSkew = -vecStartSkew * ( skewLength * 0.25f ); |
|
vecStartSkew *= skewLength * 0.1f; |
|
|
|
// Muzzleflash beam |
|
pShell->m_pBeamEffect[0] = CBeam::BeamCreate( "sprites/laserbeam.vmt", 1 ); |
|
pShell->m_pBeamEffect[0]->PointsInit( vecStart, vecStart + Vector( vecStartSkew[0], vecStartSkew[1], MORTAR_BLAST_HEIGHT ) ); |
|
pShell->m_pBeamEffect[0]->SetColor( 16, 16, 8 ); |
|
pShell->m_pBeamEffect[0]->SetBrightness( 0 ); |
|
pShell->m_pBeamEffect[0]->SetNoise( 0 ); |
|
pShell->m_pBeamEffect[0]->SetBeamFlag( FBEAM_SHADEOUT ); |
|
pShell->m_pBeamEffect[0]->SetWidth( 64.0f ); |
|
pShell->m_pBeamEffect[0]->SetEndWidth( 64.0f ); |
|
|
|
pShell->m_pBeamEffect[1] = CBeam::BeamCreate( "sprites/laserbeam.vmt", 1 ); |
|
pShell->m_pBeamEffect[1]->PointsInit( vecStart, vecStart + Vector( vecStartSkew[0], vecStartSkew[1], MORTAR_BLAST_HEIGHT ) ); |
|
pShell->m_pBeamEffect[1]->SetColor( 255, 255, 255 ); |
|
pShell->m_pBeamEffect[1]->SetBrightness( 0 ); |
|
pShell->m_pBeamEffect[1]->SetNoise( 0 ); |
|
pShell->m_pBeamEffect[1]->SetBeamFlag( FBEAM_SHADEOUT ); |
|
pShell->m_pBeamEffect[1]->SetWidth( 8.0f ); |
|
pShell->m_pBeamEffect[1]->SetEndWidth( 8.0f ); |
|
|
|
trace_t skyTrace; |
|
UTIL_TraceLine( targetPos, targetPos + Vector( vecEndSkew[0], vecEndSkew[1], MORTAR_BLAST_HEIGHT ), MASK_SOLID_BRUSHONLY, pShell, COLLISION_GROUP_NONE, &skyTrace ); |
|
|
|
// We must touch the sky to make this beam |
|
if ( skyTrace.fraction <= 1.0f && skyTrace.surface.flags & SURF_SKY ) |
|
{ |
|
// Impact point beam |
|
pShell->m_pBeamEffect[2] = CBeam::BeamCreate( "sprites/laserbeam.vmt", 1 ); |
|
pShell->m_pBeamEffect[2]->PointsInit( targetPos, targetPos + Vector( vecEndSkew[0], vecEndSkew[1], MORTAR_BLAST_HEIGHT ) ); |
|
pShell->m_pBeamEffect[2]->SetColor( 16, 16, 8 ); |
|
pShell->m_pBeamEffect[2]->SetBrightness( 0 ); |
|
pShell->m_pBeamEffect[2]->SetNoise( 0 ); |
|
pShell->m_pBeamEffect[2]->SetBeamFlag( FBEAM_SHADEOUT ); |
|
pShell->m_pBeamEffect[2]->SetWidth( 32.0f ); |
|
pShell->m_pBeamEffect[2]->SetEndWidth( 32.0f ); |
|
|
|
pShell->m_pBeamEffect[3] = CBeam::BeamCreate( "sprites/laserbeam.vmt", 1 ); |
|
pShell->m_pBeamEffect[3]->PointsInit( targetPos, targetPos + Vector( vecEndSkew[0], vecEndSkew[1], MORTAR_BLAST_HEIGHT ) ); |
|
pShell->m_pBeamEffect[3]->SetColor( 255, 255, 255 ); |
|
pShell->m_pBeamEffect[3]->SetBrightness( 0 ); |
|
pShell->m_pBeamEffect[3]->SetNoise( 0 ); |
|
pShell->m_pBeamEffect[3]->SetBeamFlag( FBEAM_SHADEOUT ); |
|
pShell->m_pBeamEffect[3]->SetWidth( 4.0f ); |
|
pShell->m_pBeamEffect[3]->SetEndWidth( 4.0f ); |
|
} |
|
else |
|
{ |
|
// Mark these as not being used |
|
pShell->m_pBeamEffect[2] = NULL; |
|
pShell->m_pBeamEffect[3] = NULL; |
|
} |
|
|
|
pShell->m_vecFiredFrom = vecStart; |
|
pShell->m_flLifespan = flImpactDelay; |
|
pShell->m_flImpactTime = gpGlobals->curtime + flImpactDelay; |
|
pShell->m_flWarnTime = pShell->m_flImpactTime - flWarnDelay; |
|
pShell->m_flNPCWarnTime = pShell->m_flWarnTime - 0.5; |
|
pShell->m_warnSound = warnSound; |
|
pShell->Spawn(); |
|
|
|
// Save off the impact normal |
|
pShell->m_vecSurfaceNormal = targetNormal; |
|
pShell->m_flRadius = MORTAR_BLAST_RADIUS; |
|
|
|
return pShell; |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CMortarShell::Precache() |
|
{ |
|
m_iSpriteTexture = PrecacheModel( "sprites/physbeam.vmt" ); |
|
|
|
PrecacheScriptSound( "Weapon_Mortar.Impact" ); |
|
PrecacheMaterial( "effects/ar2ground2" ); |
|
|
|
if ( NULL_STRING != m_warnSound ) |
|
{ |
|
PrecacheScriptSound( STRING( m_warnSound ) ); |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : Send even though we don't have a model |
|
//------------------------------------------------------------------------------ |
|
int CMortarShell::UpdateTransmitState( void ) |
|
{ |
|
return SetTransmitState( FL_EDICT_PVSCHECK ); |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CMortarShell::Spawn() |
|
{ |
|
Precache(); |
|
|
|
AddEffects( EF_NODRAW ); |
|
AddSolidFlags( FSOLID_NOT_SOLID ); |
|
|
|
Vector mins( -MORTAR_BLAST_RADIUS, -MORTAR_BLAST_RADIUS, -MORTAR_BLAST_RADIUS ); |
|
Vector maxs( MORTAR_BLAST_RADIUS, MORTAR_BLAST_RADIUS, MORTAR_BLAST_RADIUS ); |
|
|
|
UTIL_SetSize( this, mins, maxs ); |
|
|
|
m_vecFlyDir = GetAbsOrigin() - m_vecFiredFrom; |
|
VectorNormalize( m_vecFlyDir ); |
|
|
|
m_flSpawnedTime = gpGlobals->curtime; |
|
|
|
SetThink( &CMortarShell::FlyThink ); |
|
SetNextThink( gpGlobals->curtime ); |
|
|
|
// No model but we still need to force this! |
|
AddEFlags( EFL_FORCE_CHECK_TRANSMIT ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : type - |
|
// steps - |
|
// bias - |
|
//----------------------------------------------------------------------------- |
|
ConVar curve_bias( "curve_bias", "0.5" ); |
|
|
|
enum |
|
{ |
|
CURVE_BIAS, |
|
CURVE_GAIN, |
|
CURVE_SMOOTH, |
|
CURVE_SMOOTH_TWEAK, |
|
}; |
|
|
|
void UTIL_VisualizeCurve( int type, int steps, float bias ) |
|
{ |
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 ); |
|
Vector vForward, vRight, vUp; |
|
|
|
pPlayer->EyeVectors( &vForward, &vRight, &vUp ); |
|
|
|
Vector renderOrigin = pPlayer->EyePosition() + ( vForward * 512.0f ); |
|
|
|
float renderScale = 8.0f; |
|
float lastPerc, perc; |
|
|
|
Vector renderOffs, lastRenderOffs = vec3_origin; |
|
|
|
for ( int i = 0; i < steps; i++ ) |
|
{ |
|
perc = RemapValClamped( i, 0, steps-1, 0.0f, 1.0f ); |
|
|
|
switch( type ) |
|
{ |
|
case CURVE_BIAS: |
|
perc = Bias( perc, bias ); |
|
break; |
|
|
|
case CURVE_GAIN: |
|
perc = Gain( perc, bias ); |
|
break; |
|
|
|
case CURVE_SMOOTH: |
|
perc = SmoothCurve( perc ); |
|
break; |
|
|
|
case CURVE_SMOOTH_TWEAK: |
|
perc = SmoothCurve_Tweak( perc, bias, 0.9f ); |
|
break; |
|
} |
|
|
|
renderOffs = ( vRight * (-steps*0.5f) * renderScale ) + ( vUp * (renderScale*-(steps*0.5f)) )+ ( vRight * i * renderScale ) + ( vUp * perc * (renderScale*steps) ); |
|
|
|
NDebugOverlay::Cross3D( renderOrigin + renderOffs, -Vector(2,2,2), Vector(2,2,2), 255, 0, 0, true, 0.05f ); |
|
|
|
if ( i > 0 ) |
|
{ |
|
NDebugOverlay::Line( renderOrigin + renderOffs, renderOrigin + lastRenderOffs, 255, 0, 0, true, 0.05f ); |
|
} |
|
|
|
lastRenderOffs = renderOffs; |
|
lastPerc = perc; |
|
} |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CMortarShell::FlyThink() |
|
{ |
|
SetNextThink( gpGlobals->curtime + 0.05 ); |
|
|
|
if ( gpGlobals->curtime > m_flNPCWarnTime ) |
|
{ |
|
// Warn the AI. Make this radius a little larger than the explosion will be, and make the sound last a little longer. |
|
CSoundEnt::InsertSound ( SOUND_DANGER | SOUND_CONTEXT_MORTAR, GetAbsOrigin(), MORTAR_BLAST_RADIUS * 1.25, (m_flImpactTime - m_flNPCWarnTime) + 0.15 ); |
|
m_flNPCWarnTime = FLT_MAX; |
|
} |
|
|
|
//UTIL_VisualizeCurve( CURVE_GAIN, 64, curve_bias.GetFloat() ); |
|
|
|
float lifePerc = 1.0f - ( ( m_flImpactTime - gpGlobals->curtime ) / ( m_flImpactTime - m_flSpawnedTime ) ); |
|
|
|
lifePerc = clamp( lifePerc, 0.0f, 1.0f ); |
|
|
|
float curve1 = Bias( lifePerc, 0.75f ); |
|
|
|
// Beam updates START |
|
|
|
m_pBeamEffect[0]->SetBrightness( 255 * curve1 ); |
|
m_pBeamEffect[0]->SetWidth( 64.0f * curve1 ); |
|
m_pBeamEffect[0]->SetEndWidth( 64.0f * curve1 ); |
|
|
|
m_pBeamEffect[1]->SetBrightness( 255 * curve1 ); |
|
m_pBeamEffect[1]->SetWidth( 8.0f * curve1 ); |
|
m_pBeamEffect[1]->SetEndWidth( 8.0f * curve1 ); |
|
|
|
float curve2 = Bias( lifePerc, 0.1f ); |
|
|
|
if ( m_pBeamEffect[2] ) |
|
{ |
|
m_pBeamEffect[2]->SetBrightness( 255 * curve2 ); |
|
m_pBeamEffect[2]->SetWidth( 32.0f * curve2 ); |
|
m_pBeamEffect[2]->SetEndWidth( 32.0f * curve2 ); |
|
} |
|
|
|
if ( m_pBeamEffect[3] ) |
|
{ |
|
m_pBeamEffect[3]->SetBrightness( 255 * curve2 ); |
|
m_pBeamEffect[3]->SetWidth( 8.0f * curve2 ); |
|
m_pBeamEffect[3]->SetEndWidth( 8.0f * curve2 ); |
|
} |
|
|
|
// Beam updates END |
|
|
|
if( !m_bHasWarned && gpGlobals->curtime > m_flWarnTime ) |
|
{ |
|
Warn(); |
|
} |
|
|
|
if( gpGlobals->curtime > m_flImpactTime ) |
|
{ |
|
Impact(); |
|
} |
|
|
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CMortarShell::Warn( void ) |
|
{ |
|
if ( m_warnSound != NULL_STRING ) |
|
{ |
|
CPASAttenuationFilter filter( this ); |
|
|
|
EmitSound_t ep; |
|
ep.m_nChannel = CHAN_WEAPON; |
|
ep.m_pSoundName = (char*)STRING(m_warnSound); |
|
ep.m_flVolume = 1.0f; |
|
ep.m_SoundLevel = SNDLVL_NONE; |
|
|
|
EmitSound( filter, entindex(), ep ); |
|
} |
|
|
|
m_bHasWarned = true; |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CMortarShell::Impact( void ) |
|
{ |
|
// Fire the bullets |
|
Vector vecSrc, vecShootDir; |
|
|
|
float flRadius = MORTAR_BLAST_RADIUS; |
|
|
|
trace_t tr; |
|
UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, 128 ), MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr ); |
|
|
|
UTIL_DecalTrace( &tr, "Scorch" ); |
|
|
|
// Send the effect over |
|
CEffectData data; |
|
|
|
// Do an extra effect if we struck the world |
|
if ( tr.m_pEnt && tr.m_pEnt->IsWorld() ) |
|
{ |
|
data.m_flRadius = flRadius * 0.5f; |
|
data.m_vNormal = tr.plane.normal; |
|
data.m_vOrigin = tr.endpos; |
|
|
|
DispatchEffect( "AR2Explosion", data ); |
|
} |
|
|
|
//Shockring |
|
CBroadcastRecipientFilter filter2; |
|
te->BeamRingPoint( filter2, 0, GetAbsOrigin(), //origin |
|
8.0f, //start radius |
|
flRadius * 2, //end radius |
|
m_iSpriteTexture, //texture |
|
0, //halo index |
|
0, //start frame |
|
2, //framerate |
|
0.2f, //life |
|
32, //width |
|
0, //spread |
|
0, //amplitude |
|
255, //r |
|
255, //g |
|
225, //b |
|
32, //a |
|
0, //speed |
|
FBEAM_FADEOUT |
|
); |
|
|
|
//Shockring |
|
te->BeamRingPoint( filter2, 0, GetAbsOrigin(), //origin |
|
8.0f, //start radius |
|
flRadius, //end radius |
|
m_iSpriteTexture, //texture |
|
0, //halo index |
|
0, //start frame |
|
2, //framerate |
|
0.2f, //life |
|
64, //width |
|
0, //spread |
|
0, //amplitude |
|
255, //r |
|
255, //g |
|
225, //b |
|
64, //a |
|
0, //speed |
|
FBEAM_FADEOUT |
|
); |
|
|
|
RadiusDamage( CTakeDamageInfo( this, GetOwnerEntity(), MORTAR_BLAST_DAMAGE, (DMG_BLAST|DMG_DISSOLVE) ), GetAbsOrigin(), MORTAR_BLAST_RADIUS, CLASS_NONE, NULL ); |
|
|
|
EmitSound( "Weapon_Mortar.Impact" ); |
|
|
|
UTIL_ScreenShake( GetAbsOrigin(), 10, 60, 1.0, 550, SHAKE_START, false ); |
|
|
|
//Fade the beams over time! |
|
m_flFadeTime = gpGlobals->curtime; |
|
|
|
SetThink( &CMortarShell::FadeThink ); |
|
SetNextThink( gpGlobals->curtime + 0.05f ); |
|
} |
|
|
|
#define MORTAR_FADE_LENGTH 1.0f |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CMortarShell::FadeThink( void ) |
|
{ |
|
SetNextThink( gpGlobals->curtime + 0.05f ); |
|
|
|
float lifePerc = 1.0f - ( ( gpGlobals->curtime - m_flFadeTime ) / MORTAR_FADE_LENGTH ); |
|
|
|
lifePerc = clamp( lifePerc, 0.0f, 1.0f ); |
|
|
|
float curve1 = Bias( lifePerc, 0.1f ); |
|
|
|
// Beam updates START |
|
|
|
m_pBeamEffect[0]->SetBrightness( 255 * curve1 ); |
|
m_pBeamEffect[0]->SetWidth( 64.0f * curve1 ); |
|
m_pBeamEffect[0]->SetEndWidth( 64.0f * curve1 ); |
|
|
|
m_pBeamEffect[1]->SetBrightness( 255 * curve1 ); |
|
m_pBeamEffect[1]->SetWidth( 8.0f * curve1 ); |
|
m_pBeamEffect[1]->SetEndWidth( 8.0f * curve1 ); |
|
|
|
float curve2 = Bias( lifePerc, 0.25f ); |
|
|
|
if ( m_pBeamEffect[2] ) |
|
{ |
|
m_pBeamEffect[2]->SetBrightness( 255 * curve2 ); |
|
m_pBeamEffect[2]->SetWidth( 32.0f * curve2 ); |
|
m_pBeamEffect[2]->SetEndWidth( 32.0f * curve2 ); |
|
} |
|
|
|
if ( m_pBeamEffect[3] ) |
|
{ |
|
m_pBeamEffect[3]->SetBrightness( 255 * curve2 ); |
|
m_pBeamEffect[3]->SetWidth( 8.0f * curve2 ); |
|
m_pBeamEffect[3]->SetEndWidth( 8.0f * curve2 ); |
|
} |
|
|
|
// Beam updates END |
|
|
|
if ( gpGlobals->curtime > ( m_flFadeTime + MORTAR_FADE_LENGTH ) ) |
|
{ |
|
UTIL_Remove( m_pBeamEffect[0] ); |
|
UTIL_Remove( m_pBeamEffect[1] ); |
|
UTIL_Remove( m_pBeamEffect[2] ); |
|
UTIL_Remove( m_pBeamEffect[3] ); |
|
|
|
SetThink(NULL); |
|
UTIL_Remove( this ); |
|
} |
|
} |
|
|
|
//========================================================= |
|
//========================================================= |
|
class CFuncTankMortar : public CFuncTank |
|
{ |
|
public: |
|
DECLARE_CLASS( CFuncTankMortar, CFuncTank ); |
|
|
|
CFuncTankMortar() { m_fLastShotMissed = false; } |
|
|
|
void Precache( void ); |
|
void FiringSequence( const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker ); |
|
void Fire( int bulletCount, const Vector &barrelEnd, const Vector &vecForward, CBaseEntity *pAttacker, bool bIgnoreSpread ); |
|
void ShootGun(void); |
|
void Spawn(); |
|
void SetNextAttack( float flWait ); |
|
|
|
// Input handlers. |
|
void InputShootGun( inputdata_t &inputdata ); |
|
void InputFireAtWill( inputdata_t &inputdata ); |
|
|
|
DECLARE_DATADESC(); |
|
|
|
int m_Magnitude; |
|
float m_fireDelay; |
|
string_t m_fireStartSound; |
|
//string_t m_fireEndSound; |
|
|
|
string_t m_incomingSound; |
|
float m_flWarningTime; |
|
float m_flFireVariance; |
|
|
|
bool m_fLastShotMissed; |
|
|
|
// store future firing event |
|
CBaseEntity *m_pAttacker; |
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS( func_tankmortar, CFuncTankMortar ); |
|
|
|
BEGIN_DATADESC( CFuncTankMortar ) |
|
|
|
DEFINE_KEYFIELD( m_Magnitude, FIELD_INTEGER, "iMagnitude" ), |
|
DEFINE_KEYFIELD( m_fireDelay, FIELD_FLOAT, "firedelay" ), |
|
DEFINE_KEYFIELD( m_fireStartSound, FIELD_STRING, "firestartsound" ), |
|
//DEFINE_KEYFIELD( m_fireEndSound, FIELD_STRING, "fireendsound" ), |
|
DEFINE_KEYFIELD( m_incomingSound, FIELD_STRING, "incomingsound" ), |
|
DEFINE_KEYFIELD( m_flWarningTime, FIELD_TIME, "warningtime" ), |
|
DEFINE_KEYFIELD( m_flFireVariance, FIELD_TIME, "firevariance" ), |
|
|
|
DEFINE_FIELD( m_fLastShotMissed, FIELD_BOOLEAN ), |
|
|
|
DEFINE_FIELD( m_pAttacker, FIELD_CLASSPTR ), |
|
|
|
// Inputs |
|
DEFINE_INPUTFUNC( FIELD_VOID, "ShootGun", InputShootGun ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "FireAtWill", InputFireAtWill ), |
|
END_DATADESC() |
|
|
|
|
|
void CFuncTankMortar::Spawn() |
|
{ |
|
BaseClass::Spawn(); |
|
|
|
m_takedamage = DAMAGE_NO; |
|
} |
|
|
|
void CFuncTankMortar::Precache( void ) |
|
{ |
|
if ( m_fireStartSound != NULL_STRING ) |
|
PrecacheScriptSound( STRING(m_fireStartSound) ); |
|
//if ( m_fireEndSound != NULL_STRING ) |
|
// PrecacheScriptSound( STRING(m_fireEndSound) ); |
|
if ( m_incomingSound != NULL_STRING ) |
|
PrecacheScriptSound( STRING(m_incomingSound) ); |
|
BaseClass::Precache(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CFuncTankMortar::SetNextAttack( float flWait ) |
|
{ |
|
if ( m_flFireVariance > 0.09 ) |
|
flWait += random->RandomFloat( -m_flFireVariance, m_flFireVariance ); |
|
BaseClass::SetNextAttack( flWait ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Input handler to make the tank shoot. |
|
//----------------------------------------------------------------------------- |
|
void CFuncTankMortar::InputShootGun( inputdata_t &inputdata ) |
|
{ |
|
ShootGun(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// This mortar can fire the next round as soon as it is ready. This is not a |
|
// 'sticky' state, it just allows us to get the next shot off as soon as the |
|
// tank is on target. great for scripted applications where you need a shot as |
|
// soon as you can get it. |
|
//----------------------------------------------------------------------------- |
|
void CFuncTankMortar::InputFireAtWill( inputdata_t &inputdata ) |
|
{ |
|
SetNextAttack( gpGlobals->curtime ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFuncTankMortar::ShootGun( void ) |
|
{ |
|
Vector forward; |
|
AngleVectors( GetLocalAngles(), &forward ); |
|
UpdateMatrix(); |
|
forward = m_parentMatrix.ApplyRotation( forward ); |
|
|
|
Fire( 1, WorldBarrelPosition(), forward, m_pAttacker, false ); |
|
} |
|
|
|
|
|
void CFuncTankMortar::FiringSequence( const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker ) |
|
{ |
|
if ( gpGlobals->curtime > GetNextAttack() ) |
|
{ |
|
ShootGun(); |
|
m_fireLast = gpGlobals->curtime; |
|
SetNextAttack( gpGlobals->curtime + (1.0 / m_fireRate ) ); |
|
} |
|
else |
|
{ |
|
m_fireLast = gpGlobals->curtime; |
|
} |
|
} |
|
|
|
void CFuncTankMortar::Fire( int bulletCount, const Vector &barrelEnd, const Vector &vecForward, CBaseEntity *pAttacker, bool bIgnoreSpread ) |
|
{ |
|
Vector vecProjectedPosition = vec3_invalid; |
|
trace_t tr; |
|
|
|
if ( m_hTarget ) |
|
{ |
|
float leadTime = (m_fireDelay * 1.1); |
|
|
|
if ( m_hTarget->IsNPC() ) // Give NPCs a little extra grace |
|
leadTime = 1.25; |
|
|
|
Vector vLead = m_hTarget->GetSmoothedVelocity() * leadTime; |
|
Vector vNoise; |
|
|
|
vecProjectedPosition = m_hTarget->WorldSpaceCenter() + vLead; |
|
vNoise.AsVector2D().Random( -6*12, 6*12); |
|
vNoise.z = 0; |
|
|
|
if( m_hTarget->Classify() != CLASS_BULLSEYE ) |
|
{ |
|
// Don't apply noise when attacking a bullseye. |
|
vecProjectedPosition += vNoise; |
|
} |
|
} |
|
else if ( IsPlayerManned() ) |
|
{ |
|
CalcPlayerCrosshairTarget( &vecProjectedPosition ); |
|
} |
|
else if ( IsNPCManned() ) |
|
{ |
|
CalcNPCEnemyTarget( &vecProjectedPosition ); |
|
//vecProjectedPosition += GetEnemy()->GetSmoothedVelocity() * (m_fireDelay * 1.1); |
|
} |
|
else |
|
return; |
|
|
|
#define TARGET_SEARCH_DEPTH 100 |
|
|
|
// find something interesting to shoot at near the projected position. |
|
Vector delta; |
|
|
|
// Make a really rough approximation of the last half of the mortar trajectory and trace it. |
|
// Do this so that mortars fired into windows land on rooftops, and that targets projected |
|
// inside buildings (or out of the world) clip to the world. (usually a building facade) |
|
|
|
// Find halfway between the mortar and the target. |
|
Vector vecSpot = ( vecProjectedPosition + GetAbsOrigin() ) * 0.5; |
|
vecSpot.z = GetAbsOrigin().z; |
|
|
|
// Trace up to find the fake 'apex' of the shell. The skybox or 1024 units, whichever comes first. |
|
UTIL_TraceLine( vecSpot, vecSpot + Vector(0, 0, 1024), MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr ); |
|
vecSpot = tr.endpos; |
|
|
|
//NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, false, 5 ); |
|
|
|
// Now trace from apex to target |
|
UTIL_TraceLine( vecSpot, vecProjectedPosition, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr ); |
|
|
|
if( mortar_visualize.GetBool() ) |
|
{ |
|
NDebugOverlay::Line( tr.startpos, tr.endpos, 255,0,0, false, 5 ); |
|
} |
|
|
|
if ( m_fireStartSound != NULL_STRING ) |
|
{ |
|
CPASAttenuationFilter filter( this ); |
|
|
|
EmitSound_t ep; |
|
ep.m_nChannel = CHAN_WEAPON; |
|
ep.m_pSoundName = (char*)STRING(m_fireStartSound); |
|
ep.m_flVolume = 1.0f; |
|
ep.m_SoundLevel = SNDLVL_NONE; |
|
|
|
EmitSound( filter, entindex(), ep ); |
|
} |
|
|
|
Vector vecFinalDir = tr.endpos - tr.startpos; |
|
VectorNormalize( vecFinalDir ); |
|
|
|
CMortarShell::Create( barrelEnd, tr.endpos, vecFinalDir, m_fireDelay, m_flWarningTime, m_incomingSound ); |
|
BaseClass::Fire( bulletCount, barrelEnd, vecForward, this, bIgnoreSpread ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Func tank that fires physics cannisters placed on it |
|
//----------------------------------------------------------------------------- |
|
class CFuncTankPhysCannister : public CFuncTank |
|
{ |
|
public: |
|
DECLARE_CLASS( CFuncTankPhysCannister, CFuncTank ); |
|
DECLARE_DATADESC(); |
|
|
|
void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ); |
|
|
|
protected: |
|
string_t m_iszBarrelVolume; |
|
CHandle<CBaseTrigger> m_hBarrelVolume; |
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS( func_tankphyscannister, CFuncTankPhysCannister ); |
|
|
|
BEGIN_DATADESC( CFuncTankPhysCannister ) |
|
|
|
DEFINE_KEYFIELD( m_iszBarrelVolume, FIELD_STRING, "barrel_volume" ), |
|
DEFINE_FIELD( m_hBarrelVolume, FIELD_EHANDLE ), |
|
|
|
END_DATADESC() |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFuncTankPhysCannister::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ) |
|
{ |
|
// Find our barrel volume |
|
if ( !m_hBarrelVolume ) |
|
{ |
|
if ( m_iszBarrelVolume != NULL_STRING ) |
|
{ |
|
m_hBarrelVolume = dynamic_cast<CBaseTrigger*>( gEntList.FindEntityByName( NULL, m_iszBarrelVolume ) ); |
|
} |
|
|
|
if ( !m_hBarrelVolume ) |
|
{ |
|
Msg("ERROR: Couldn't find barrel volume for func_tankphyscannister %s.\n", STRING(GetEntityName()) ); |
|
return; |
|
} |
|
} |
|
|
|
// Do we have a cannister in our barrel volume? |
|
CPhysicsCannister *pCannister = (CPhysicsCannister *)m_hBarrelVolume->GetTouchedEntityOfType( "physics_cannister" ); |
|
if ( !pCannister ) |
|
{ |
|
// Play a no-ammo sound |
|
return; |
|
} |
|
|
|
// Fire the cannister! |
|
pCannister->CannisterFire( pAttacker ); |
|
} |
|
|
|
//========================================================= |
|
//========================================================= |
|
static const char *s_pUpdateBeamThinkContext = "UpdateBeamThinkContext"; |
|
#define COMBINE_CANNON_BEAM "effects/blueblacklargebeam.vmt" |
|
//#define COMBINE_CANNON_BEAM "sprites/strider_bluebeam.vmt" |
|
|
|
class CFuncTankCombineCannon : public CFuncTankGun |
|
{ |
|
DECLARE_CLASS( CFuncTankCombineCannon, CFuncTankGun ); |
|
|
|
void Precache(); |
|
void Spawn(); |
|
void CreateBeam(); |
|
void DestroyBeam(); |
|
void FuncTankPostThink(); |
|
void AdjustRateOfFire(); |
|
void UpdateBeamThink( void ); |
|
void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ); |
|
void MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType ); |
|
void TankDeactivate(); |
|
|
|
void InputSetTargetEntity( inputdata_t &inputdata ); |
|
void InputClearTargetEntity( inputdata_t &inputdata ); |
|
|
|
void InputEnableHarrass( inputdata_t &inputdata ); |
|
void InputDisableHarrass( inputdata_t &inputdata ); |
|
|
|
COutputEvent m_OnShotAtPlayer; |
|
|
|
CHandle<CBeam> m_hBeam; |
|
|
|
DECLARE_DATADESC(); |
|
|
|
private: |
|
float m_originalFireRate; |
|
float m_flTimeNextSweep; |
|
float m_flTimeBeamOn; |
|
Vector m_vecTrueForward; |
|
bool m_bShouldHarrass; |
|
bool m_bLastTargetWasNPC; // Tells whether the last entity we fired a shot at was an NPC (otherwise it was the player) |
|
}; |
|
|
|
BEGIN_DATADESC( CFuncTankCombineCannon ) |
|
DEFINE_FIELD( m_originalFireRate, FIELD_FLOAT ), |
|
DEFINE_THINKFUNC( UpdateBeamThink ), |
|
DEFINE_FIELD( m_flTimeNextSweep, FIELD_TIME ), |
|
DEFINE_FIELD( m_flTimeBeamOn, FIELD_TIME ), |
|
DEFINE_FIELD( m_hBeam, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_vecTrueForward, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_bShouldHarrass, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_bLastTargetWasNPC, FIELD_BOOLEAN ), |
|
|
|
DEFINE_INPUTFUNC( FIELD_VOID, "EnableHarrass", InputEnableHarrass ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "DisableHarrass", InputDisableHarrass ), |
|
|
|
DEFINE_OUTPUT( m_OnShotAtPlayer, "OnShotAtPlayer" ), |
|
|
|
END_DATADESC() |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CFuncTankCombineCannon::Precache() |
|
{ |
|
m_originalFireRate = m_fireRate; |
|
|
|
PrecacheModel(COMBINE_CANNON_BEAM); |
|
PrecacheParticleSystem( "Weapon_Combine_Ion_Cannon" ); |
|
|
|
BaseClass::Precache(); |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CFuncTankCombineCannon::Spawn() |
|
{ |
|
BaseClass::Spawn(); |
|
m_flTimeBeamOn = gpGlobals->curtime; |
|
CreateBeam(); |
|
|
|
m_bShouldHarrass = true; |
|
|
|
GetVectors( &m_vecTrueForward, NULL, NULL ); |
|
m_bLastTargetWasNPC = false; |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CFuncTankCombineCannon::CreateBeam() |
|
{ |
|
if (!m_hBeam && gpGlobals->curtime >= m_flTimeBeamOn ) |
|
{ |
|
m_hBeam = CBeam::BeamCreate( COMBINE_CANNON_BEAM, 1.0f ); |
|
m_hBeam->SetColor( 255, 255, 255 ); |
|
SetContextThink( &CFuncTankCombineCannon::UpdateBeamThink, gpGlobals->curtime, s_pUpdateBeamThinkContext ); |
|
} |
|
else |
|
{ |
|
// Beam seems to be on, or I'm not supposed to have it on at the moment. |
|
return; |
|
} |
|
|
|
Vector vecInitialAim; |
|
|
|
AngleVectors( GetAbsAngles(), &vecInitialAim, NULL, NULL ); |
|
|
|
m_hBeam->PointsInit( WorldBarrelPosition(), WorldBarrelPosition() + vecInitialAim ); |
|
m_hBeam->SetBrightness( 255 ); |
|
m_hBeam->SetNoise( 0 ); |
|
m_hBeam->SetWidth( 3.0f ); |
|
m_hBeam->SetEndWidth( 0 ); |
|
m_hBeam->SetScrollRate( 0 ); |
|
m_hBeam->SetFadeLength( 60 ); // five feet to fade out |
|
//m_hBeam->SetHaloTexture( sHaloSprite ); |
|
m_hBeam->SetHaloScale( 4.0f ); |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CFuncTankCombineCannon::DestroyBeam() |
|
{ |
|
if( m_hBeam ) |
|
{ |
|
UTIL_Remove( m_hBeam ); |
|
m_hBeam.Set(NULL); |
|
} |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CFuncTankCombineCannon::AdjustRateOfFire() |
|
{ |
|
// Maintain 1.5 rounds per second rate of fire. |
|
m_fireRate = 1.5; |
|
/* |
|
if( m_hTarget.Get() != NULL && m_hTarget->IsPlayer() ) |
|
{ |
|
if( m_bLastTargetWasNPC ) |
|
{ |
|
// Cheat, and be able to fire RIGHT NOW if the target is a player and the |
|
// last target I fired at was an NPC. This prevents the player from running |
|
// for it while the gun is busy dealing with NPCs |
|
SetNextAttack( gpGlobals->curtime ); |
|
} |
|
} |
|
*/ |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
#define COMBINE_CANNON_BEAM_MAX_DIST 1900.0f |
|
void CFuncTankCombineCannon::UpdateBeamThink() |
|
{ |
|
SetContextThink( &CFuncTankCombineCannon::UpdateBeamThink, gpGlobals->curtime + 0.025, s_pUpdateBeamThinkContext ); |
|
|
|
// Always try to create the beam. |
|
CreateBeam(); |
|
|
|
if( !m_hBeam ) |
|
return; |
|
|
|
trace_t trBeam; |
|
trace_t trShot; |
|
trace_t trBlockLOS; |
|
|
|
Vector vecBarrel = WorldBarrelPosition(); |
|
Vector vecAim; |
|
AngleVectors( GetAbsAngles(), &vecAim, NULL, NULL ); |
|
|
|
AI_TraceLine( vecBarrel, vecBarrel + vecAim * COMBINE_CANNON_BEAM_MAX_DIST, MASK_SHOT, this, COLLISION_GROUP_NONE, &trBeam ); |
|
|
|
m_hBeam->SetStartPos( trBeam.startpos ); |
|
m_hBeam->SetEndPos( trBeam.endpos ); |
|
|
|
if( !(m_spawnflags & SF_TANK_AIM_AT_POS) ) |
|
{ |
|
SetTargetPosition( trBeam.endpos ); |
|
} |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CFuncTankCombineCannon::FuncTankPostThink() |
|
{ |
|
AdjustRateOfFire(); |
|
|
|
if( m_hTarget.Get() == NULL ) |
|
{ |
|
if( gpGlobals->curtime > m_flTimeNextSweep ) |
|
{ |
|
AddSpawnFlags( SF_TANK_AIM_AT_POS ); |
|
|
|
Vector vecTargetPosition = GetTargetPosition(); |
|
CBasePlayer *pPlayer = AI_GetSinglePlayer(); |
|
Vector vecToPlayer = pPlayer->WorldSpaceCenter() - GetAbsOrigin(); |
|
vecToPlayer.NormalizeInPlace(); |
|
|
|
bool bHarass = false; |
|
float flDot = DotProduct( m_vecTrueForward, vecToPlayer ); |
|
|
|
if( flDot >= 0.9f && m_bShouldHarrass ) |
|
{ |
|
//Msg("%s Harrassing player\n", GetDebugName() ); |
|
vecTargetPosition = pPlayer->EyePosition(); |
|
bHarass = true; |
|
} |
|
else |
|
{ |
|
//Msg( "%s Bored\n", GetDebugName() ); |
|
// Just point off in the distance, more or less directly ahead of me. |
|
vecTargetPosition = GetAbsOrigin() + m_vecTrueForward * 1900.0f; |
|
} |
|
|
|
int i; |
|
Vector vecTest; |
|
bool bFoundPoint = false; |
|
for( i = 0 ; i < 5 ; i++ ) |
|
{ |
|
vecTest = vecTargetPosition; |
|
|
|
if( bHarass ) |
|
{ |
|
vecTest.x += random->RandomFloat( -48, 48 ); |
|
vecTest.y += random->RandomFloat( -48, 48 ); |
|
vecTest.z += random->RandomFloat( 16, 48 ); |
|
} |
|
else |
|
{ |
|
vecTest.x += random->RandomFloat( -48, 48 ); |
|
vecTest.y += random->RandomFloat( -48, 48 ); |
|
vecTest.z += random->RandomFloat( -48, 48 ); |
|
} |
|
|
|
// Get the barrel position |
|
Vector vecBarrelEnd = WorldBarrelPosition(); |
|
trace_t trLOS; |
|
trace_t trShoot; |
|
|
|
// Ignore the func_tank and any prop it's parented to, and check line of sight to the point |
|
// Trace to the point. If an opaque trace doesn't reach the point, that means the beam hit |
|
// something closer, (including a blockLOS), so try again. |
|
CTraceFilterSkipTwoEntities traceFilter( this, GetParent(), COLLISION_GROUP_NONE ); |
|
AI_TraceLine( vecBarrelEnd, vecTest, MASK_BLOCKLOS_AND_NPCS, &traceFilter, &trLOS ); |
|
AI_TraceLine( vecBarrelEnd, vecTest, MASK_SHOT, &traceFilter, &trShoot ); |
|
|
|
if( trLOS.fraction < trShoot.fraction ) |
|
{ |
|
// Damn block LOS brushes. |
|
continue; |
|
} |
|
|
|
//Msg("Point is visible in %d tries\n", i); |
|
bFoundPoint = true; |
|
break; |
|
} |
|
|
|
if( bFoundPoint ) |
|
{ |
|
vecTargetPosition = vecTest; |
|
SetTargetPosition( vecTargetPosition ); |
|
//Msg("New place\n"); |
|
} |
|
|
|
if( bHarass ) |
|
{ |
|
m_flTimeNextSweep = gpGlobals->curtime + random->RandomFloat( 0.25f, 0.75f ); |
|
} |
|
else |
|
{ |
|
m_flTimeNextSweep = gpGlobals->curtime + random->RandomFloat( 1, 3 ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
//Msg("%d engaging: %s\n", entindex(), m_hTarget->GetClassname() ); |
|
RemoveSpawnFlags( SF_TANK_AIM_AT_POS ); |
|
} |
|
} |
|
|
|
//--------------------------------------------------------- |
|
// A normal func_tank uses a method of aiming the gun that will |
|
// always follow a fast-moving player. This is because the func_tank |
|
// turns the weapon by applying angular velocities in the early |
|
// phase of the func_tank's Think(). Because the bullet is fired |
|
// later in the same think, it is fired before the game physics have |
|
// updated the func_tank's angles using the newly-computed angular |
|
// velocity, so the bullet always trails the target slightly. |
|
// This is unacceptable for the Combine Cannon, as the cannon MUST |
|
// strike a moving player with absolute certainty. As a quick |
|
// remedy, this code allows the combine cannon to fire a bullet |
|
// at a slightly different angle than the gun is aiming, to |
|
// ensure a hit. Large discrepancies are ignored and we accept |
|
// the miss instead of presenting a bullet fired at an obviously |
|
// adjusted angle. |
|
//--------------------------------------------------------- |
|
void CFuncTankCombineCannon::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker, bool bIgnoreSpread ) |
|
{ |
|
// Specifically do NOT fire in aim at pos mode. This is just for show. |
|
if( HasSpawnFlags(SF_TANK_AIM_AT_POS) ) |
|
return; |
|
|
|
Vector vecAdjustedForward = forward; |
|
|
|
if( m_hTarget != NULL ) |
|
{ |
|
Vector vecToTarget = m_hTarget->BodyTarget( barrelEnd, false ) - barrelEnd; |
|
VectorNormalize( vecToTarget ); |
|
|
|
float flDot = DotProduct( vecToTarget, forward ); |
|
|
|
if( flDot >= 0.97 ) |
|
{ |
|
vecAdjustedForward = vecToTarget; |
|
} |
|
|
|
if( m_hTarget->IsNPC() ) |
|
m_bLastTargetWasNPC = true; |
|
else |
|
m_bLastTargetWasNPC = false; |
|
|
|
if( m_hTarget->IsPlayer() ) |
|
m_OnShotAtPlayer.FireOutput( this, this ); |
|
} |
|
|
|
BaseClass::Fire( bulletCount, barrelEnd, vecAdjustedForward, pAttacker, bIgnoreSpread ); |
|
|
|
// Turn off the beam and tell it to stay off for a bit. We want it to look like the beam became the |
|
// ion cannon 'rail gun' effect. |
|
DestroyBeam(); |
|
m_flTimeBeamOn = gpGlobals->curtime + 0.2f; |
|
|
|
m_flTimeNextSweep = gpGlobals->curtime + random->RandomInt( 1.0f, 2.0f ); |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CFuncTankCombineCannon::MakeTracer( const Vector &vecTracerSrc, const trace_t &tr, int iTracerType ) |
|
{ |
|
// If the shot passed near the player, shake the screen. |
|
if( AI_IsSinglePlayer() ) |
|
{ |
|
Vector vecPlayer = AI_GetSinglePlayer()->EyePosition(); |
|
|
|
Vector vecNearestPoint = PointOnLineNearestPoint( vecTracerSrc, tr.endpos, vecPlayer ); |
|
|
|
float flDist = vecPlayer.DistTo( vecNearestPoint ); |
|
|
|
if( flDist >= 10.0f && flDist <= 120.0f ) |
|
{ |
|
// Don't shake the screen if we're hit (within 10 inches), but do shake if a shot otherwise comes within 10 feet. |
|
UTIL_ScreenShake( vecNearestPoint, 10, 60, 0.3, 120.0f, SHAKE_START, false ); |
|
} |
|
} |
|
|
|
// Send the railgun effect |
|
DispatchParticleEffect( "Weapon_Combine_Ion_Cannon", vecTracerSrc, tr.endpos, vec3_angle, NULL ); |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CFuncTankCombineCannon::TankDeactivate() |
|
{ |
|
DestroyBeam(); |
|
m_flTimeBeamOn = gpGlobals->curtime + 1.0f; |
|
SetContextThink( NULL, 0, s_pUpdateBeamThinkContext ); |
|
|
|
BaseClass::TankDeactivate(); |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CFuncTankCombineCannon::InputSetTargetEntity( inputdata_t &inputdata ) |
|
{ |
|
BaseClass::InputSetTargetEntity( inputdata ); |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CFuncTankCombineCannon::InputClearTargetEntity( inputdata_t &inputdata ) |
|
{ |
|
/* |
|
m_targetEntityName = NULL_STRING; |
|
m_hTarget = NULL; |
|
|
|
// No longer aim at target position if have one |
|
m_spawnflags &= ~SF_TANK_AIM_AT_POS; |
|
*/ |
|
BaseClass::InputClearTargetEntity( inputdata ); |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CFuncTankCombineCannon::InputEnableHarrass( inputdata_t &inputdata ) |
|
{ |
|
m_bShouldHarrass = true; |
|
} |
|
|
|
//--------------------------------------------------------- |
|
//--------------------------------------------------------- |
|
void CFuncTankCombineCannon::InputDisableHarrass( inputdata_t &inputdata ) |
|
{ |
|
m_bShouldHarrass = false; |
|
} |
|
|
|
|
|
LINK_ENTITY_TO_CLASS( func_tank_combine_cannon, CFuncTankCombineCannon );
|
|
|