summaryrefslogtreecommitdiff
path: root/game/server/tf/player_vs_environment/tf_base_boss.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/tf/player_vs_environment/tf_base_boss.cpp')
-rw-r--r--game/server/tf/player_vs_environment/tf_base_boss.cpp639
1 files changed, 639 insertions, 0 deletions
diff --git a/game/server/tf/player_vs_environment/tf_base_boss.cpp b/game/server/tf/player_vs_environment/tf_base_boss.cpp
new file mode 100644
index 0000000..8e31cd1
--- /dev/null
+++ b/game/server/tf/player_vs_environment/tf_base_boss.cpp
@@ -0,0 +1,639 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+#include "cbase.h"
+
+#include "tf_gamerules.h"
+#include "tf_base_boss.h"
+#include "entity_currencypack.h"
+#include "tf_gamestats.h"
+#include "tf_player.h"
+
+LINK_ENTITY_TO_CLASS( base_boss, CTFBaseBoss );
+
+PRECACHE_REGISTER( base_boss );
+
+IMPLEMENT_SERVERCLASS_ST( CTFBaseBoss, DT_TFBaseBoss)
+ SendPropFloat( SENDINFO(m_lastHealthPercentage), 11, SPROP_NOSCALE, 0.0, 1.0 ),
+END_SEND_TABLE()
+
+BEGIN_DATADESC( CTFBaseBoss )
+
+ DEFINE_KEYFIELD( m_initialHealth, FIELD_INTEGER, "health" ),
+ DEFINE_KEYFIELD( m_modelString, FIELD_STRING, "model" ),
+ DEFINE_KEYFIELD( m_speed, FIELD_FLOAT, "speed" ),
+ DEFINE_KEYFIELD( m_startDisabled, FIELD_INTEGER, "start_disabled" ),
+
+ DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
+ DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeed", InputSetSpeed ),
+
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "SetMaxHealth", InputSetMaxHealth ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "AddHealth", InputAddHealth ),
+ DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveHealth", InputRemoveHealth ),
+
+ DEFINE_OUTPUT( m_outputOnHealthBelow90Percent, "OnHealthBelow90Percent" ),
+ DEFINE_OUTPUT( m_outputOnHealthBelow80Percent, "OnHealthBelow80Percent" ),
+ DEFINE_OUTPUT( m_outputOnHealthBelow70Percent, "OnHealthBelow70Percent" ),
+ DEFINE_OUTPUT( m_outputOnHealthBelow60Percent, "OnHealthBelow60Percent" ),
+ DEFINE_OUTPUT( m_outputOnHealthBelow50Percent, "OnHealthBelow50Percent" ),
+ DEFINE_OUTPUT( m_outputOnHealthBelow40Percent, "OnHealthBelow40Percent" ),
+ DEFINE_OUTPUT( m_outputOnHealthBelow30Percent, "OnHealthBelow30Percent" ),
+ DEFINE_OUTPUT( m_outputOnHealthBelow20Percent, "OnHealthBelow20Percent" ),
+ DEFINE_OUTPUT( m_outputOnHealthBelow10Percent, "OnHealthBelow10Percent" ),
+
+ DEFINE_OUTPUT( m_outputOnKilled, "OnKilled" ),
+
+ DEFINE_THINKFUNC( BossThink ),
+
+END_DATADESC()
+
+ConVar tf_base_boss_speed( "tf_base_boss_speed", "75", FCVAR_CHEAT );
+ConVar tf_base_boss_max_turn_rate( "tf_base_boss_max_turn_rate", "25", FCVAR_CHEAT );
+
+extern void HandleRageGain( CTFPlayer *pPlayer, unsigned int iRequiredBuffFlags, float flDamage, float fInverseRageGainScale );
+
+//--------------------------------------------------------------------------------------
+//--------------------------------------------------------------------------------------
+float CTFBaseBossLocomotion::GetRunSpeed( void ) const
+{
+ CTFBaseBoss *boss = (CTFBaseBoss *)GetBot()->GetEntity();
+ return boss->GetMaxSpeed();
+}
+
+
+//--------------------------------------------------------------------------------------
+void CTFBaseBossLocomotion::FaceTowards( const Vector &target )
+{
+ CTFBaseBoss *pTank = static_cast< CTFBaseBoss* >( GetBot()->GetEntity() );
+
+ const float deltaT = GetUpdateInterval();
+
+ QAngle angles = pTank->GetLocalAngles();
+
+ float desiredYaw = UTIL_VecToYaw( target - GetFeet() );
+
+ float angleDiff = UTIL_AngleDiff( desiredYaw, angles.y );
+
+ float deltaYaw = tf_base_boss_max_turn_rate.GetFloat() * deltaT;
+
+ if ( angleDiff < -deltaYaw )
+ {
+ angles.y -= deltaYaw;
+ }
+ else if ( angleDiff > deltaYaw )
+ {
+ angles.y += deltaYaw;
+ }
+ else
+ {
+ angles.y += angleDiff;
+ }
+
+ Vector forward, right;
+ pTank->GetVectors( NULL, &right, NULL );
+
+ forward = CrossProduct( GetGroundNormal(), right );
+
+ float desiredPitch = UTIL_VecToPitch( forward );
+
+ angleDiff = UTIL_AngleDiff( desiredPitch, angles.x );
+
+ float deltaPitch = tf_base_boss_max_turn_rate.GetFloat() * deltaT;
+
+ if ( angleDiff < -deltaPitch )
+ {
+ angles.x -= deltaPitch;
+ }
+ else if ( angleDiff > deltaPitch )
+ {
+ angles.x += deltaPitch;
+ }
+ else
+ {
+ angles.x += angleDiff;
+ }
+
+ pTank->SetLocalAngles( angles );
+ pTank->UpdateCollisionBounds();
+}
+
+
+//--------------------------------------------------------------------------------------
+//--------------------------------------------------------------------------------------
+CTFBaseBoss::CTFBaseBoss()
+{
+ m_modelString = NULL_STRING;
+ m_lastHealthPercentage = 1.0f;
+ m_speed = tf_base_boss_speed.GetFloat();
+ m_locomotor = new CTFBaseBossLocomotion( this );
+ m_currencyValue = TF_BASE_BOSS_CURRENCY;
+ m_initialHealth = 0;
+
+ m_bResolvePlayerCollisions = true;
+}
+
+
+//--------------------------------------------------------------------------------------
+CTFBaseBoss::~CTFBaseBoss()
+{
+ delete m_locomotor;
+}
+
+
+//--------------------------------------------------------------------------------------
+void CTFBaseBoss::Precache( void )
+{
+ if ( m_modelString != NULL_STRING )
+ {
+ PrecacheModel( STRING( m_modelString ) );
+ }
+
+ BaseClass::Precache();
+}
+
+
+//--------------------------------------------------------------------------------------
+void CTFBaseBoss::Spawn( void )
+{
+ Precache();
+
+ BaseClass::Spawn();
+
+ if ( m_modelString != NULL_STRING )
+ {
+ SetModel( STRING( m_modelString ) );
+ }
+
+ m_isEnabled = m_startDisabled ? false : true;
+
+ SetHealth( m_initialHealth );
+ SetMaxHealth( m_initialHealth );
+
+ if ( TFGameRules() )
+ {
+ TFGameRules()->AddActiveBoss( this );
+ }
+
+ m_lastHealthPercentage = 1.0f;
+ m_damagePoseParameter = -1;
+
+ SetThink( &CTFBaseBoss::BossThink );
+ SetNextThink( gpGlobals->curtime );
+}
+
+int CTFBaseBoss::UpdateTransmitState()
+{
+ // ALWAYS transmit to all clients.
+ return SetTransmitState( FL_EDICT_ALWAYS );
+}
+
+
+//--------------------------------------------------------------------------------------
+void CTFBaseBoss::ResolvePlayerCollision( CTFPlayer *player )
+{
+ Vector bossGlobalMins = WorldAlignMins() + GetAbsOrigin();
+ Vector bossGlobalMaxs = WorldAlignMaxs() + GetAbsOrigin();
+
+ Vector playerGlobalMins = player->WorldAlignMins() + player->GetAbsOrigin();
+ Vector playerGlobalMaxs = player->WorldAlignMaxs() + player->GetAbsOrigin();
+
+ Vector newPlayerPos = player->GetAbsOrigin();
+
+
+ if ( playerGlobalMins.x > bossGlobalMaxs.x ||
+ playerGlobalMaxs.x < bossGlobalMins.x ||
+ playerGlobalMins.y > bossGlobalMaxs.y ||
+ playerGlobalMaxs.y < bossGlobalMins.y ||
+ playerGlobalMins.z > bossGlobalMaxs.z ||
+ playerGlobalMaxs.z < bossGlobalMins.z )
+ {
+ // no overlap
+ return;
+ }
+
+ Vector toPlayer = player->WorldSpaceCenter() - WorldSpaceCenter();
+
+ Vector overlap;
+ float signX, signY, signZ;
+
+ if ( toPlayer.x >= 0 )
+ {
+ overlap.x = bossGlobalMaxs.x - playerGlobalMins.x;
+ signX = 1.0f;
+ }
+ else
+ {
+ overlap.x = playerGlobalMaxs.x - bossGlobalMins.x;
+ signX = -1.0f;
+ }
+
+ if ( toPlayer.y >= 0 )
+ {
+ overlap.y = bossGlobalMaxs.y - playerGlobalMins.y;
+ signY = 1.0f;
+ }
+ else
+ {
+ overlap.y = playerGlobalMaxs.y - bossGlobalMins.y;
+ signY = -1.0f;
+ }
+
+ if ( toPlayer.z >= 0 )
+ {
+ overlap.z = bossGlobalMaxs.z - playerGlobalMins.z;
+ signZ = 1.0f;
+ }
+ else
+ {
+ // don't push player underground
+ overlap.z = 99999.9f; // playerGlobalMaxs.z - bossGlobalMins.z;
+ signZ = -1.0f;
+ }
+
+ float bloat = 5.0f;
+
+ if ( overlap.x < overlap.y )
+ {
+ if ( overlap.x < overlap.z )
+ {
+ // X is least overlap
+ newPlayerPos.x += signX * ( overlap.x + bloat );
+ }
+ else
+ {
+ // Z is least overlap
+ newPlayerPos.z += signZ * ( overlap.z + bloat );
+ }
+ }
+ else if ( overlap.z < overlap.y )
+ {
+ // Z is least overlap
+ newPlayerPos.z += signZ * ( overlap.z + bloat );
+ }
+ else
+ {
+ // Y is least overlap
+ newPlayerPos.y += signY * ( overlap.y + bloat );
+ }
+
+ // check if new location is valid
+ trace_t result;
+ Ray_t ray;
+ ray.Init( newPlayerPos, newPlayerPos, player->WorldAlignMins(), player->WorldAlignMaxs() );
+ UTIL_TraceRay( ray, MASK_PLAYERSOLID, player, COLLISION_GROUP_PLAYER_MOVEMENT, &result );
+
+ if ( result.DidHit() )
+ {
+ // Trace down from above to find safe ground
+ ray.Init( newPlayerPos + Vector( 0.0f, 0.0f, 32.0f ), newPlayerPos, player->WorldAlignMins(), player->WorldAlignMaxs() );
+ UTIL_TraceRay( ray, MASK_PLAYERSOLID, player, COLLISION_GROUP_PLAYER_MOVEMENT, &result );
+
+ if ( result.startsolid )
+ {
+ // player was crushed against something
+ player->TakeDamage( CTakeDamageInfo( this, this, 99999.9f, DMG_CRUSH ) );
+ return;
+ }
+ else
+ {
+ // Use the trace end position
+ newPlayerPos = result.endpos;
+ }
+ }
+
+ player->SetAbsOrigin( newPlayerPos );
+}
+
+
+//--------------------------------------------------------------------------------------
+void CTFBaseBoss::Touch( CBaseEntity *pOther )
+{
+ BaseClass::Touch( pOther );
+
+ if ( pOther && pOther->IsBaseObject() )
+ {
+ // ran over an engineer building - destroy it
+ pOther->TakeDamage( CTakeDamageInfo( this, this, 99999.9f, DMG_CRUSH ) );
+ }
+}
+
+
+//--------------------------------------------------------------------------------------
+void CTFBaseBoss::BossThink( void )
+{
+ SetNextThink( gpGlobals->curtime );
+
+ if ( m_damagePoseParameter < 0 )
+ {
+ m_damagePoseParameter = LookupPoseParameter( "damage" );
+ }
+
+ if ( m_damagePoseParameter >= 0 )
+ {
+ // Avoid dividing by zero
+ if ( GetMaxHealth() )
+ {
+ SetPoseParameter( m_damagePoseParameter, 1.0f - ( (float)GetHealth() / (float)GetMaxHealth() ) );
+ }
+ else
+ {
+ SetPoseParameter( m_damagePoseParameter, 1.0f );
+ }
+ }
+
+ if ( !m_isEnabled )
+ {
+ return;
+ }
+
+ Update();
+
+ if ( m_bResolvePlayerCollisions )
+ {
+ CUtlVector< CTFPlayer * > playerVector;
+ CollectPlayers( &playerVector, TEAM_ANY, COLLECT_ONLY_LIVING_PLAYERS );
+ for( int i=0; i<playerVector.Count(); ++i )
+ {
+ ResolvePlayerCollision( playerVector[i] );
+ }
+ }
+}
+
+//--------------------------------------------------------------------------------------
+void CTFBaseBoss::Event_Killed( const CTakeDamageInfo &info )
+{
+ m_outputOnKilled.FireOutput( this, this );
+
+ // drop some loot!
+ m_currencyValue = GetCurrencyValue();
+
+ int nRemainingMoney = m_currencyValue;
+
+ QAngle angRand = vec3_angle;
+
+ while( nRemainingMoney > 0 )
+ {
+ int nAmount = 0;
+
+ if ( nRemainingMoney >= 100 )
+ {
+ nAmount = 25;
+ }
+ else if ( nRemainingMoney >= 40 )
+ {
+ nAmount = 10;
+ }
+ else if ( nRemainingMoney >= 5 )
+ {
+ nAmount = 5;
+ }
+ else
+ {
+ nAmount = nRemainingMoney;
+ }
+
+ nRemainingMoney -= nAmount;
+
+ angRand.y = RandomFloat( -180.0f, 180.0f );
+
+ CCurrencyPackCustom *pCurrencyPack = assert_cast< CCurrencyPackCustom* >( CBaseEntity::CreateNoSpawn( "item_currencypack_custom", WorldSpaceCenter(), angRand, this ) );
+
+ if ( pCurrencyPack )
+ {
+ pCurrencyPack->SetAmount( nAmount );
+
+ Vector vecImpulse = RandomVector( -1,1 );
+ vecImpulse.z = RandomFloat( 5.0f, 20.0f );
+ VectorNormalize( vecImpulse );
+ Vector vecVelocity = vecImpulse * 250.0 * RandomFloat( 1.0f, 4.0f );
+
+ DispatchSpawn( pCurrencyPack );
+ pCurrencyPack->DropSingleInstance( vecVelocity, this, 0, 0 );
+ }
+ }
+
+ BaseClass::Event_Killed( info );
+
+ UTIL_Remove( this );
+}
+
+void CTFBaseBoss::UpdateOnRemove()
+{
+ if ( TFGameRules() )
+ {
+ TFGameRules()->RemoveActiveBoss( this );
+ }
+
+ BaseClass::UpdateOnRemove();
+}
+
+int CTFBaseBoss::OnTakeDamage( const CTakeDamageInfo &rawInfo )
+{
+ CTakeDamageInfo info = rawInfo;
+
+ if ( TFGameRules() )
+ {
+ TFGameRules()->ApplyOnDamageModifyRules( info, this, true );
+ }
+
+ // On damage Rage
+ // Give the soldier/pyro some rage points for dealing/taking damage.
+ if ( info.GetDamage() && info.GetAttacker() != this )
+ {
+ CTFPlayer *pAttacker = ToTFPlayer( info.GetAttacker() );
+
+ // Buff flag 1: we get rage when we deal damage. Here, that means the soldier that attacked
+ // gets rage when we take damage.
+ HandleRageGain( pAttacker, kRageBuffFlag_OnDamageDealt, info.GetDamage(), 6.0f );
+
+ // Buff 5: our pyro attacker get rage when we're damaged by fire
+ if ( ( info.GetDamageType() & DMG_BURN ) != 0 || ( info.GetDamageType() & DMG_PLASMA ) != 0 )
+ {
+ HandleRageGain( pAttacker, kRageBuffFlag_OnBurnDamageDealt, info.GetDamage(), 30.f );
+ }
+
+ if ( pAttacker && info.GetWeapon() )
+ {
+ CTFWeaponBase *pWeapon = dynamic_cast<CTFWeaponBase *>( info.GetWeapon() );
+ if ( pWeapon )
+ {
+ pWeapon->ApplyOnHitAttributes( this, pAttacker, info );
+ }
+ }
+ }
+
+ return BaseClass::OnTakeDamage( info );
+}
+
+//--------------------------------------------------------------------------------------
+int CTFBaseBoss::OnTakeDamage_Alive( const CTakeDamageInfo &rawInfo )
+{
+ if ( !rawInfo.GetAttacker() || rawInfo.GetAttacker()->GetTeamNumber() == GetTeamNumber() )
+ {
+ // no friendly fire damage
+ return 0;
+ }
+
+ CTakeDamageInfo info = rawInfo;
+
+ // weapon-specific damage modification
+ ModifyDamage( &info );
+
+ if ( TFGameRules() )
+ {
+ CTFGameRules::DamageModifyExtras_t outParams;
+ info.SetDamage( TFGameRules()->ApplyOnDamageAliveModifyRules( info, this, outParams ) );
+ }
+
+ // fire event for client combat text, beep, etc.
+ IGameEvent *event = gameeventmanager->CreateEvent( "npc_hurt" );
+ if ( event )
+ {
+
+ event->SetInt( "entindex", entindex() );
+ event->SetInt( "health", MAX( 0, GetHealth() ) );
+ event->SetInt( "damageamount", info.GetDamage() );
+ event->SetBool( "crit", ( info.GetDamageType() & DMG_CRITICAL ) ? true : false );
+
+ CTFPlayer *attackerPlayer = ToTFPlayer( info.GetAttacker() );
+ if ( attackerPlayer )
+ {
+ event->SetInt( "attacker_player", attackerPlayer->GetUserID() );
+
+ if ( attackerPlayer->GetActiveTFWeapon() )
+ {
+ event->SetInt( "weaponid", attackerPlayer->GetActiveTFWeapon()->GetWeaponID() );
+ }
+ else
+ {
+ event->SetInt( "weaponid", 0 );
+ }
+ }
+ else
+ {
+ // hurt by world
+ event->SetInt( "attacker_player", 0 );
+ event->SetInt( "weaponid", 0 );
+ }
+
+ gameeventmanager->FireEvent( event );
+ }
+
+ int result = BaseClass::OnTakeDamage_Alive( info );
+
+ // emit injury outputs
+ float healthPercentage = (float)GetHealth() / (float)GetMaxHealth();
+
+ if ( m_lastHealthPercentage > 0.9f && healthPercentage < 0.9f )
+ {
+ m_outputOnHealthBelow90Percent.FireOutput( this, this );
+ }
+ else if ( m_lastHealthPercentage > 0.8f && healthPercentage < 0.8f )
+ {
+ m_outputOnHealthBelow80Percent.FireOutput( this, this );
+ }
+ else if ( m_lastHealthPercentage > 0.7f && healthPercentage < 0.7f )
+ {
+ m_outputOnHealthBelow70Percent.FireOutput( this, this );
+ }
+ else if ( m_lastHealthPercentage > 0.6f && healthPercentage < 0.6f )
+ {
+ m_outputOnHealthBelow60Percent.FireOutput( this, this );
+ }
+ else if ( m_lastHealthPercentage > 0.5f && healthPercentage < 0.5f )
+ {
+ m_outputOnHealthBelow50Percent.FireOutput( this, this );
+ }
+ else if ( m_lastHealthPercentage > 0.4f && healthPercentage < 0.4f )
+ {
+ m_outputOnHealthBelow40Percent.FireOutput( this, this );
+ }
+ else if ( m_lastHealthPercentage > 0.3f && healthPercentage < 0.3f )
+ {
+ m_outputOnHealthBelow30Percent.FireOutput( this, this );
+ }
+ else if ( m_lastHealthPercentage > 0.2f && healthPercentage < 0.2f )
+ {
+ m_outputOnHealthBelow20Percent.FireOutput( this, this );
+ }
+ else if ( m_lastHealthPercentage > 0.1f && healthPercentage < 0.1f )
+ {
+ m_outputOnHealthBelow10Percent.FireOutput( this, this );
+ }
+
+ m_lastHealthPercentage = healthPercentage;
+
+ // Let attacker react to the damage they dealt
+ CTFPlayer *pAttacker = ToTFPlayer( rawInfo.GetAttacker() );
+ if ( pAttacker )
+ {
+ pAttacker->OnDealtDamage( this, info );
+
+ CTF_GameStats.Event_BossDamage( pAttacker, info.GetDamage() );
+ }
+
+ return result;
+}
+
+
+//--------------------------------------------------------------------------------------
+void CTFBaseBoss::InputEnable( inputdata_t &inputdata )
+{
+ m_isEnabled = true;
+}
+
+
+//--------------------------------------------------------------------------------------
+void CTFBaseBoss::InputDisable( inputdata_t &inputdata )
+{
+ m_isEnabled = false;
+}
+
+
+//------------------------------------------------------------------------------
+void CTFBaseBoss::InputSetSpeed( inputdata_t &inputdata )
+{
+ m_speed = inputdata.value.Float();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the health of the boss
+//-----------------------------------------------------------------------------
+void CTFBaseBoss::InputSetHealth( inputdata_t &inputdata )
+{
+ m_iHealth = inputdata.value.Int();
+ SetHealth( m_iHealth );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Set the max health of the boss
+//-----------------------------------------------------------------------------
+void CTFBaseBoss::InputSetMaxHealth( inputdata_t &inputdata )
+{
+ m_iMaxHealth = inputdata.value.Int();
+ SetMaxHealth( m_iMaxHealth );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Add health to the boss
+//-----------------------------------------------------------------------------
+void CTFBaseBoss::InputAddHealth( inputdata_t &inputdata )
+{
+ int iHealth = inputdata.value.Int();
+ SetHealth( MIN( GetMaxHealth(), GetHealth() + iHealth ) );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove health from the boss
+//-----------------------------------------------------------------------------
+void CTFBaseBoss::InputRemoveHealth( inputdata_t &inputdata )
+{
+ int iDamage = inputdata.value.Int();
+
+ SetHealth( GetHealth() - iDamage );
+ if ( GetHealth() <= 0 )
+ {
+ CTakeDamageInfo info( inputdata.pCaller, inputdata.pActivator, vec3_origin, GetAbsOrigin(), iDamage, DMG_GENERIC, TF_DMG_CUSTOM_NONE );
+ Event_Killed( info );
+ }
+}