You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1700 lines
46 KiB
1700 lines
46 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include "cbase.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 "triggers.h" |
|
#include "physics_cannister.h" |
|
|
|
#include "player.h" |
|
#include "entitylist.h" |
|
|
|
|
|
#define SF_TANK_ACTIVE 0x0001 |
|
#define SF_TANK_PLAYER 0x0002 |
|
#define SF_TANK_HUMANS 0x0004 |
|
#define SF_TANK_ALIENS 0x0008 |
|
#define SF_TANK_LINEOFSIGHT 0x0010 |
|
#define SF_TANK_CANCONTROL 0x0020 |
|
#define SF_TANK_DAMAGE_KICK 0x0040 // Kick when take damage |
|
#define SF_TANK_AIM_AT_POS 0x0080 // Aim at a particular position |
|
#define SF_TANK_SOUNDON 0x8000 |
|
|
|
|
|
enum TANKBULLET |
|
{ |
|
TANK_BULLET_NONE = 0, |
|
TANK_BULLET_SMALL = 1, |
|
TANK_BULLET_MEDIUM = 2, |
|
TANK_BULLET_LARGE = 3, |
|
}; |
|
|
|
#define FUNCTANK_FIREVOLUME 1000 |
|
|
|
|
|
// Custom damage |
|
// env_laser (duration is 0.5 rate of fire) |
|
// rockets |
|
// explosion? |
|
|
|
class CFuncTank : public CBaseEntity |
|
{ |
|
DECLARE_CLASS( CFuncTank, CBaseEntity ); |
|
public: |
|
~CFuncTank( void ); |
|
void Spawn( void ); |
|
void Activate( void ); |
|
void Precache( void ); |
|
bool CreateVPhysics( void ); |
|
bool KeyValue( const char *szKeyName, const char *szValue ); |
|
|
|
int ObjectCaps( void ); |
|
void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); |
|
void Think( void ); |
|
void TrackTarget( void ); |
|
int DrawDebugTextOverlays(void); |
|
|
|
virtual void FiringSequence( const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker ); |
|
virtual void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker ); |
|
|
|
void StartRotSound( void ); |
|
void StopRotSound( void ); |
|
|
|
inline bool IsActive( void ) { return (m_spawnflags & SF_TANK_ACTIVE)?TRUE:FALSE; } |
|
|
|
// Input handlers. |
|
void InputActivate( inputdata_t &inputdata ); |
|
void InputDeactivate( inputdata_t &inputdata ); |
|
void InputSetFireRate( inputdata_t &inputdata ); |
|
void InputSetTargetPosition( inputdata_t &inputdata ); |
|
void InputSetTargetEntityName( inputdata_t &inputdata ); |
|
void InputSetTargetEntity( inputdata_t &inputdata ); |
|
|
|
void TankActivate(void); |
|
void TankDeactivate(void); |
|
|
|
inline bool CanFire( void ); |
|
bool InRange( float range ); |
|
|
|
void TankTrace( const Vector &vecStart, const Vector &vecForward, const Vector &vecSpread, trace_t &tr ); |
|
void TraceAttack( CBaseEntity *pAttacker, float flDamage, const Vector &vecDir, trace_t *ptr, int bitsDamageType); |
|
|
|
Vector WorldBarrelPosition( void ) |
|
{ |
|
EntityMatrix tmp; |
|
tmp.InitFromEntity( this ); |
|
return tmp.LocalToWorld( m_barrelPos ); |
|
} |
|
|
|
void UpdateMatrix( void ) |
|
{ |
|
m_parentMatrix.InitFromEntity( GetParent() ? GetParent() : NULL ); |
|
} |
|
QAngle AimBarrelAt( const Vector &parentTarget ); |
|
|
|
bool ShouldSavePhysics() { return false; } |
|
|
|
DECLARE_DATADESC(); |
|
|
|
bool OnControls( CBaseEntity *pTest ); |
|
bool StartControl( CBasePlayer *pController ); |
|
void StopControl( void ); |
|
void ControllerPostFrame( void ); |
|
|
|
CBaseEntity *FindTarget( string_t targetName, CBaseEntity *pActivator ); |
|
|
|
protected: |
|
CBasePlayer* m_pController; |
|
float m_flNextAttack; |
|
Vector m_vecControllerUsePos; |
|
|
|
float m_yawCenter; // "Center" yaw |
|
float m_yawRate; // Max turn rate to track targets |
|
float m_yawRange; // Range of turning motion (one-sided: 30 is +/- 30 degress from center) |
|
// Zero is full rotation |
|
float m_yawTolerance; // Tolerance angle |
|
|
|
float m_pitchCenter; // "Center" pitch |
|
float m_pitchRate; // Max turn rate on pitch |
|
float m_pitchRange; // Range of pitch motion as above |
|
float m_pitchTolerance; // Tolerance angle |
|
|
|
float m_fireLast; // Last time I fired |
|
float m_fireRate; // How many rounds/second |
|
float m_lastSightTime;// Last time I saw target |
|
float m_persist; // Persistence of firing (how long do I shoot when I can't see) |
|
float m_persist2; // Secondary persistence of firing (randomly shooting when I can't see) |
|
float m_persist2burst;// How long secondary persistence burst lasts |
|
float m_minRange; // Minimum range to aim/track |
|
float m_maxRange; // Max range to aim/track |
|
|
|
Vector m_barrelPos; // Length of the freakin barrel |
|
float m_spriteScale; // Scale of any sprites we shoot |
|
string_t m_iszSpriteSmoke; |
|
string_t m_iszSpriteFlash; |
|
TANKBULLET m_bulletType; // Bullet type |
|
int m_iBulletDamage; // 0 means use Bullet type's default damage |
|
|
|
Vector m_sightOrigin; // Last sight of target |
|
int m_spread; // firing spread |
|
string_t m_iszMaster; // Master entity (game_team_master or multisource) |
|
|
|
int m_iSmallAmmoType; |
|
int m_iMediumAmmoType; |
|
int m_iLargeAmmoType; |
|
|
|
string_t m_soundStartRotate; |
|
string_t m_soundStopRotate; |
|
string_t m_soundLoopRotate; |
|
|
|
string_t m_targetEntityName; |
|
EHANDLE m_hTarget; |
|
Vector m_vTargetPosition; |
|
EntityMatrix m_parentMatrix; |
|
|
|
COutputEvent m_OnFire; |
|
COutputEvent m_OnLoseTarget; |
|
COutputEvent m_OnAquireTarget; |
|
|
|
CHandle<CBaseTrigger> m_hControlVolume; |
|
string_t m_iszControlVolume; |
|
}; |
|
|
|
|
|
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_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_KEYFIELD( m_spriteScale, FIELD_FLOAT, "spritescale" ), |
|
DEFINE_KEYFIELD( m_iszSpriteSmoke, FIELD_STRING, "spritesmoke" ), |
|
DEFINE_KEYFIELD( m_iszSpriteFlash, FIELD_STRING, "spriteflash" ), |
|
DEFINE_KEYFIELD( m_bulletType, FIELD_INTEGER, "bullet" ), |
|
DEFINE_KEYFIELD( m_spread, FIELD_INTEGER, "firespread" ), |
|
DEFINE_KEYFIELD( m_iBulletDamage, FIELD_INTEGER, "bullet_damage" ), |
|
DEFINE_KEYFIELD( m_iszMaster, FIELD_STRING, "master" ), |
|
DEFINE_KEYFIELD( m_soundStartRotate, FIELD_SOUNDNAME, "rotatestartsound" ), |
|
DEFINE_KEYFIELD( m_soundStopRotate, FIELD_SOUNDNAME, "rotatestopsound" ), |
|
DEFINE_KEYFIELD( m_soundLoopRotate, FIELD_SOUNDNAME, "rotatesound" ), |
|
DEFINE_KEYFIELD( m_iszControlVolume, FIELD_STRING, "control_volume" ), |
|
|
|
DEFINE_FIELD( m_yawCenter, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_pitchCenter, 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_VECTOR ), |
|
DEFINE_FIELD( m_pController, FIELD_CLASSPTR ), |
|
DEFINE_FIELD( m_vecControllerUsePos, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_flNextAttack, FIELD_TIME ), |
|
DEFINE_FIELD( m_targetEntityName, FIELD_STRING ), |
|
DEFINE_FIELD( m_vTargetPosition, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_persist2burst, FIELD_FLOAT), |
|
//DEFINE_FIELD( m_parentMatrix, FIELD_MATRIX ), // DON'T SAVE |
|
DEFINE_FIELD( m_hTarget, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_hControlVolume, FIELD_EHANDLE ), |
|
|
|
|
|
// Do not save these. |
|
//DEFINE_FIELD( m_iSmallAmmoType, FIELD_INTEGER ), |
|
//DEFINE_FIELD( m_iMediumAmmoType, FIELD_INTEGER ), |
|
//DEFINE_FIELD( m_iLargeAmmoType, FIELD_INTEGER ), |
|
|
|
// Inputs |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Activate", InputActivate ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Deactivate", InputDeactivate ), |
|
DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFireRate", InputSetFireRate ), |
|
DEFINE_INPUTFUNC( FIELD_VECTOR, "SetTargetPosition", InputSetTargetPosition ), |
|
DEFINE_INPUTFUNC( FIELD_STRING, "SetTargetEntityName", InputSetTargetEntityName ), |
|
DEFINE_INPUTFUNC( FIELD_EHANDLE, "SetTargetEntity", InputSetTargetEntity ), |
|
|
|
// Outputs |
|
DEFINE_OUTPUT(m_OnFire, "OnFire"), |
|
DEFINE_OUTPUT(m_OnLoseTarget, "OnLoseTarget"), |
|
DEFINE_OUTPUT(m_OnAquireTarget, "OnAquireTarget"), |
|
|
|
END_DATADESC() |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CFuncTank::~CFuncTank( void ) |
|
{ |
|
if ( m_soundLoopRotate != NULL_STRING && (m_spawnflags & SF_TANK_SOUNDON) ) |
|
{ |
|
StopSound( entindex(), CHAN_STATIC, STRING(m_soundLoopRotate) ); |
|
} |
|
} |
|
|
|
|
|
int CFuncTank::ObjectCaps( void ) |
|
{ |
|
int iCaps = BaseClass::ObjectCaps(); |
|
|
|
if ( m_spawnflags & SF_TANK_CANCONTROL ) |
|
{ |
|
iCaps |= FCAP_IMPULSE_USE; |
|
} |
|
|
|
return iCaps; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// 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 |
|
else if (flTimeDelay <= m_persist2burst) |
|
{ |
|
return true; |
|
} |
|
// If less than persistence2, occasionally do another burst |
|
else if (flTimeDelay <= m_persist2) |
|
{ |
|
if (random->RandomInt(0,30)==0) |
|
{ |
|
m_persist2burst = flTimeDelay+0.5; |
|
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 = 0; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// 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 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 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: 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: |
|
// 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); |
|
Msg( "%f\n",vCrossProduct.z); |
|
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); |
|
} |
|
else if (FStrEq(szKeyName, "barrely")) |
|
{ |
|
m_barrelPos.y = atof(szValue); |
|
} |
|
else if (FStrEq(szKeyName, "barrelz")) |
|
{ |
|
m_barrelPos.z = atof(szValue); |
|
} |
|
else |
|
return BaseClass::KeyValue( szKeyName, szValue ); |
|
|
|
return true; |
|
} |
|
|
|
|
|
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) |
|
|
|
|
|
void CFuncTank::Spawn( void ) |
|
{ |
|
Precache(); |
|
|
|
SetMoveType( MOVETYPE_PUSH ); // so it doesn't get pushed by anything |
|
SetSolid( SOLID_VPHYSICS ); |
|
SetModel( STRING( GetModelName() ) ); |
|
|
|
m_yawCenter = GetLocalAngles().y; |
|
m_pitchCenter = GetLocalAngles().x; |
|
m_vTargetPosition = vec3_origin; |
|
|
|
if ( IsActive() ) |
|
SetNextThink( 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; |
|
CreateVPhysics(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::Activate( void ) |
|
{ |
|
BaseClass::Activate(); |
|
|
|
m_hControlVolume = NULL; |
|
|
|
// Find our control volume |
|
if ( m_iszControlVolume != NULL_STRING ) |
|
{ |
|
m_hControlVolume = dynamic_cast<CBaseTrigger*>( gEntList.FindEntityByName( NULL, m_iszControlVolume ) ); |
|
} |
|
|
|
if (( !m_hControlVolume ) && (m_spawnflags & SF_TANK_CANCONTROL)) |
|
{ |
|
Msg( "ERROR: Couldn't find control volume for player-controllable func_tank %s.\n", STRING(GetEntityName()) ); |
|
UTIL_Remove( this ); |
|
} |
|
} |
|
|
|
bool CFuncTank::CreateVPhysics() |
|
{ |
|
VPhysicsInitShadow( false, false ); |
|
return true; |
|
} |
|
|
|
|
|
void CFuncTank::Precache( void ) |
|
{ |
|
m_iSmallAmmoType = GetAmmoDef()->Index("9mmRound"); |
|
m_iMediumAmmoType = GetAmmoDef()->Index("9mmRound"); |
|
m_iLargeAmmoType = GetAmmoDef()->Index("12mmRound"); |
|
|
|
if ( m_iszSpriteSmoke != NULL_STRING ) |
|
PrecacheModel( STRING(m_iszSpriteSmoke) ); |
|
if ( m_iszSpriteFlash != NULL_STRING ) |
|
PrecacheModel( STRING(m_iszSpriteFlash) ); |
|
|
|
if ( m_soundStartRotate != NULL_STRING ) |
|
PrecacheScriptSound( STRING(m_soundStartRotate) ); |
|
if ( m_soundStopRotate != NULL_STRING ) |
|
PrecacheScriptSound( STRING(m_soundStopRotate) ); |
|
if ( m_soundLoopRotate != NULL_STRING ) |
|
PrecacheScriptSound( STRING(m_soundLoopRotate) ); |
|
} |
|
|
|
|
|
|
|
//================================================================================== |
|
// TANK CONTROLLING |
|
bool CFuncTank::OnControls( CBaseEntity *pTest ) |
|
{ |
|
if ( !(m_spawnflags & SF_TANK_CANCONTROL) ) |
|
return FALSE; |
|
|
|
Vector offset = pTest->GetLocalOrigin() - GetLocalOrigin(); |
|
|
|
if ( (m_vecControllerUsePos - pTest->GetLocalOrigin()).Length() < 30 ) |
|
return TRUE; |
|
|
|
return FALSE; |
|
} |
|
|
|
bool CFuncTank::StartControl( CBasePlayer *pController ) |
|
{ |
|
if ( m_pController != NULL ) |
|
return FALSE; |
|
|
|
// Team only or disabled? |
|
if ( m_iszMaster != NULL_STRING ) |
|
{ |
|
if ( !UTIL_IsMasterTriggered( m_iszMaster, pController ) ) |
|
return FALSE; |
|
} |
|
|
|
// Holster player's weapon |
|
m_pController = pController; |
|
if ( m_pController->GetActiveWeapon() ) |
|
{ |
|
m_pController->GetActiveWeapon()->Holster(); |
|
} |
|
|
|
m_pController->m_Local.m_iHideHUD |= HIDEHUD_WEAPONSELECTION; |
|
m_vecControllerUsePos = m_pController->GetLocalOrigin(); |
|
|
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
|
|
return TRUE; |
|
} |
|
|
|
void CFuncTank::StopControl() |
|
{ |
|
// TODO: bring back the controllers current weapon |
|
if ( !m_pController ) |
|
return; |
|
|
|
if ( m_pController->GetActiveWeapon() ) |
|
m_pController->GetActiveWeapon()->Deploy(); |
|
|
|
m_pController->m_Local.m_iHideHUD &= ~HIDEHUD_WEAPONSELECTION; |
|
SetNextThink( TICK_NEVER_THINK ); |
|
m_pController = NULL; |
|
|
|
if ( IsActive() ) |
|
SetNextThink( gpGlobals->curtime + 1.0f ); |
|
} |
|
|
|
// Called each frame by the player's ItemPostFrame |
|
void CFuncTank::ControllerPostFrame( void ) |
|
{ |
|
Assert(m_pController != NULL); |
|
|
|
if ( gpGlobals->curtime < m_flNextAttack ) |
|
return; |
|
|
|
Vector forward; |
|
AngleVectors( GetAbsAngles(), &forward ); |
|
if ( m_pController->m_nButtons & IN_ATTACK ) |
|
{ |
|
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; |
|
|
|
Fire( bulletCount, WorldBarrelPosition(), forward, m_pController ); |
|
|
|
// HACKHACK -- make some noise (that the AI can hear) |
|
CSoundEnt::InsertSound( SOUND_COMBAT, WorldSpaceCenter(), FUNCTANK_FIREVOLUME, 0.2 ); |
|
|
|
m_flNextAttack = gpGlobals->curtime + (1/m_fireRate); |
|
} |
|
} |
|
|
|
|
|
void CFuncTank::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) |
|
{ |
|
if ( m_spawnflags & SF_TANK_CANCONTROL ) |
|
{ |
|
// player controlled turret |
|
CBasePlayer *pPlayer = ToBasePlayer( pActivator ); |
|
if ( !pPlayer ) |
|
return; |
|
|
|
if ( value == 2 && useType == USE_SET ) |
|
{ |
|
ControllerPostFrame(); |
|
} |
|
else if ( !m_pController && useType != USE_OFF ) |
|
{ |
|
// The player must be within the func_tank controls |
|
Assert( m_hControlVolume ); |
|
if ( !m_hControlVolume->IsTouching( pPlayer ) ) |
|
return; |
|
|
|
pPlayer->SetUseEntity( this ); |
|
StartControl( pPlayer ); |
|
} |
|
else |
|
{ |
|
StopControl(); |
|
} |
|
} |
|
else |
|
{ |
|
if ( !ShouldToggle( useType, IsActive() ) ) |
|
return; |
|
|
|
if ( IsActive() ) |
|
{ |
|
TankDeactivate(); |
|
} |
|
else |
|
{ |
|
TankActivate(); |
|
} |
|
} |
|
} |
|
|
|
|
|
bool CFuncTank::InRange( float range ) |
|
{ |
|
if ( range < m_minRange ) |
|
return FALSE; |
|
if ( m_maxRange > 0 && range > m_maxRange ) |
|
return FALSE; |
|
|
|
return TRUE; |
|
} |
|
|
|
|
|
void CFuncTank::Think( void ) |
|
{ |
|
// refresh the matrix |
|
UpdateMatrix(); |
|
|
|
SetLocalAngularVelocity( vec3_angle ); |
|
TrackTarget(); |
|
|
|
if ( fabs(GetLocalAngularVelocity().x) > 1 || fabs(GetLocalAngularVelocity().y) > 1 ) |
|
StartRotSound(); |
|
else |
|
StopRotSound(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Aim the offset barrel at a position in parent space |
|
// Input : parentTarget - the position of the target in parent space |
|
// Output : Vector - angles in local space |
|
//----------------------------------------------------------------------------- |
|
QAngle CFuncTank::AimBarrelAt( const Vector &parentTarget ) |
|
{ |
|
Vector target = parentTarget - GetLocalOrigin(); |
|
float quadTarget = target.LengthSqr(); |
|
float quadTargetXY = target.x*target.x + target.y*target.y; |
|
|
|
// Target is too close! Can't aim at it |
|
if ( quadTarget <= m_barrelPos.LengthSqr() ) |
|
{ |
|
return GetLocalAngles(); |
|
} |
|
else |
|
{ |
|
// We're trying to aim the offset barrel at an arbitrary point. |
|
// To calculate this, I think of the target as being on a sphere with |
|
// it's center at the origin of the gun. |
|
// The rotation we need is the opposite of the rotation that moves the target |
|
// along the surface of that sphere to intersect with the gun's shooting direction |
|
// To calculate that rotation, we simply calculate the intersection of the ray |
|
// coming out of the barrel with the target sphere (that's the new target position) |
|
// and use atan2() to get angles |
|
|
|
// angles from target pos to center |
|
float targetToCenterYaw = atan2( target.y, target.x ); |
|
float centerToGunYaw = atan2( m_barrelPos.y, sqrt( quadTarget - (m_barrelPos.y*m_barrelPos.y) ) ); |
|
|
|
float targetToCenterPitch = atan2( target.z, sqrt( quadTargetXY ) ); |
|
float centerToGunPitch = atan2( -m_barrelPos.z, sqrt( quadTarget - (m_barrelPos.z*m_barrelPos.z) ) ); |
|
return QAngle( -RAD2DEG(targetToCenterPitch+centerToGunPitch), RAD2DEG( targetToCenterYaw - centerToGunYaw ), 0 ); |
|
} |
|
} |
|
|
|
|
|
void CFuncTank::TrackTarget( void ) |
|
{ |
|
trace_t tr; |
|
bool updateTime = FALSE, lineOfSight; |
|
QAngle angles; |
|
Vector barrelEnd; |
|
CBaseEntity *pTarget = NULL; |
|
|
|
barrelEnd.Init(); |
|
|
|
// Get a position to aim for |
|
if (m_pController) |
|
{ |
|
// Tanks attempt to mirror the player's angles |
|
angles = m_pController->EyeAngles(); |
|
SetNextThink( gpGlobals->curtime + 0.05 ); |
|
} |
|
else |
|
{ |
|
if ( IsActive() ) |
|
{ |
|
SetNextThink( gpGlobals->curtime + 0.1f ); |
|
} |
|
else |
|
{ |
|
return; |
|
} |
|
|
|
// ----------------------------------- |
|
// Get world target position |
|
// ----------------------------------- |
|
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 ) // New HL2 behavior |
|
{ |
|
m_hTarget = FindTarget( m_targetEntityName, NULL ); |
|
} |
|
else // HL1 style |
|
{ |
|
m_hTarget = ToBasePlayer( GetContainingEntity( UTIL_FindClientInPVS( edict() ) ) ); |
|
} |
|
|
|
if ( m_hTarget != NULL ) |
|
{ |
|
SetNextThink( gpGlobals->curtime ); // Think again immediately |
|
} |
|
else |
|
{ |
|
if ( IsActive() ) |
|
{ |
|
SetNextThink( gpGlobals->curtime + 2 ); // Wait 2 secs |
|
} |
|
|
|
if ( m_fireLast !=0 ) |
|
{ |
|
m_OnLoseTarget.FireOutput(this, this); |
|
m_fireLast = 0; |
|
} |
|
} |
|
|
|
return; |
|
} |
|
pTarget = pEntity; |
|
|
|
// Calculate angle needed to aim at target |
|
worldTargetPosition = pEntity->EyePosition(); |
|
} |
|
|
|
float range = (worldTargetPosition - barrelEnd).Length(); |
|
|
|
if ( !InRange( range ) ) |
|
{ |
|
m_fireLast = 0; |
|
return; |
|
} |
|
|
|
UTIL_TraceLine( barrelEnd, worldTargetPosition, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); |
|
|
|
if (m_spawnflags & SF_TANK_AIM_AT_POS) |
|
{ |
|
updateTime = TRUE; |
|
m_sightOrigin = m_vTargetPosition; |
|
} |
|
else |
|
{ |
|
lineOfSight = FALSE; |
|
// No line of sight, don't track |
|
if ( tr.fraction == 1.0 || tr.m_pEnt == pTarget ) |
|
{ |
|
lineOfSight = TRUE; |
|
|
|
CBaseEntity *pInstance = pTarget; |
|
if ( InRange( range ) && pInstance && pInstance->IsAlive() ) |
|
{ |
|
updateTime = TRUE; |
|
|
|
// Sight position is BodyTarget with no noise (so gun doesn't bob up and down) |
|
m_sightOrigin = pInstance->BodyTarget( GetLocalOrigin(), false ); |
|
} |
|
} |
|
} |
|
|
|
// Convert targetPosition to parent |
|
angles = AimBarrelAt( m_parentMatrix.WorldToLocal( m_sightOrigin ) ); |
|
} |
|
|
|
// 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 ); |
|
angles.y = m_yawCenter + offsetY; |
|
angles.x = m_pitchCenter + offsetX; |
|
|
|
// Limit against range in y |
|
|
|
// MDB - don't check pitch! If two func_tanks are meant to align, |
|
// and one can pitch and the other cannot, this can lead to them getting |
|
// different values for angles.y. Nothing is lost by not updating yaw |
|
// because the target is not in pitch range. |
|
|
|
bool bOutsideYawRange = ( fabs( offsetY ) > m_yawRange + m_yawTolerance ); |
|
bool bOutsidePitchRange = ( fabs( offsetX ) > m_pitchRange + m_pitchTolerance ); |
|
|
|
Vector vecToTarget = m_sightOrigin - GetLocalOrigin(); |
|
|
|
// if target is outside yaw range |
|
if ( bOutsideYawRange ) |
|
{ |
|
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); |
|
} |
|
} |
|
|
|
if ( bOutsidePitchRange || bOutsideYawRange || ( vecToTarget.Length() < ( barrelEnd - GetAbsOrigin() ).Length() ) ) |
|
{ |
|
// Don't update if you saw the player, but out of range |
|
updateTime = false; |
|
} |
|
|
|
if ( updateTime ) |
|
{ |
|
m_lastSightTime = gpGlobals->curtime; |
|
m_persist2burst = 0; |
|
} |
|
|
|
// Move toward target at rate or less |
|
float distY = UTIL_AngleDistance( angles.y, GetLocalAngles().y ); |
|
|
|
QAngle vecAngVel = GetLocalAngularVelocity(); |
|
vecAngVel.y = distY * 10; |
|
vecAngVel.y = clamp( vecAngVel.y, -m_yawRate, m_yawRate ); |
|
|
|
// Limit against range in x |
|
angles.x = clamp( angles.x, m_pitchCenter - m_pitchRange, m_pitchCenter + m_pitchRange ); |
|
|
|
// Move toward target at rate or less |
|
float distX = UTIL_AngleDistance( angles.x, GetLocalAngles().x ); |
|
vecAngVel.x = distX * 10; |
|
vecAngVel.x = clamp( vecAngVel.x, -m_pitchRate, m_pitchRate ); |
|
SetLocalAngularVelocity( vecAngVel ); |
|
|
|
SetMoveDoneTime( 0.1 ); |
|
if ( m_pController ) |
|
return; |
|
|
|
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 ) |
|
{ |
|
float length = (m_maxRange > 0) ? m_maxRange : MAX_TRACE_LENGTH; |
|
UTIL_TraceLine( barrelEnd, barrelEnd + forward * length, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); |
|
|
|
if ( tr.m_pEnt == pTarget ) |
|
fire = TRUE; |
|
} |
|
else |
|
fire = TRUE; |
|
|
|
if ( fire ) |
|
{ |
|
if (m_fireLast == 0) |
|
{ |
|
m_OnAquireTarget.FireOutput(this, this); |
|
} |
|
FiringSequence( barrelEnd, forward, this ); |
|
} |
|
else |
|
{ |
|
if (m_fireLast !=0) |
|
{ |
|
m_OnLoseTarget.FireOutput(this, this); |
|
} |
|
m_fireLast = 0; |
|
} |
|
} |
|
else |
|
{ |
|
if (m_fireLast !=0) |
|
{ |
|
m_OnLoseTarget.FireOutput(this, this); |
|
} |
|
m_fireLast = 0; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// 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 ) |
|
{ |
|
Fire( bulletCount, barrelEnd, forward, pAttacker ); |
|
m_fireLast = gpGlobals->curtime; |
|
} |
|
} |
|
else |
|
{ |
|
m_fireLast = gpGlobals->curtime; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Fire targets and spawn sprites. |
|
// Input : bulletCount - |
|
// barrelEnd - |
|
// forward - |
|
// pAttacker - |
|
//----------------------------------------------------------------------------- |
|
void CFuncTank::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker ) |
|
{ |
|
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( 60 ); |
|
pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); |
|
pSprite->SetScale( m_spriteScale ); |
|
} |
|
|
|
m_OnFire.FireOutput(this, this); |
|
} |
|
|
|
|
|
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.85f; |
|
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; |
|
} |
|
|
|
// ############################################################################# |
|
// CFuncTankGun |
|
// ############################################################################# |
|
class CFuncTankGun : public CFuncTank |
|
{ |
|
public: |
|
DECLARE_CLASS( CFuncTankGun, CFuncTank ); |
|
|
|
void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker ); |
|
}; |
|
LINK_ENTITY_TO_CLASS( func_tank, CFuncTankGun ); |
|
|
|
void CFuncTankGun::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker ) |
|
{ |
|
int i; |
|
|
|
for ( i = 0; i < bulletCount; i++ ) |
|
{ |
|
switch( m_bulletType ) |
|
{ |
|
case TANK_BULLET_SMALL: |
|
FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], MAX_TRACE_LENGTH, m_iSmallAmmoType, 1, -1, -1, m_iBulletDamage, pAttacker ); |
|
break; |
|
|
|
case TANK_BULLET_MEDIUM: |
|
FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], MAX_TRACE_LENGTH, m_iMediumAmmoType, 1, -1, -1, m_iBulletDamage, pAttacker ); |
|
break; |
|
|
|
case TANK_BULLET_LARGE: |
|
FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], MAX_TRACE_LENGTH, m_iLargeAmmoType, 1, -1, -1, m_iBulletDamage, pAttacker ); |
|
break; |
|
default: |
|
case TANK_BULLET_NONE: |
|
break; |
|
} |
|
} |
|
CFuncTank::Fire( bulletCount, barrelEnd, forward, pAttacker ); |
|
} |
|
|
|
// ############################################################################# |
|
// 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 ); |
|
|
|
float m_flPulseSpeed; |
|
float m_flPulseWidth; |
|
color32 m_flPulseColor; |
|
float m_flPulseLife; |
|
float m_flPulseLag; |
|
string_t m_sPulseFireSound; |
|
}; |
|
LINK_ENTITY_TO_CLASS( func_tankpulselaser, CFuncTankPulseLaser ); |
|
|
|
BEGIN_DATADESC( CFuncTankPulseLaser ) |
|
|
|
DEFINE_KEYFIELD( m_flPulseSpeed, FIELD_FLOAT, "PulseSpeed" ), |
|
DEFINE_KEYFIELD( m_flPulseWidth, FIELD_FLOAT, "PulseWidth" ), |
|
DEFINE_KEYFIELD( m_flPulseColor, FIELD_COLOR32, "PulseColor" ), |
|
DEFINE_KEYFIELD( m_flPulseLife, FIELD_FLOAT, "PulseLife" ), |
|
DEFINE_KEYFIELD( m_flPulseLag, FIELD_FLOAT, "PulseLag" ), |
|
DEFINE_KEYFIELD( m_sPulseFireSound, FIELD_SOUNDNAME, "PulseFireSound" ), |
|
|
|
END_DATADESC() |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
void CFuncTankPulseLaser::Precache(void) |
|
{ |
|
UTIL_PrecacheOther( "grenade_beam" ); |
|
|
|
if ( m_sPulseFireSound != NULL_STRING ) |
|
{ |
|
PrecacheScriptSound( STRING(m_sPulseFireSound) ); |
|
} |
|
BaseClass::Precache(); |
|
} |
|
//------------------------------------------------------------------------------ |
|
// Purpose : |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
void CFuncTankPulseLaser::Fire( int bulletCount, const Vector &barrelEnd, const Vector &vecForward, CBaseEntity *pAttacker ) |
|
{ |
|
// -------------------------------------------------- |
|
// 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 = ATTN_TO_SNDLVL( 0.6 ); |
|
|
|
EmitSound( filter, entindex(), ep ); |
|
} |
|
|
|
} |
|
CFuncTank::Fire( bulletCount, barrelEnd, vecForward, pAttacker ); |
|
} |
|
|
|
// ############################################################################# |
|
// 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 ); |
|
void Think( void ); |
|
CEnvLaser *GetLaser( void ); |
|
void UpdateOnRemove( 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(); |
|
} |
|
} |
|
|
|
void CFuncTankLaser::UpdateOnRemove( void ) |
|
{ |
|
if( GetLaser() ) |
|
{ |
|
m_pLaser->TurnOff(); |
|
} |
|
|
|
BaseClass::UpdateOnRemove(); |
|
} |
|
|
|
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 ) |
|
{ |
|
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 ); |
|
} |
|
} |
|
|
|
class CFuncTankRocket : public CFuncTank |
|
{ |
|
public: |
|
DECLARE_CLASS( CFuncTankRocket, CFuncTank ); |
|
|
|
void Precache( void ); |
|
void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker ); |
|
}; |
|
LINK_ENTITY_TO_CLASS( func_tankrocket, CFuncTankRocket ); |
|
|
|
void CFuncTankRocket::Precache( void ) |
|
{ |
|
UTIL_PrecacheOther( "rpg_rocket" ); |
|
CFuncTank::Precache(); |
|
} |
|
|
|
|
|
|
|
void CFuncTankRocket::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker ) |
|
{ |
|
int i; |
|
|
|
for ( i = 0; i < bulletCount; i++ ) |
|
{ |
|
CBaseEntity *pRocket = CBaseEntity::Create( "rpg_rocket", barrelEnd, GetAbsAngles(), this ); |
|
pRocket->SetAbsAngles( GetAbsAngles() ); |
|
} |
|
CFuncTank::Fire( bulletCount, barrelEnd, forward, this ); |
|
} |
|
|
|
|
|
class CFuncTankMortar : public CFuncTank |
|
{ |
|
public: |
|
DECLARE_CLASS( CFuncTankMortar, CFuncTank ); |
|
|
|
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 ); |
|
void ShootGun(void); |
|
|
|
// Input handlers. |
|
void InputShootGun( inputdata_t &inputdata ); |
|
|
|
DECLARE_DATADESC(); |
|
|
|
int m_Magnitude; |
|
float m_fireDelay; |
|
string_t m_fireStartSound; |
|
string_t m_fireEndSound; |
|
|
|
// 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_FIELD( m_pAttacker, FIELD_CLASSPTR ), |
|
|
|
// Inputs |
|
DEFINE_INPUTFUNC( FIELD_VOID, "ShootGun", InputShootGun ), |
|
|
|
END_DATADESC() |
|
|
|
|
|
|
|
void CFuncTankMortar::Precache( void ) |
|
{ |
|
if ( m_fireStartSound != NULL_STRING ) |
|
PrecacheScriptSound( STRING(m_fireStartSound) ); |
|
if ( m_fireEndSound != NULL_STRING ) |
|
PrecacheScriptSound( STRING(m_fireEndSound) ); |
|
BaseClass::Precache(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Input handler to make the tank shoot. |
|
//----------------------------------------------------------------------------- |
|
void CFuncTankMortar::InputShootGun( inputdata_t &inputdata ) |
|
{ |
|
ShootGun(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFuncTankMortar::ShootGun( void ) |
|
{ |
|
Vector forward; |
|
AngleVectors( GetLocalAngles(), &forward ); |
|
UpdateMatrix(); |
|
forward = m_parentMatrix.ApplyRotation( forward ); |
|
|
|
// use cached firing state |
|
Fire( 1, WorldBarrelPosition(), forward, m_pAttacker ); |
|
} |
|
|
|
|
|
void CFuncTankMortar::FiringSequence( const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker ) |
|
{ |
|
if ( m_fireLast != 0 ) |
|
{ |
|
int bulletCount = (gpGlobals->curtime - m_fireLast) * m_fireRate; |
|
// Only create 1 explosion |
|
if ( bulletCount > 0 ) |
|
{ |
|
// fire in "firedelay" seconds |
|
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_NORM; |
|
|
|
EmitSound( filter, entindex(), ep ); |
|
} |
|
|
|
if ( m_fireDelay != 0 ) |
|
{ |
|
g_EventQueue.AddEvent( this, "ShootGun", m_fireDelay, pAttacker, this, 0 ); |
|
} |
|
else |
|
{ |
|
ShootGun(); |
|
} |
|
m_fireLast = gpGlobals->curtime; |
|
} |
|
} |
|
else |
|
{ |
|
m_fireLast = gpGlobals->curtime; |
|
} |
|
} |
|
|
|
void CFuncTankMortar::Fire( int bulletCount, const Vector &barrelEnd, const Vector &vecForward, CBaseEntity *pAttacker ) |
|
{ |
|
if ( m_fireEndSound != NULL_STRING ) |
|
{ |
|
CPASAttenuationFilter filter( this ); |
|
|
|
EmitSound_t ep; |
|
ep.m_nChannel = CHAN_WEAPON; |
|
ep.m_pSoundName = STRING(m_fireEndSound); |
|
ep.m_flVolume = 1.0f; |
|
ep.m_SoundLevel = SNDLVL_NORM; |
|
|
|
EmitSound( filter, entindex(), ep ); |
|
} |
|
|
|
trace_t tr; |
|
|
|
TankTrace( barrelEnd, vecForward, gTankSpread[m_spread], tr ); |
|
|
|
const CBaseEntity * ent = NULL; |
|
if ( g_pGameRules->IsMultiplayer() ) |
|
{ |
|
// temp remove suppress host |
|
ent = te->GetSuppressHost(); |
|
te->SetSuppressHost( NULL ); |
|
} |
|
|
|
ExplosionCreate( tr.endpos, GetAbsAngles(), this, m_Magnitude, 0, true ); |
|
|
|
if ( g_pGameRules->IsMultiplayer() ) |
|
{ |
|
te->SetSuppressHost( (CBaseEntity *) ent ); |
|
} |
|
|
|
BaseClass::Fire( bulletCount, barrelEnd, vecForward, this ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Func tank that fires physics cannisters placed on it |
|
//----------------------------------------------------------------------------- |
|
class CFuncTankPhysCannister : public CFuncTank |
|
{ |
|
public: |
|
DECLARE_CLASS( CFuncTankPhysCannister, CFuncTank ); |
|
DECLARE_DATADESC(); |
|
|
|
void Activate( void ); |
|
void Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker ); |
|
|
|
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::Activate( void ) |
|
{ |
|
BaseClass::Activate(); |
|
|
|
m_hBarrelVolume = NULL; |
|
|
|
// Find our barrel volume |
|
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()) ); |
|
UTIL_Remove( this ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CFuncTankPhysCannister::Fire( int bulletCount, const Vector &barrelEnd, const Vector &forward, CBaseEntity *pAttacker ) |
|
{ |
|
Assert( m_hBarrelVolume ); |
|
|
|
// 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 ); |
|
} |
|
|
|
|