2013-12-02 19:31:46 -08:00
//========= 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"
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
# include "shot_manipulator.h"
# include "filters.h"
# endif
2013-12-02 19:31:46 -08:00
# 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 ) ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
extern ConVar ai_debug_shoot_positions ;
# endif
2013-12-02 19:31:46 -08:00
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 " ) ,
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
DEFINE_KEYFIELD ( m_iszShootSound , FIELD_SOUNDNAME , " shootsound " ) ,
# endif
# ifndef AMMOTYPE_MOVED
2013-12-02 19:31:46 -08:00
DEFINE_KEYFIELD ( m_bulletType , FIELD_INTEGER , " bullet " ) ,
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
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 " ) ,
2019-08-31 19:28:20 +00:00
# ifndef AMMOTYPE_MOVED
2013-12-02 19:31:46 -08:00
# 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
2019-08-31 19:28:20 +00:00
# endif // AMMOTYPE_MOVED
2013-12-02 19:31:46 -08:00
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 ) ,
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
DEFINE_KEYFIELD ( m_bShouldFindNPCs , FIELD_BOOLEAN , " ShouldFindNPCs " ) ,
# else
2013-12-02 19:31:46 -08:00
DEFINE_FIELD ( m_bShouldFindNPCs , FIELD_BOOLEAN ) ,
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
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 " ) ,
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
DEFINE_KEYFIELD ( m_bDontHitController , FIELD_BOOLEAN , " DontHitController " ) ,
DEFINE_KEYFIELD ( m_bControllerGlued , FIELD_BOOLEAN , " ControllerGlued " ) ,
DEFINE_KEYFIELD ( m_iszTraceFilter , FIELD_STRING , " TraceFilter " ) ,
DEFINE_FIELD ( m_hTraceFilter , FIELD_EHANDLE ) ,
DEFINE_KEYFIELD ( m_flPlayerBBoxDist , FIELD_FLOAT , " PlayerBBoxDist " ) ,
# endif
2013-12-02 19:31:46 -08:00
// 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 ) ,
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
DEFINE_INPUTFUNC ( FIELD_STRING , " TeleportNPCToManTank " , InputTeleportNPCToManTank ) ,
DEFINE_INPUTFUNC ( FIELD_STRING , " ForceNPCToManTank " , InputForceNPCToManTank ) ,
# endif
2013-12-02 19:31:46 -08:00
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 ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
m_flPlayerBBoxDist = 24 ;
# endif
2013-12-02 19:31:46 -08:00
}
//-----------------------------------------------------------------------------
// 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?
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
CBaseEntity * pEntity = gEntList . FindEntityByNameNearest ( inputdata . value . String ( ) , GetAbsOrigin ( ) , 0 , this , inputdata . pActivator , inputdata . pCaller ) ;
# else
2013-12-02 19:31:46 -08:00
CBaseEntity * pEntity = gEntList . FindEntityByName ( NULL , inputdata . value . StringID ( ) ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
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 ( ) ;
}
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
//-----------------------------------------------------------------------------
// Purpose: Input handler for telling the func_tank to teleport an NPC to man it.
//-----------------------------------------------------------------------------
void CFuncTank : : InputTeleportNPCToManTank ( 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 . FindEntityByNameNearest ( inputdata . value . String ( ) , GetAbsOrigin ( ) , 0 , this , inputdata . pActivator , inputdata . pCaller ) ;
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 ) )
{
Vector vecVec ;
QAngle angAng ;
Vector vecVel = vec3_origin ;
if ( m_iszNPCManPoint ! = NULL_STRING )
{
CBaseEntity * pEntity = gEntList . FindEntityByName ( NULL , m_iszNPCManPoint , this ) ;
if ( pEntity )
{
vecVec = pEntity - > GetAbsOrigin ( ) ;
}
}
angAng = pNPC - > GetAbsAngles ( ) ;
angAng . y = UTIL_VecToYaw ( GetAbsOrigin ( ) - vecVec ) ; // Yaw from man point to turret
pNPC - > Teleport ( & vecVec , & angAng , & vecVel ) ;
m_hController = pNPC ;
pBehavior - > SetFuncTank ( this ) ;
NPC_SetInRoute ( true ) ;
# if 1
pNPC - > GetMotor ( ) - > SetIdealYawToTarget ( GetAbsOrigin ( ) ) ;
pNPC - > SetTurnActivity ( ) ;
pNPC - > DoHolster ( ) ;
pNPC - > SpeakSentence ( FUNCTANK_SENTENCE_JUST_MOUNTED ) ;
// We are at the correct position and facing for the func_tank, mount it.
StartControl ( pNPC ) ;
pNPC - > ClearEnemyMemory ( ) ;
pBehavior - > SetMounted ( true ) ;
pNPC - > SetIdealActivity ( ACT_IDLE_MANNEDGUN ) ;
# endif
return ;
}
}
}
else
{
Warning ( " %s unable to find NPC \" %s \" to teleport to tank \n " , GetDebugName ( ) , inputdata . value . String ( ) ) ;
}
// NPC_FindController() doesn't return a NPC and teleporting a random NPC seems kind of dangerous anyway.
}
//-----------------------------------------------------------------------------
// Purpose: Input handler for telling the func_tank to find a NPC and instantly make them man it, no matter what they're doing or where they are.
//-----------------------------------------------------------------------------
void CFuncTank : : InputForceNPCToManTank ( 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 . FindEntityByNameNearest ( inputdata . value . String ( ) , GetAbsOrigin ( ) , 0 , this , inputdata . pActivator , inputdata . pCaller ) ;
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 ) )
{
// Set the forced condition
pBehavior - > SetCondition ( CAI_FuncTankBehavior : : COND_FUNCTANK_FORCED ) ;
m_hController = pNPC ;
pBehavior - > SetFuncTank ( this ) ;
NPC_SetInRoute ( true ) ;
return ;
}
}
}
else
{
Warning ( " %s unable to find NPC \" %s \" to force to tank \n " , GetDebugName ( ) , inputdata . value . String ( ) ) ;
}
// NPC_FindController() doesn't return a NPC and teleporting a random NPC seems kind of dangerous anyway.
}
# endif
2013-12-02 19:31:46 -08:00
//-----------------------------------------------------------------------------
// 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 ( ) ;
2019-08-31 19:28:20 +00:00
# ifndef AMMOTYPE_MOVED
2013-12-02 19:31:46 -08:00
# ifdef HL2_EPISODIC
2019-08-31 19:28:20 +00:00
m_iAmmoType = GetAmmoDef ( ) - > Index ( STRING ( m_iszAmmoType ) ) ;
2013-12-02 19:31:46 -08:00
# else
2019-08-31 19:28:20 +00:00
m_iSmallAmmoType = GetAmmoDef ( ) - > Index ( " Pistol " ) ;
m_iMediumAmmoType = GetAmmoDef ( ) - > Index ( " SMG1 " ) ;
m_iLargeAmmoType = GetAmmoDef ( ) - > Index ( " AR2 " ) ;
# endif // HL2_EPISODIC
# endif // AMMOTYPE_MOVED
2013-12-02 19:31:46 -08:00
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 ( ) ;
// 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 ) ) ;
}
}
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
if ( m_iszTraceFilter ! = NULL_STRING )
{
m_hTraceFilter = dynamic_cast < CBaseFilter * > ( gEntList . FindEntityByName ( NULL , STRING ( m_iszTraceFilter ) ) ) ;
if ( ! m_hTraceFilter )
Warning ( " WARNING: %s trace filter %s is not a filter! \n " , GetDebugName ( ) , STRING ( m_iszTraceFilter ) ) ;
}
# endif
2013-12-02 19:31:46 -08:00
}
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 ) ) ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
if ( m_iszShootSound ! = NULL_STRING )
PrecacheScriptSound ( STRING ( m_iszShootSound ) ) ;
# endif
2013-12-02 19:31:46 -08:00
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
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
if ( m_hController - > IsPlayer ( ) )
{
m_OnGotPlayerController . FireOutput ( m_hController , this ) ;
}
else
{
m_OnGotController . FireOutput ( m_hController , this ) ;
}
# else
2013-12-02 19:31:46 -08:00
if ( m_hController - > IsPlayer ( ) )
{
m_OnGotPlayerController . FireOutput ( this , this ) ;
}
else
{
m_OnGotController . FireOutput ( this , this ) ;
}
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
OnStartControlled ( ) ;
return true ;
}
//-----------------------------------------------------------------------------
// Purpose:
// TODO: bring back the controllers current weapon
//-----------------------------------------------------------------------------
void CFuncTank : : StopControl ( )
{
// Do we have a controller?
if ( ! m_hController )
return ;
OnStopControlled ( ) ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
// Arm player/npc weapon if they're not in a vehicle.
if ( ! m_hController - > IsInAVehicle ( ) & & m_hController - > GetActiveWeapon ( ) )
# else
2013-12-02 19:31:46 -08:00
// Arm player/npc weapon.
if ( m_hController - > GetActiveWeapon ( ) )
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
{
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.
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
if ( m_hController - > IsPlayer ( ) )
{
m_OnLostPlayerController . FireOutput ( m_hController , this ) ;
}
else
{
m_OnLostController . FireOutput ( m_hController , this ) ;
}
# else
2013-12-02 19:31:46 -08:00
if ( m_hController - > IsPlayer ( ) )
{
m_OnLostPlayerController . FireOutput ( this , this ) ;
}
else
{
m_OnLostController . FireOutput ( this , this ) ;
}
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
// 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 ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
CTraceFilterSimple traceFilter = GetTraceFilter ( ) ;
UTIL_TraceHull ( start , start + forward * 8192 , - Vector ( 8 , 8 , 8 ) , Vector ( 8 , 8 , 8 ) , MASK_SHOT , & traceFilter , & tr ) ;
# else
2013-12-02 19:31:46 -08:00
UTIL_TraceHull ( start , start + forward * 8192 , - Vector ( 8 , 8 , 8 ) , Vector ( 8 , 8 , 8 ) , MASK_SHOT , this , COLLISION_GROUP_NONE , & tr ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
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.
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
m_OnAmmoDepleted . FireOutput ( pPlayer , this ) ;
# endif
2013-12-02 19:31:46 -08:00
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 ) ;
}
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
// This is now needed in some cases
m_fireLast = gpGlobals - > curtime ;
# endif
2013-12-02 19:31:46 -08:00
}
//-----------------------------------------------------------------------------
// 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 ( ) ;
}
2019-09-28 22:56:52 +00:00
# ifdef MAPBASE
else if ( m_barrelPos . LengthSqr ( ) = = 0.0f )
{
// Do a simpler calculation that doesn't take barrel into account
float targetToCenterYaw = atan2 ( target . y , target . x ) ;
float targetToCenterPitch = atan2 ( target . z , sqrt ( quadTargetXY ) ) ;
return QAngle ( - RAD2DEG ( targetToCenterPitch ) , RAD2DEG ( targetToCenterYaw ) , 0 ) ;
}
# endif
2013-12-02 19:31:46 -08:00
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 ) ;
}
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
CTraceFilterSimple traceFilter = GetTraceFilter ( ) ;
UTIL_TraceLine ( vecStart + vecDir * m_flPlayerBBoxDist , vecStart + vecDir * 8192 , MASK_BLOCKLOS_AND_NPCS , & traceFilter , & tr ) ;
//DebugDrawLine(tr.startpos, tr.endpos, 222, 222, 0, false, 0.1);
# else
2013-12-02 19:31:46 -08:00
// 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 ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
* 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!
//-----------------------------------------------------------------------------
2019-08-31 19:28:20 +00:00
void CFuncTank : : LostTarget ( CBaseEntity * pTarget )
2013-12-02 19:31:46 -08:00
{
if ( m_fireLast ! = 0 )
{
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
m_OnLoseTarget . Set ( pTarget , pTarget , this ) ;
# else
2013-12-02 19:31:46 -08:00
m_OnLoseTarget . FireOutput ( this , this ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
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 ) ;
}
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
LostTarget ( pEntity ) ;
# else
2013-12-02 19:31:46 -08:00
LostTarget ( ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
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 )
{
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
LostTarget ( m_hTarget ) ;
m_hTarget = NULL ;
# else
2013-12-02 19:31:46 -08:00
m_hTarget = NULL ;
LostTarget ( ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
}
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 )
{
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
m_OnAquireTarget . Set ( pTarget , pTarget , this ) ;
# else
2013-12-02 19:31:46 -08:00
m_OnAquireTarget . FireOutput ( this , this ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
}
FiringSequence ( barrelEnd , forward , this ) ;
}
else
{
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
LostTarget ( pTarget ) ;
# else
2013-12-02 19:31:46 -08:00
LostTarget ( ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
}
}
else
{
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
LostTarget ( pTarget ) ;
# else
2013-12-02 19:31:46 -08:00
LostTarget ( ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
}
}
//-----------------------------------------------------------------------------
// 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 )
{
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
bool bSpriteSmoke = m_iszSpriteSmoke ! = NULL_STRING ;
bool bSpriteFlash = m_iszSpriteFlash ! = NULL_STRING ;
if ( bSpriteSmoke | | bSpriteFlash )
{
if ( bSpriteSmoke )
{
CSprite * pSmoke = CSprite : : SpriteCreate ( STRING ( m_iszSpriteSmoke ) , barrelEnd , TRUE ) ;
pSmoke - > AnimateAndDie ( random - > RandomFloat ( 15.0 , 20.0 ) ) ;
pSmoke - > SetTransparency ( kRenderTransAlpha , m_clrRender - > r , m_clrRender - > g , m_clrRender - > b , 255 , kRenderFxNone ) ;
Vector vecVelocity ( 0 , 0 , random - > RandomFloat ( 40 , 80 ) ) ;
pSmoke - > SetAbsVelocity ( vecVelocity ) ;
pSmoke - > SetScale ( m_spriteScale ) ;
}
if ( bSpriteFlash )
{
CSprite * pFlash = CSprite : : SpriteCreate ( STRING ( m_iszSpriteFlash ) , barrelEnd , TRUE ) ;
pFlash - > AnimateAndDie ( 5 ) ;
pFlash - > SetTransparency ( kRenderTransAdd , 255 , 255 , 255 , 255 , kRenderFxNoDissipation ) ;
pFlash - > SetScale ( m_spriteScale ) ;
}
}
else if ( m_iEffectHandling = = EH_AR2 )
{
DoMuzzleFlash ( ) ;
}
else if ( m_iEffectHandling = = EH_COMBINE_CANNON )
{
DoMuzzleFlash ( ) ;
}
if ( m_iszShootSound ! = NULL_STRING )
{
EmitSound ( STRING ( m_iszShootSound ) ) ;
}
else if ( m_iEffectHandling = = EH_AR2 )
{
// Play the AR2 sound
EmitSound ( " Weapon_functank.Single " ) ;
}
else if ( m_iEffectHandling = = EH_COMBINE_CANNON )
{
// Play the cannon sound
EmitSound ( " NPC_Combine_Cannon.FireBullet " ) ;
}
# else
2013-12-02 19:31:46 -08:00
// 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 ) ;
}
}
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
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 ) ;
}
}
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
m_OnFire . FireOutput ( pAttacker , this ) ;
# else
2013-12-02 19:31:46 -08:00
m_OnFire . FireOutput ( this , this ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
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 ;
}
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
//=========================================================
// I decided to make a custom trace filter for func_tank trace filters.
// If the base class thinks the trace should hit the entity, it goes through
// a filter and if it passes the filter, the trace passes the entity instead.
//
// This is different from CTraceFilterEntityFilter because it's simplified and can use its own exclusion list.
// (it also came before it)
//=========================================================
class CTankTraceFilter : public CTraceFilterSimpleList
{
public :
CTankTraceFilter ( int collisionGroup ) : CTraceFilterSimpleList ( collisionGroup ) { }
bool ShouldHitEntity ( IHandleEntity * pHandleEntity , int contentsMask )
{
bool base = CTraceFilterSimpleList : : ShouldHitEntity ( pHandleEntity , contentsMask ) ;
// Our base is telling us to hit. If it passes the filter, don't.
if ( base & & m_pFilter )
{
CBaseEntity * pEntity = EntityFromEntityHandle ( pHandleEntity ) ;
return ! m_pFilter - > PassesFilter ( m_pCaller , pEntity ) ;
// TODO: Should we use this code from CBulletsTraceFilter?
/*
CBaseEntity * pEntity = EntityFromEntityHandle ( pHandleEntity ) ;
CBaseEntity * pPassEntity = EntityFromEntityHandle ( m_PassEntities [ 0 ] ) ;
if ( pEntity & & pPassEntity & & pEntity - > GetOwnerEntity ( ) = = pPassEntity & &
pPassEntity - > IsSolidFlagSet ( FSOLID_NOT_SOLID ) & & pPassEntity - > IsSolidFlagSet ( FSOLID_CUSTOMBOXTEST ) & &
pPassEntity - > IsSolidFlagSet ( FSOLID_CUSTOMRAYTEST ) )
{
// It's a bone follower of the entity to ignore (toml 8/3/2007)
return false ;
}
*/
}
return base ;
}
CBaseFilter * m_pFilter ;
CBaseEntity * m_pCaller ;
} ;
//-----------------------------------------------------------------------------
// Purpose: Gets our general trace filter we use for LOS, trace locations, etc.
// This was created so we could easily change the trace filter func_tank generally uses,
// but it can be overridden by derived classes, so there's that.
//-----------------------------------------------------------------------------
CTraceFilterSimple CFuncTank : : GetTraceFilter ( )
{
//CTraceFilterSkipTwoEntities traceFilter( this, GetParent(), COLLISION_GROUP_NONE );
CTankTraceFilter traceFilter ( COLLISION_GROUP_NONE ) ;
traceFilter . SetPassEntity ( this ) ;
if ( GetParent ( ) )
{
CBaseEntity * pParent = GetParent ( ) ;
traceFilter . AddEntityToIgnore ( pParent ) ;
// Add the parent's parent too. (for func_tanks mounted on moving things, like vehicles)
if ( pParent - > GetParent ( ) )
traceFilter . AddEntityToIgnore ( pParent - > GetParent ( ) ) ;
}
if ( m_bDontHitController )
traceFilter . AddEntityToIgnore ( GetController ( ) ) ;
traceFilter . m_pFilter = m_hTraceFilter . Get ( ) ;
traceFilter . m_pCaller = this ;
return traceFilter ;
}
# endif
2013-12-02 19:31:46 -08:00
//-----------------------------------------------------------------------------
// 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
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
CTraceFilterSimple traceFilter = GetTraceFilter ( ) ;
# else
2013-12-02 19:31:46 -08:00
CTraceFilterSkipTwoEntities traceFilter ( this , GetParent ( ) , COLLISION_GROUP_NONE ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
// 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 ) ;
2019-08-31 19:28:20 +00:00
# ifdef AMMOTYPE_MOVED
DECLARE_DATADESC ( ) ;
string_t m_iszAmmoType ; // The name of the ammodef that we use when we fire. Bullet damage still comes from keyvalues.
int m_iAmmoType ; // The cached index of the ammodef that we use when we fire.
# endif // !AMMOTYPE_MOVED
# ifdef AMMOTYPE_MOVED
void Spawn ( void ) ;
bool KeyValue ( const char * szKeyName , const char * szValue ) ;
# endif // AMMOTYPE_MOVED
2013-12-02 19:31:46 -08:00
void Fire ( int bulletCount , const Vector & barrelEnd , const Vector & forward , CBaseEntity * pAttacker , bool bIgnoreSpread ) ;
} ;
2019-08-31 19:28:20 +00:00
# ifdef AMMOTYPE_MOVED
BEGIN_DATADESC ( CFuncTankGun )
DEFINE_KEYFIELD ( m_iszAmmoType , FIELD_STRING , " ammotype " ) ,
DEFINE_FIELD ( m_iAmmoType , FIELD_INTEGER ) ,
END_DATADESC ( )
# endif // AMMOTYPE_MOVED
2013-12-02 19:31:46 -08:00
LINK_ENTITY_TO_CLASS ( func_tank , CFuncTankGun ) ;
2019-08-31 19:28:20 +00:00
# ifdef AMMOTYPE_MOVED
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CFuncTankGun : : Spawn ( void )
{
if ( m_iszAmmoType ! = NULL_STRING )
m_iAmmoType = GetAmmoDef ( ) - > Index ( STRING ( m_iszAmmoType ) ) ;
BaseClass : : Spawn ( ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Caches entity key values until spawn is called.
// Input : szKeyName -
// szValue -
// Output :
//-----------------------------------------------------------------------------
bool CFuncTankGun : : KeyValue ( const char * szKeyName , const char * szValue )
{
if ( FStrEq ( szKeyName , " bullet " ) )
{
switch ( atoi ( szValue ) )
{
case TANK_BULLET_SMALL :
m_iAmmoType = GetAmmoDef ( ) - > Index ( " Pistol " ) ;
break ;
case TANK_BULLET_MEDIUM :
m_iAmmoType = GetAmmoDef ( ) - > Index ( " SMG1 " ) ;
break ;
case TANK_BULLET_LARGE :
m_iAmmoType = GetAmmoDef ( ) - > Index ( " AR2 " ) ;
break ;
default :
m_iAmmoType = - 1 ;
}
return true ;
}
return BaseClass : : KeyValue ( szKeyName , szValue ) ;
}
# endif // AMMOTYPE_MOVED
2013-12-02 19:31:46 -08:00
//-----------------------------------------------------------------------------
// 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 ( ) ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
CUtlVector < CBaseEntity * > ignorelist ;
if ( pAttacker )
{
if ( m_bDontHitController )
{
ignorelist . AddToTail ( pAttacker ) ;
}
// Ignore any vehicle our controller is in
if ( pAttacker - > MyCombatCharacterPointer ( ) & & pAttacker - > MyCombatCharacterPointer ( ) - > IsInAVehicle ( ) )
{
ignorelist . AddToTail ( pAttacker - > MyCombatCharacterPointer ( ) - > GetVehicleEntity ( ) ) ;
}
}
info . m_pIgnoreEntList = & ignorelist ;
# endif
# if defined(HL2_EPISODIC) || defined(AMMOTYPE_MOVED)
2013-12-02 19:31:46 -08:00
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 ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
# define m_sPulseFireSound m_iszShootSound
# else
2013-12-02 19:31:46 -08:00
string_t m_sPulseFireSound ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
} ;
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 ) ;
}
}
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
//-----------------------------------------------------------------------------
// Missile for func_tankrocket so kills are credited properly and don't kill friendlies
//-----------------------------------------------------------------------------
class CFuncTankMissile : public CMissile
{
DECLARE_CLASS ( CFuncTankMissile , CMissile ) ;
DECLARE_DATADESC ( ) ;
public :
virtual void Spawn ( void ) ;
EHANDLE m_hTurret ;
private :
void FTnkMissileTouch ( CBaseEntity * pOther ) ;
} ;
BEGIN_DATADESC ( CFuncTankMissile )
DEFINE_FIELD ( m_hTurret , FIELD_EHANDLE ) ,
// Function Pointers
DEFINE_FUNCTION ( FTnkMissileTouch ) ,
END_DATADESC ( )
LINK_ENTITY_TO_CLASS ( func_tankrocket_missile , CFuncTankMissile ) ;
void CFuncTankMissile : : Spawn ( void )
{
Precache ( ) ;
SetSolid ( SOLID_BBOX ) ;
SetModel ( " models/weapons/w_missile_launch.mdl " ) ;
UTIL_SetSize ( this , - Vector ( 4 , 4 , 4 ) , Vector ( 4 , 4 , 4 ) ) ;
SetTouch ( & CFuncTankMissile : : FTnkMissileTouch ) ;
SetMoveType ( MOVETYPE_FLYGRAVITY , MOVECOLLIDE_FLY_BOUNCE ) ;
SetThink ( & CMissile : : IgniteThink ) ;
SetNextThink ( gpGlobals - > curtime + 0.3f ) ;
m_takedamage = DAMAGE_YES ;
m_iHealth = m_iMaxHealth = 100 ;
m_bloodColor = DONT_BLEED ;
AddFlag ( FL_OBJECT ) ;
}
//-----------------------------------------------------------------------------
// The actual explosion
//-----------------------------------------------------------------------------
void CFuncTankMissile : : FTnkMissileTouch ( CBaseEntity * pOther )
{
Assert ( pOther ) ;
// Don't touch triggers (but DO hit weapons)
if ( pOther - > IsSolidFlagSet ( FSOLID_TRIGGER | FSOLID_VOLUME_CONTENTS ) & & pOther - > GetCollisionGroup ( ) ! = COLLISION_GROUP_WEAPON )
{
// Some NPCs are triggers that can take damage (like antlion grubs). We should hit them.
if ( ( pOther - > m_takedamage = = DAMAGE_NO ) | | ( pOther - > m_takedamage = = DAMAGE_EVENTS_ONLY ) )
return ;
}
// Do not touch the turret we fired from
if ( pOther = = m_hTurret . Get ( ) )
{
return ;
}
Explode ( ) ;
}
# endif
2013-12-02 19:31:46 -08:00
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 )
{
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
UTIL_PrecacheOther ( " func_tankrocket_missile " ) ;
# else
2013-12-02 19:31:46 -08:00
UTIL_PrecacheOther ( " rpg_missile " ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
CFuncTank : : Precache ( ) ;
}
void CFuncTankRocket : : Fire ( int bulletCount , const Vector & barrelEnd , const Vector & forward , CBaseEntity * pAttacker , bool bIgnoreSpread )
{
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
CFuncTankMissile * pRocket = ( CFuncTankMissile * ) CBaseEntity : : Create ( " func_tankrocket_missile " , barrelEnd , GetAbsAngles ( ) , GetController ( ) ) ;
pRocket - > AddEffects ( EF_NOSHADOW ) ;
pRocket - > m_hTurret . Set ( this ) ;
# else
2013-12-02 19:31:46 -08:00
CMissile * pRocket = ( CMissile * ) CBaseEntity : : Create ( " rpg_missile " , barrelEnd , GetAbsAngles ( ) , this ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
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 ) ;
}
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
static const char * s_pAirboatGunThinkContext = " AirboatGunThinkContext " ;
# endif
2013-12-02 19:31:46 -08:00
//-----------------------------------------------------------------------------
// Airboat gun
//-----------------------------------------------------------------------------
class CFuncTankAirboatGun : public CFuncTank
{
public :
DECLARE_CLASS ( CFuncTankAirboatGun , CFuncTank ) ;
DECLARE_DATADESC ( ) ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
CFuncTankAirboatGun ( )
{
// -1 = original behavior
m_spread = - 1 ;
}
# endif
2013-12-02 19:31:46 -08:00
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 ( ) ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
virtual void FuncTankAirboatGunThink ( ) ;
virtual void TankActivate ( void ) ;
virtual void TankDeactivate ( void ) ;
virtual void OnStartControlled ( ) ;
# endif
2013-12-02 19:31:46 -08:00
virtual void OnStopControlled ( ) ;
virtual const char * GetTracerType ( void ) ;
virtual Vector WorldBarrelPosition ( void ) ;
virtual void DoImpactEffect ( trace_t & tr , int nDamageType ) ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
virtual void StopLoopingSounds ( ) { DestroySounds ( ) ; BaseClass : : StopLoopingSounds ( ) ; }
# endif
2013-12-02 19:31:46 -08:00
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 ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
float m_flHeavyShotInterval = 0.2f ;
int m_iHeavyShotSpread ;
Mapbase v3.0
- Overhauled matcher system and added expanded wildcard support, meaning "text", "te?t", "blah_test", etc. are now supported instead of only trailing *
- Added support for regular expression matching
- Added the missing matrixinvert.h file, which prevented programmers from compiling vbsp
- Fixed env_global_light brightness
- Added info_player_view_proxy, an entity which mimics a player's view (created for script_intro)
- New UnZoomWithRate and SetZoomRate inputs on env_zoom
- New "Don't change target's angles" flag on logic_measure_movement
- Fixed logic_measure_movement not using ignore origin flags correctly
- Fixed filter_damage_mod secondary filter not recognizing mode correctly
- Fixed DisableGeigerCounter causing NPCs to ignore player, and possibly fixed other adverse effects in similar code
- Added SetEntityName input for setting an entity's targetname
- Added "SetTarget" support to point_posecontroller
- Added "sk_alyx_health" convar for adjustable npc_alyx health, restored/fixed "sk_barney_health" for adjustable npc_barney health
- Added ignore flags and several direction-related inputs to math_vector
- Added pose parameter stuff to logic_modelinfo
- Fixed ChangeWeapon not changing during combat
- Fixed holster/unholster on NPCs without holster/unholster animations
- Fixed func_tank and other "player in the way" code ignoring notarget
- Added SetPoseParameter input to animating entities
- Introduced an experimental chapter system which allows for custom chapter title/restriction management
- Added SetChapterTitle input to worldspawn for dynamic title changing
- Fixed func_tankairboatgun damage credit, added ability to use its damage keyvalues
- Fixed mapbase_rpc_enabled not actually affecting RPC
- Moved some newly found Combine soldier grenade code to ai_grenade for other grenade users
- Fixed some problems with the new shared activities
- Restored an old Mapbase entity as "prop_interactable", an entity which contains all of the functionality of a func_button/func_door in a prop entity
- Added env_microphone pitch scale for scaling the pitch of transmitted sounds
- Added experimental, long-overdue code for scenes to be able to access !target#'s and other scene-related names through AI_INPUT
- Added "mat_specular_disable_on_missing", which "disables" specular reflections when the cubemap texture is missing
- Fixed $envmapmasktransform and $bumptransform on VertexLitGeneric and related shaders
- Areaportal leaks now stop compilation in leaktest
- Replaced "-nodefaultcubemap" with "-defaultcubemap" as the shader changes allow maps to compile without default cubemaps by default with little effect
2020-02-05 19:08:49 +00:00
bool m_bUseDamageKV ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
} ;
//-----------------------------------------------------------------------------
// 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 ) ,
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
DEFINE_KEYFIELD ( m_flHeavyShotInterval , FIELD_FLOAT , " heavy_shot_interval " ) ,
DEFINE_KEYFIELD ( m_iHeavyShotSpread , FIELD_INTEGER , " heavy_shot_spread " ) ,
Mapbase v3.0
- Overhauled matcher system and added expanded wildcard support, meaning "text", "te?t", "blah_test", etc. are now supported instead of only trailing *
- Added support for regular expression matching
- Added the missing matrixinvert.h file, which prevented programmers from compiling vbsp
- Fixed env_global_light brightness
- Added info_player_view_proxy, an entity which mimics a player's view (created for script_intro)
- New UnZoomWithRate and SetZoomRate inputs on env_zoom
- New "Don't change target's angles" flag on logic_measure_movement
- Fixed logic_measure_movement not using ignore origin flags correctly
- Fixed filter_damage_mod secondary filter not recognizing mode correctly
- Fixed DisableGeigerCounter causing NPCs to ignore player, and possibly fixed other adverse effects in similar code
- Added SetEntityName input for setting an entity's targetname
- Added "SetTarget" support to point_posecontroller
- Added "sk_alyx_health" convar for adjustable npc_alyx health, restored/fixed "sk_barney_health" for adjustable npc_barney health
- Added ignore flags and several direction-related inputs to math_vector
- Added pose parameter stuff to logic_modelinfo
- Fixed ChangeWeapon not changing during combat
- Fixed holster/unholster on NPCs without holster/unholster animations
- Fixed func_tank and other "player in the way" code ignoring notarget
- Added SetPoseParameter input to animating entities
- Introduced an experimental chapter system which allows for custom chapter title/restriction management
- Added SetChapterTitle input to worldspawn for dynamic title changing
- Fixed func_tankairboatgun damage credit, added ability to use its damage keyvalues
- Fixed mapbase_rpc_enabled not actually affecting RPC
- Moved some newly found Combine soldier grenade code to ai_grenade for other grenade users
- Fixed some problems with the new shared activities
- Restored an old Mapbase entity as "prop_interactable", an entity which contains all of the functionality of a func_button/func_door in a prop entity
- Added env_microphone pitch scale for scaling the pitch of transmitted sounds
- Added experimental, long-overdue code for scenes to be able to access !target#'s and other scene-related names through AI_INPUT
- Added "mat_specular_disable_on_missing", which "disables" specular reflections when the cubemap texture is missing
- Fixed $envmapmasktransform and $bumptransform on VertexLitGeneric and related shaders
- Areaportal leaks now stop compilation in leaktest
- Replaced "-nodefaultcubemap" with "-defaultcubemap" as the shader changes allow maps to compile without default cubemaps by default with little effect
2020-02-05 19:08:49 +00:00
DEFINE_KEYFIELD ( m_bUseDamageKV , FIELD_BOOLEAN , " use_damage_kv " ) ,
2019-08-31 19:28:20 +00:00
# endif
# ifdef MAPBASE
DEFINE_THINKFUNC ( FuncTankAirboatGunThink ) ,
# endif
2013-12-02 19:31:46 -08:00
END_DATADESC ( )
LINK_ENTITY_TO_CLASS ( func_tankairboatgun , CFuncTankAirboatGun ) ;
//-----------------------------------------------------------------------------
// Precache:
//-----------------------------------------------------------------------------
void CFuncTankAirboatGun : : Precache ( void )
{
BaseClass : : Precache ( ) ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
// Odd placement, but it works
if ( m_iszShootSound = = NULL_STRING )
{
m_iszShootSound = AllocPooledString ( " Airboat.FireGunLoop " ) ;
PrecacheScriptSound ( STRING ( m_iszShootSound ) ) ;
}
# else
2013-12-02 19:31:46 -08:00
PrecacheScriptSound ( " Airboat.FireGunLoop " ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
PrecacheScriptSound ( " Airboat.FireGunRevDown " ) ;
CreateSounds ( ) ;
}
//-----------------------------------------------------------------------------
// Precache:
//-----------------------------------------------------------------------------
void CFuncTankAirboatGun : : Spawn ( void )
{
BaseClass : : Spawn ( ) ;
m_flNextHeavyShotTime = 0.0f ;
m_bIsFiring = false ;
m_flLastImpactEffectTime = - 1 ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
SetContextThink ( & CFuncTankAirboatGun : : FuncTankAirboatGunThink , gpGlobals - > curtime , s_pAirboatGunThinkContext ) ;
# endif
2013-12-02 19:31:46 -08:00
}
//-----------------------------------------------------------------------------
// 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 " ) ;
}
}
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
else if ( GetParent ( ) & & GetParent ( ) - > GetBaseAnimating ( ) )
{
m_hAirboatGunModel = GetParent ( ) - > GetBaseAnimating ( ) ;
m_nGunBarrelAttachment = GetGunBarrelAttachment ( ) ;
}
# endif
2013-12-02 19:31:46 -08:00
}
//-----------------------------------------------------------------------------
// Create/destroy looping sounds
//-----------------------------------------------------------------------------
void CFuncTankAirboatGun : : CreateSounds ( )
{
CSoundEnvelopeController & controller = CSoundEnvelopeController : : GetController ( ) ;
CPASAttenuationFilter filter ( this ) ;
if ( ! m_pGunFiringSound )
{
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
m_pGunFiringSound = controller . SoundCreate ( filter , entindex ( ) , STRING ( m_iszShootSound ) ) ;
# else
2013-12-02 19:31:46 -08:00
m_pGunFiringSound = controller . SoundCreate ( filter , entindex ( ) , " Airboat.FireGunLoop " ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
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 ( ) ;
}
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
//-----------------------------------------------------------------------------
// Maintains airboat gun sounds on NPCs
//-----------------------------------------------------------------------------
void CFuncTankAirboatGun : : FuncTankAirboatGunThink ( void )
{
if ( ! GetController ( ) )
{
if ( m_fireLast ! = 0 )
{
StartFiring ( ) ;
}
else
{
StopFiring ( ) ;
}
}
else if ( GetController ( ) - > IsNPC ( ) )
{
// Attempt to estimate when we wouldn't be firing
if ( ( gpGlobals - > curtime - m_fireLast - ( 1.0 / m_fireRate ) ) < 0.1f )
{
StartFiring ( ) ;
}
else
{
StopFiring ( ) ;
}
}
SetNextThink ( gpGlobals - > curtime + 0.05f , s_pAirboatGunThinkContext ) ;
}
void CFuncTankAirboatGun : : TankActivate ( void )
{
SetNextThink ( gpGlobals - > curtime + 0.05f , s_pAirboatGunThinkContext ) ;
BaseClass : : TankActivate ( ) ;
}
void CFuncTankAirboatGun : : TankDeactivate ( void )
{
DevMsg ( " Tank deactivate \n " ) ;
SetNextThink ( TICK_NEVER_THINK , s_pAirboatGunThinkContext ) ;
StopFiring ( ) ;
BaseClass : : TankDeactivate ( ) ;
}
void CFuncTankAirboatGun : : OnStartControlled ( )
{
if ( GetController ( ) & & GetController ( ) - > IsNPC ( ) )
SetNextThink ( gpGlobals - > curtime + 0.05f , s_pAirboatGunThinkContext ) ;
BaseClass : : OnStartControlled ( ) ;
}
# endif
2013-12-02 19:31:46 -08:00
//-----------------------------------------------------------------------------
// Stop controlled
//-----------------------------------------------------------------------------
void CFuncTankAirboatGun : : OnStopControlled ( )
{
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
DevMsg ( " Tank stop control \n " ) ;
SetNextThink ( TICK_NEVER_THINK , s_pAirboatGunThinkContext ) ;
# endif
2013-12-02 19:31:46 -08:00
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 ;
Mapbase v3.0
- Overhauled matcher system and added expanded wildcard support, meaning "text", "te?t", "blah_test", etc. are now supported instead of only trailing *
- Added support for regular expression matching
- Added the missing matrixinvert.h file, which prevented programmers from compiling vbsp
- Fixed env_global_light brightness
- Added info_player_view_proxy, an entity which mimics a player's view (created for script_intro)
- New UnZoomWithRate and SetZoomRate inputs on env_zoom
- New "Don't change target's angles" flag on logic_measure_movement
- Fixed logic_measure_movement not using ignore origin flags correctly
- Fixed filter_damage_mod secondary filter not recognizing mode correctly
- Fixed DisableGeigerCounter causing NPCs to ignore player, and possibly fixed other adverse effects in similar code
- Added SetEntityName input for setting an entity's targetname
- Added "SetTarget" support to point_posecontroller
- Added "sk_alyx_health" convar for adjustable npc_alyx health, restored/fixed "sk_barney_health" for adjustable npc_barney health
- Added ignore flags and several direction-related inputs to math_vector
- Added pose parameter stuff to logic_modelinfo
- Fixed ChangeWeapon not changing during combat
- Fixed holster/unholster on NPCs without holster/unholster animations
- Fixed func_tank and other "player in the way" code ignoring notarget
- Added SetPoseParameter input to animating entities
- Introduced an experimental chapter system which allows for custom chapter title/restriction management
- Added SetChapterTitle input to worldspawn for dynamic title changing
- Fixed func_tankairboatgun damage credit, added ability to use its damage keyvalues
- Fixed mapbase_rpc_enabled not actually affecting RPC
- Moved some newly found Combine soldier grenade code to ai_grenade for other grenade users
- Fixed some problems with the new shared activities
- Restored an old Mapbase entity as "prop_interactable", an entity which contains all of the functionality of a func_button/func_door in a prop entity
- Added env_microphone pitch scale for scaling the pitch of transmitted sounds
- Added experimental, long-overdue code for scenes to be able to access !target#'s and other scene-related names through AI_INPUT
- Added "mat_specular_disable_on_missing", which "disables" specular reflections when the cubemap texture is missing
- Fixed $envmapmasktransform and $bumptransform on VertexLitGeneric and related shaders
- Areaportal leaks now stop compilation in leaktest
- Replaced "-nodefaultcubemap" with "-defaultcubemap" as the shader changes allow maps to compile without default cubemaps by default with little effect
2020-02-05 19:08:49 +00:00
# ifdef MAPBASE
info . m_pAttacker = pAttacker ;
info . m_pAdditionalIgnoreEnt = GetParent ( ) ;
if ( m_bUseDamageKV )
{
info . m_flDamage = m_iBulletDamage ;
info . m_iPlayerDamage = m_iBulletDamageVsPlayer ;
}
# endif
2013-12-02 19:31:46 -08:00
if ( gpGlobals - > curtime > = m_flNextHeavyShotTime )
{
info . m_iShots = 1 ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
info . m_vecSpread = gTankSpread [ m_iHeavyShotSpread ] ;
# else
2013-12-02 19:31:46 -08:00
info . m_vecSpread = VECTOR_CONE_PRECALCULATED ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
info . m_flDamageForceScale = 1000.0f ;
}
else
{
info . m_iShots = 2 ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
info . m_vecSpread = m_spread ! = - 1 ? gTankSpread [ m_spread ] : VECTOR_CONE_5DEGREES ;
# else
2013-12-02 19:31:46 -08:00
info . m_vecSpread = VECTOR_CONE_5DEGREES ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
}
FireBullets ( info ) ;
DoMuzzleFlash ( ) ;
// NOTE: This must occur after FireBullets
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
if ( gpGlobals - > curtime > = m_flNextHeavyShotTime & & m_flHeavyShotInterval ! = - 1 )
# else
2013-12-02 19:31:46 -08:00
if ( gpGlobals - > curtime > = m_flNextHeavyShotTime )
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
{
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
m_flNextHeavyShotTime = gpGlobals - > curtime + m_flHeavyShotInterval ;
# else
2013-12-02 19:31:46 -08:00
m_flNextHeavyShotTime = gpGlobals - > curtime + AIRBOAT_GUN_HEAVY_SHOT_INTERVAL ;
2019-08-31 19:28:20 +00:00
# endif
}
# ifdef MAPBASE
// Things from CFuncTank::Fire().
// We can't use everything because it overrides a few things.
if ( pAttacker & & pAttacker - > IsPlayer ( ) )
{
if ( IsX360 ( ) )
{
// Now, if you're playing Mapbase on the Xbox 360, the airboat gun turret will make your controller rumble!
// Isn't that lovely? Hmm?
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 ) ;
}
2013-12-02 19:31:46 -08:00
}
2019-08-31 19:28:20 +00:00
m_OnFire . FireOutput ( pAttacker , this ) ;
m_bReadyToFire = false ;
# endif
2013-12-02 19:31:46 -08:00
}
//-----------------------------------------------------------------------------
// 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 ) ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
static CMortarShell * Create ( const Vector & vecStart , const trace_t & tr , const Vector & vecShotDir , float flImpactDelay , float flWarnDelay , string_t warnSound ) ;
# else
2013-12-02 19:31:46 -08:00
static CMortarShell * Create ( const Vector & vecStart , const Vector & vecTarget , const Vector & vecShotDir , float flImpactDelay , float flWarnDelay , string_t warnSound ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
void Spawn ( void ) ;
void Precache ( void ) ;
void Impact ( void ) ;
void Warn ( void ) ;
void FlyThink ( void ) ;
void FadeThink ( void ) ;
int UpdateTransmitState ( void ) ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
void SetRadius ( float fl ) { m_flRadius = fl ; }
void SetMagnitude ( int i ) { m_Magnitude = i ; }
public :
bool m_bDontHitController ;
# endif
2013-12-02 19:31:46 -08:00
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 ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
int m_Magnitude ;
# endif
2013-12-02 19:31:46 -08:00
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 ) ,
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
DEFINE_FIELD ( m_bDontHitController , FIELD_BOOLEAN ) ,
DEFINE_FIELD ( m_Magnitude , FIELD_INTEGER ) ,
# endif
2013-12-02 19:31:46 -08:00
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
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
CMortarShell * CMortarShell : : Create ( const Vector & vecStart , const trace_t & tr , const Vector & vecShotDir , float flImpactDelay , float flWarnDelay , string_t warnSound )
# else
2013-12-02 19:31:46 -08:00
CMortarShell * CMortarShell : : Create ( const Vector & vecStart , const Vector & vecTarget , const Vector & vecShotDir , float flImpactDelay , float flWarnDelay , string_t warnSound )
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
{
CMortarShell * pShell = ( CMortarShell * ) CreateEntityByName ( " mortarshell " ) ;
2019-08-31 19:28:20 +00:00
# ifndef MAPBASE
2013-12-02 19:31:46 -08:00
// 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 ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
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 ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
float flRadius = m_flRadius ;
# else
2013-12-02 19:31:46 -08:00
float flRadius = MORTAR_BLAST_RADIUS ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
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
) ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
RadiusDamage ( CTakeDamageInfo ( this , GetOwnerEntity ( ) , m_Magnitude / 2 , ( DMG_BLAST | DMG_DISSOLVE ) ) , GetAbsOrigin ( ) , flRadius , CLASS_NONE , m_bDontHitController ? GetOwnerEntity ( ) : NULL ) ;
# else
2013-12-02 19:31:46 -08:00
RadiusDamage ( CTakeDamageInfo ( this , GetOwnerEntity ( ) , MORTAR_BLAST_DAMAGE , ( DMG_BLAST | DMG_DISSOLVE ) ) , GetAbsOrigin ( ) , MORTAR_BLAST_RADIUS , CLASS_NONE , NULL ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
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 ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
# define m_fireStartSound m_iszShootSound
# else
2013-12-02 19:31:46 -08:00
string_t m_fireStartSound ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
//string_t m_fireEndSound;
string_t m_incomingSound ;
float m_flWarningTime ;
float m_flFireVariance ;
bool m_fLastShotMissed ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
float m_flRadius ;
int m_iMortarTraceMask = MASK_SOLID_BRUSHONLY ;
# endif
2013-12-02 19:31:46 -08:00
// 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 ) ,
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
DEFINE_KEYFIELD ( m_flRadius , FIELD_FLOAT , " radius " ) ,
DEFINE_KEYFIELD ( m_iMortarTraceMask , FIELD_INTEGER , " trace_mask " ) ,
# endif
2013-12-02 19:31:46 -08:00
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.
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
UTIL_TraceLine ( vecSpot , vecSpot + Vector ( 0 , 0 , 1024 ) , m_iMortarTraceMask , NULL , COLLISION_GROUP_NONE , & tr ) ;
# else
2013-12-02 19:31:46 -08:00
UTIL_TraceLine ( vecSpot , vecSpot + Vector ( 0 , 0 , 1024 ) , MASK_SOLID_BRUSHONLY , NULL , COLLISION_GROUP_NONE , & tr ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
vecSpot = tr . endpos ;
//NDebugOverlay::Line( tr.startpos, tr.endpos, 0,255,0, false, 5 );
// Now trace from apex to target
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
UTIL_TraceLine ( vecSpot , vecProjectedPosition , m_iMortarTraceMask , NULL , COLLISION_GROUP_NONE , & tr ) ;
# else
2013-12-02 19:31:46 -08:00
UTIL_TraceLine ( vecSpot , vecProjectedPosition , MASK_SOLID_BRUSHONLY , NULL , COLLISION_GROUP_NONE , & tr ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
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 ) ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
CMortarShell * pShell = CMortarShell : : Create ( barrelEnd , tr , vecFinalDir , m_fireDelay , m_flWarningTime , m_incomingSound ) ;
pShell - > SetOwnerEntity ( GetController ( ) ) ;
pShell - > m_bDontHitController = m_bDontHitController ;
if ( m_flRadius ! = 0 )
pShell - > SetRadius ( m_flRadius ) ;
pShell - > SetMagnitude ( m_Magnitude ) ;
# else
2013-12-02 19:31:46 -08:00
CMortarShell : : Create ( barrelEnd , tr . endpos , vecFinalDir , m_fireDelay , m_flWarningTime , m_incomingSound ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
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)
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
bool m_bControllableVersion = false ; // Allows new behavior that makes player/NPC control easier. Doesn't use spawnflag for legacy purposes, as if anyone used this as a regular tank before.
# endif
2013-12-02 19:31:46 -08:00
} ;
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 ) ,
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
DEFINE_KEYFIELD ( m_bControllableVersion , FIELD_BOOLEAN , " ControllableVersion " ) ,
# endif
2013-12-02 19:31:46 -08:00
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 ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
if ( ! m_bControllableVersion )
CreateBeam ( ) ;
# else
2013-12-02 19:31:46 -08:00
CreateBeam ( ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
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 ( )
{
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
// Only maintain 1.5 rounds per second if we're using legacy behavior.
if ( ! m_bControllableVersion )
m_fireRate = 1.5 ;
# else
2013-12-02 19:31:46 -08:00
// Maintain 1.5 rounds per second rate of fire.
m_fireRate = 1.5 ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
/*
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 ( ) ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
// Controllables don't sweep
if ( m_bControllableVersion )
return ;
# endif
2013-12-02 19:31:46 -08:00
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.
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
CTraceFilterSimple traceFilter = GetTraceFilter ( ) ;
# else
2013-12-02 19:31:46 -08:00
CTraceFilterSkipTwoEntities traceFilter ( this , GetParent ( ) , COLLISION_GROUP_NONE ) ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
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 )
{
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
// If we're in aim-at-pos mode and not in the new controllable version, don't fire in this mode
if ( HasSpawnFlags ( SF_TANK_AIM_AT_POS ) & & ! m_bControllableVersion )
return ;
# else
2013-12-02 19:31:46 -08:00
// Specifically do NOT fire in aim at pos mode. This is just for show.
if ( HasSpawnFlags ( SF_TANK_AIM_AT_POS ) )
return ;
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
Vector vecAdjustedForward = forward ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
if ( ! IsPlayerManned ( ) & & m_hTarget ! = NULL )
# else
2013-12-02 19:31:46 -08:00
if ( m_hTarget ! = NULL )
2019-08-31 19:28:20 +00:00
# endif
2013-12-02 19:31:46 -08:00
{
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 ) ;
2019-08-31 19:28:20 +00:00
# ifdef MAPBASE
//-----------------------------------------------------------------------------
// Purpose: Func tank that fires a bunch of outputs instead
//-----------------------------------------------------------------------------
class CFuncTankLogic : public CFuncTank
{
public :
DECLARE_CLASS ( CFuncTankLogic , CFuncTank ) ;
DECLARE_DATADESC ( ) ;
CFuncTankLogic ( ) ;
void Fire ( int bulletCount , const Vector & barrelEnd , const Vector & forward , CBaseEntity * pAttacker , bool bIgnoreSpread ) ;
protected :
bool m_bShootsThroughWater ;
COutputVector m_OnFire_BarrelPos ;
COutputVector m_OnFire_BarrelAng ;
COutputVector m_OnFire_ShootPos ;
COutputEHANDLE m_OnFire_FirstEnt ;
} ;
LINK_ENTITY_TO_CLASS ( func_tanklogic , CFuncTankLogic ) ;
BEGIN_DATADESC ( CFuncTankLogic )
//DEFINE_KEYFIELD( m_bDontHitController, FIELD_BOOLEAN, "DontHitController" ),
DEFINE_OUTPUT ( m_OnFire_BarrelPos , " OnFire_BarrelPos " ) ,
DEFINE_OUTPUT ( m_OnFire_BarrelAng , " OnFire_BarrelAng " ) ,
DEFINE_OUTPUT ( m_OnFire_ShootPos , " OnFire_ShootPos " ) ,
DEFINE_OUTPUT ( m_OnFire_FirstEnt , " OnFire_FirstEnt " ) ,
END_DATADESC ( )
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CFuncTankLogic : : CFuncTankLogic ( )
{
// This is overriden by KV later
m_bDontHitController = true ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CFuncTankLogic : : Fire ( int bulletCount , const Vector & barrelEnd , const Vector & forward , CBaseEntity * pAttacker , bool bIgnoreSpread )
{
// A bunch of stuff from FireBullets() to replicate firing a regular func_tank.
// It mostly just has the trace stuff.
Vector vecDir ;
Vector vecEnd ;
trace_t tr ;
CTraceFilterSimple traceFilter = GetTraceFilter ( ) ;
CShotManipulator Manipulator ( forward ) ;
vecDir = Manipulator . ApplySpread ( bIgnoreSpread ? gTankSpread [ 0 ] : gTankSpread [ m_spread ] ) ;
vecEnd = barrelEnd + vecDir * MAX_TRACE_LENGTH ;
int mask = MASK_SHOT ;
if ( ! m_bShootsThroughWater )
mask | = CONTENTS_WATER ;
AI_TraceLine ( barrelEnd , vecEnd , mask , & traceFilter , & tr ) ;
if ( tr . startsolid )
{
tr . endpos = tr . startpos ;
tr . fraction = 0.0f ;
}
if ( ai_debug_shoot_positions . GetBool ( ) )
NDebugOverlay : : Line ( barrelEnd , tr . endpos , 255 , 255 , 255 , false , .1 ) ;
BaseClass : : Fire ( bulletCount , barrelEnd , forward , pAttacker , bIgnoreSpread ) ;
m_OnFire_BarrelPos . Set ( barrelEnd , pAttacker , this ) ;
m_OnFire_BarrelAng . Set ( forward , pAttacker , this ) ;
m_OnFire_ShootPos . Set ( tr . endpos , pAttacker , this ) ;
m_OnFire_FirstEnt . Set ( tr . m_pEnt , tr . m_pEnt , this ) ;
}
# endif // MAPBASE