Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
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.
 
 
 
 
 
 

639 lines
17 KiB

//========= 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 );
}
}