diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/tf/halloween/merasmus | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/server/tf/halloween/merasmus')
28 files changed, 4558 insertions, 0 deletions
diff --git a/game/server/tf/halloween/merasmus/merasmus.cpp b/game/server/tf/halloween/merasmus/merasmus.cpp new file mode 100644 index 0000000..93f42d2 --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus.cpp @@ -0,0 +1,1609 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#include "cbase.h" +#include "tf_player.h" +#include "tf_gamerules.h" +#include "tf_team.h" +#include "nav_mesh/tf_nav_area.h" +#include "NextBot/Path/NextBotChasePath.h" +#include "econ_wearable.h" +#include "team_control_point_master.h" +#include "particle_parse.h" +#include "tf_weaponbase_merasmus_grenade.h" +#include "merasmus_dancer.h" +#include "tf_wheel_of_doom.h" +#include "soundenvelope.h" +#include "util.h" +#include "tf_obj_sentrygun.h" +#include "logicrelay.h" +#include "steamworks_gamestats.h" + +#include "tf/halloween/ghost/ghost.h" + +#include "player_vs_environment/monster_resource.h" + +#include "merasmus_trick_or_treat_prop.h" +#include "merasmus.h" +#include "merasmus_behavior/merasmus_disguise.h" +#include "merasmus_behavior/merasmus_dying.h" +#include "merasmus_behavior/merasmus_reveal.h" +#include "merasmus_behavior/merasmus_teleport.h" + +#include "rtime.h" +#include "gc_clientsystem.h" +#include "tf_gcmessages.h" + +#include "tf_fx.h" + +ConVar tf_merasmus_health_base( "tf_merasmus_health_base", "33750", FCVAR_CHEAT ); +ConVar tf_merasmus_health_per_player( "tf_merasmus_health_per_player", "2500", FCVAR_CHEAT ); +ConVar tf_merasmus_min_player_count( "tf_merasmus_min_player_count", "10", FCVAR_CHEAT ); + +ConVar tf_merasmus_lifetime( "tf_merasmus_lifetime", "120", FCVAR_CHEAT ); + +ConVar tf_merasmus_speed( "tf_merasmus_speed", "600", FCVAR_CHEAT ); +ConVar tf_merasmus_speed_recovery_rate( "tf_merasmus_speed_recovery_rate", "100", FCVAR_CHEAT, "Movement units/second" ); +ConVar tf_merasmus_chase_duration( "tf_merasmus_chase_duration", "7", FCVAR_CHEAT ); +ConVar tf_merasmus_chase_range( "tf_merasmus_chase_range", "2000", FCVAR_CHEAT ); + +ConVar tf_merasmus_should_disguise_threshold( "tf_merasmus_should_disguise_threshold", "0.45f", FCVAR_CHEAT ); +ConVar tf_merasmus_min_props_to_reveal( "tf_merasmus_min_props_to_reveal", "0.7f", FCVAR_CHEAT, "Percentage of total fake props players have to destroy before Merasmus reveals himself"); + +ConVar tf_merasmus_attack_range( "tf_merasmus_attack_range", "200", FCVAR_CHEAT ); + +ConVar tf_merasmus_health_regen_rate( "tf_merasmus_health_regen_rate", "0.001f", FCVAR_CHEAT, "Percentage of Max HP per sec that Merasmus will regenerate while in disguise" ); + +ConVar tf_merasmus_bomb_head_duration( "tf_merasmus_bomb_head_duration", "15.f", FCVAR_CHEAT ); +ConVar tf_merasmus_bomb_head_per_team( "tf_merasmus_bomb_head_per_team", "1", FCVAR_CHEAT ); + +ConVar tf_merasmus_stun_duration( "tf_merasmus_stun_duration", "2.f", FCVAR_CHEAT ); + +extern ConVar tf_merasmus_spawn_interval; +extern ConVar tf_merasmus_spawn_interval_variation; + +#define MERASMUS_MODEL_NAME "models/bots/merasmus/merasmus.mdl" +#define MERASMUS_BOMB_MODEL "models/props_lakeside_event/bomb_temp.mdl" + +static const char* s_pszDisguiseProps[] = +{ + // "models/props_hydro/dumptruck.mdl", // 265 + "models/props_halloween/pumpkin_02.mdl", + "models/props_halloween/pumpkin_03.mdl", + "models/egypt/palm_tree/palm_tree.mdl", + "models/props_spytech/control_room_console01.mdl", + "models/props_spytech/work_table001.mdl", + // "models/egypt/tent/tent.mdl", // 248 + "models/props_coalmines/boulder1.mdl", + "models/props_coalmines/boulder2.mdl", + "models/props_farm/concrete_block001.mdl", // 152 + // "models/props_farm/tractor_tire001.mdl", // requires offset + "models/props_farm/welding_machine01.mdl", + "models/props_medieval/medieval_resupply.mdl", + "models/props_medieval/target/target.mdl", + "models/props_swamp/picnic_table.mdl", + "models/props_manor/baby_grand_01.mdl", // 154 + "models/props_manor/bookcase_132_02.mdl", + "models/props_manor/chair_01.mdl", + "models/props_manor/couch_01.mdl", + "models/props_manor/grandfather_clock_01.mdl", + // "models/props_manor/tractor_01.mdl", // 227 + // "models/props_gameplay/haybale.mdl", // requires offset + "models/props_viaduct_event/coffin_simple_closed.mdl", + // "models/props_farm/wooden_barrel.mdl", // requires offset + "models/props_2fort/miningcrate001.mdl", + "models/props_gameplay/resupply_locker.mdl", + "models/props_2fort/oildrum.mdl", + // "models/props_farm/wood_pile.mdl", // requires offset + "models/props_lakeside/wood_crate_01.mdl", + // "models/props_farm/pallet001.mdl", // requires offset + "models/props_well/hand_truck01.mdl", + "models/props_vehicles/mining_car_metal.mdl", + "models/props_2fort/tire002.mdl", + "models/props_well/computer_cart01.mdl", + "models/egypt/palm_tree/palm_tree.mdl" +}; + + +//----------------------------------------------------------------------------------------------------- +// The Horseless Headless Horseman +//----------------------------------------------------------------------------------------------------- +LINK_ENTITY_TO_CLASS( merasmus, CMerasmus ); + +IMPLEMENT_SERVERCLASS_ST( CMerasmus, DT_Merasmus ) + SendPropBool( SENDINFO( m_bRevealed ) ), + SendPropBool( SENDINFO( m_bIsDoingAOEAttack ) ), + SendPropBool( SENDINFO( m_bStunned ) ), +END_SEND_TABLE() + + +int CMerasmus::m_level = 1; + +IMPLEMENT_AUTO_LIST( IMerasmusAutoList ); + +//----------------------------------------------------------------------------------------------------- +CMerasmus::CMerasmus() +{ + m_intention = new CMerasmusIntention( this ); + m_locomotor = new CMerasmusLocomotion( this ); + m_flyingLocomotor = new CMerasmusFlyingLocomotion( this ); + m_body = new CMerasmusBody( this ); + m_bRevealed = false; + m_bIsDoingAOEAttack = false; + + m_wheel = NULL; + + m_stunTimer.Invalidate(); + m_bStunned = false; + + m_nBombHitCount = 0; + + m_hMerasmusRevealer = NULL; + + m_isFlying = false; + m_isHiding=false; + + m_hHealthBar = g_pMonsterResource; + ListenForGameEvent( "player_death" ); +} + + +//----------------------------------------------------------------------------------------------------- +CMerasmus::~CMerasmus() +{ + if ( m_intention ) + delete m_intention; + + if ( m_locomotor ) + delete m_locomotor; + + if ( m_flyingLocomotor ) + delete m_flyingLocomotor; + + if ( m_body ) + delete m_body; + + // Make sure the health meter goes away + if( m_hHealthBar.Get() ) + { + m_hHealthBar->HideBossHealthMeter(); + } + + IGameEvent *event = gameeventmanager->CreateEvent( "recalculate_truce" ); + if ( event ) + { + gameeventmanager->FireEvent( event, true ); + } +} + +//----------------------------------------------------------------------------------------------------- +void CMerasmus::Precache() +{ + BaseClass::Precache(); + + // always allow late precaching, so we don't pay the cost of the + // Halloween Boss for the entire year + + bool bAllowPrecache = CBaseEntity::IsPrecacheAllowed(); + CBaseEntity::SetAllowPrecache( true ); + + PrecacheMerasmus(); + + CBaseEntity::SetAllowPrecache( bAllowPrecache ); +} + + +void CMerasmus::PrecacheMerasmus() +{ + int model = PrecacheModel( MERASMUS_MODEL_NAME ); + PrecacheGibsForModel( model ); + + // precache disguise prop list + for ( int i=0; i<ARRAYSIZE( s_pszDisguiseProps ); ++i ) + { + PrecacheModel( s_pszDisguiseProps[i] ); + } + + PrecacheModel( MERASMUS_BOMB_MODEL ); + PrecacheModel( "models/props_lakeside_event/bomb_temp_hat.mdl" ); // bomb head on player + PrecacheModel( "models/props_halloween/bombonomicon.mdl" ); // bombonomicon hint to player + + // Boss VOs + PrecacheScriptSound( "Halloween.MerasmusAppears" ); + PrecacheScriptSound( "Halloween.MerasmusBanish" ); + //PrecacheScriptSound( "Halloween.MerasmusCastBleedingSpell" ); + PrecacheScriptSound( "Halloween.MerasmusCastFireSpell" ); + PrecacheScriptSound( "Halloween.MerasmusCastJarateSpell" ); + PrecacheScriptSound( "Halloween.MerasmusCastJarateSpellRare" ); + PrecacheScriptSound( "Halloween.MerasmusLaunchSpell" ); + PrecacheScriptSound( "Halloween.MerasmusControlPoint" ); + PrecacheScriptSound( "Halloween.MerasmusDepart" ); + PrecacheScriptSound( "Halloween.MerasmusDepartRare" ); + PrecacheScriptSound( "Halloween.MerasmusDiscovered" ); + PrecacheScriptSound( "Halloween.MerasmusGrenadeThrow" ); + PrecacheScriptSound( "Halloween.MerasmusHidden" ); + PrecacheScriptSound( "Halloween.MerasmusHiddenRare" ); + PrecacheScriptSound( "Halloween.MerasmusHitByBomb" ); + PrecacheScriptSound( "Halloween.MerasmusHitByBombRare" ); + PrecacheScriptSound( "Halloween.MerasmusInitiateHiding" ); + PrecacheScriptSound( "Halloween.MerasmusStaffAttack" ); + PrecacheScriptSound( "Halloween.MerasmusStaffAttackRare" ); + PrecacheScriptSound( "Halloween.MerasmusTauntFakeProp" ); + + //PrecacheScriptSound( "Halloween.MerasmusTeleport" ); + + // Boss event sound effects + PrecacheScriptSound( "Halloween.MerasmusBossSpawn" ); + PrecacheScriptSound( "Halloween.Merasmus_Death" ); + PrecacheScriptSound( "Halloween.EyeballBossEscapeSoon" ); + PrecacheScriptSound( "Halloween.EyeballBossEscapeImminent" ); + PrecacheScriptSound( "Halloween.EyeballBossEscaped" ); + PrecacheScriptSound( "Halloween.Merasmus_Float" ); + PrecacheScriptSound( "Halloween.Merasmus_Stun" ); + PrecacheScriptSound( "Halloween.Merasmus_Spell" ); + PrecacheScriptSound( "Halloween.Merasmus_Hiding_Explode" ); + + PrecacheParticleSystem( "merasmus_spawn" ); // spawn + PrecacheParticleSystem( "merasmus_tp" ); // puff effect + PrecacheParticleSystem( "merasmus_blood" ); // when he takes damage + PrecacheParticleSystem( "merasmus_blood_bits" ); // when he takes damage while stunned + PrecacheParticleSystem( "merasmus_ambient_body" ); // glow around the body + PrecacheParticleSystem( "merasmus_shoot" ); // when he casts spell + PrecacheParticleSystem( "merasmus_book_attack" ); // big attack + PrecacheParticleSystem( "merasmus_object_spawn" ); // object spawn + PrecacheParticleSystem( "merasmus_zap" ); // zap! + PrecacheParticleSystem( "merasmus_dazed" ); // stunned + PrecacheParticleSystem( "merasmus_dazed_explosion" ); // bomb head explode + + // TEMP + PrecacheScriptSound( "Halloween.HeadlessBossAxeHitFlesh" ); +} + + +//----------------------------------------------------------------------------------------------------- +void CMerasmus::Spawn( void ) +{ + Precache(); + + SetModel( MERASMUS_MODEL_NAME ); + + BaseClass::Spawn(); + + PlayHighPrioritySound("Halloween.MerasmusAppears"); + + m_isHiding=false; + + // scale the boss' health with the player count + int totalPlayers = GetGlobalTFTeam( TF_TEAM_BLUE )->GetNumPlayers() + GetGlobalTFTeam( TF_TEAM_RED )->GetNumPlayers(); + + int health = tf_merasmus_health_base.GetInt(); + if ( totalPlayers > tf_merasmus_min_player_count.GetInt() ) + { + health += ( totalPlayers - tf_merasmus_min_player_count.GetInt() ) * tf_merasmus_health_per_player.GetInt(); + } + + CBaseEntity *pWheel = gEntList.FindEntityByName( NULL, "wheel_of_fortress" ); + if ( pWheel ) + { + m_wheel = assert_cast< CWheelOfDoom* >( pWheel ); + } + + SetHealth( health ); + SetMaxHealth( health ); + + m_homePos = GetAbsOrigin(); + + m_damagePoseParameter = -1; + + SetBloodColor( DONT_BLEED ); + + if ( m_pIdleSound ) + { + CSoundEnvelopeController::GetController().SoundDestroy( m_pIdleSound ); + m_pIdleSound = NULL; + } + + // Collect all of the players that are alive at this moment. These are the players who will get + // their hat leveled up when Merasmus dies. We only want to give credit to these players to discourage + // the strategy of spawning Merasmus with 10 people so his health is scaled for 10 people, + // then have 22 other people connect and destroy him. + CUtlVector< CTFPlayer * > playerVector; + CollectPlayers( &playerVector ); + FOR_EACH_VEC( playerVector, i ) + { + m_startingAttackersVector.AddToTail( playerVector[i] ); + } + + CPVSFilter filter( GetAbsOrigin() ); + CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); + m_pIdleSound = controller.SoundCreate( filter, entindex(), "Halloween.Merasmus_Float" ); + controller.Play( m_pIdleSound, 1.0, 100 ); + + m_solidType = GetSolid(); + m_solidFlags = GetSolidFlags(); + + const float flLifeTime = tf_merasmus_lifetime.GetFloat(); + m_lifeTimer.Start( flLifeTime ); + m_flLastWarnTime = 0; + + IGameEvent *event = gameeventmanager->CreateEvent( "merasmus_summoned" ); + if ( event ) + { + event->SetInt( "level", GetLevel() ); + gameeventmanager->FireEvent( event ); + } + TriggerLogicRelay( "boss_enter_relay", true ); + + DispatchParticleEffect( "merasmus_spawn", GetAbsOrigin(), GetAbsAngles() ); + + m_bossStats.ResetStats(); + + event = gameeventmanager->CreateEvent( "recalculate_truce" ); + if ( event ) + { + gameeventmanager->FireEvent( event, true ); + } +} + + +//--------------------------------------------------------------------------------------------- +void RemoveAllBombHeadFromPlayers() +{ + CUtlVector< CTFPlayer * > playerVector; + CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS ); + CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS ); + + for ( int i=0; i<playerVector.Count(); ++i ) + { + if ( playerVector[i]->m_Shared.InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) ) + { + playerVector[i]->m_Shared.RemoveCond( TF_COND_HALLOWEEN_BOMB_HEAD ); + } + } +} + + +void CMerasmus::UpdateOnRemove() +{ + Assert( TFGameRules() ); + if ( m_hHealthBar ) + { + m_hHealthBar->HideBossHealthMeter(); + } + + if ( m_pIdleSound ) + { + CSoundEnvelopeController::GetController().SoundDestroy( m_pIdleSound ); + m_pIdleSound = NULL; + } + + // the boss is NULL, remove bomb head condition won't give players power play + RemoveAllBombHeadFromPlayers(); + + RemoveAllFakeProps(); + + BaseClass::UpdateOnRemove(); + + // Report Stats + SW_ReportMerasmusStats(); +} + + +//--------------------------------------------------------------------------------------------- +float MerasmusModifyDamage( const CTakeDamageInfo &info ) +{ + CTFWeaponBase *pWeapon = dynamic_cast< CTFWeaponBase * >( info.GetWeapon() ); + CObjectSentrygun *sentry = dynamic_cast< CObjectSentrygun * >( info.GetInflictor() ); + CTFProjectile_SentryRocket *sentryRocket = dynamic_cast< CTFProjectile_SentryRocket * >( info.GetInflictor() ); + + if ( sentry || sentryRocket ) + { + return info.GetDamage() * 0.5f; + } + else if ( pWeapon ) + { + switch( pWeapon->GetWeaponID() ) + { + case TF_WEAPON_MINIGUN: + return info.GetDamage() * 0.5f; + + case TF_WEAPON_SODA_POPPER: + return info.GetDamage() * 1.5f; + + case TF_WEAPON_HANDGUN_SCOUT_PRIMARY: + return info.GetDamage() * 1.75; + + case TF_WEAPON_SCATTERGUN: + case TF_WEAPON_REVOLVER: + return info.GetDamage() * 2.f; + + case TF_WEAPON_SNIPERRIFLE: + case TF_WEAPON_SNIPERRIFLE_DECAP: + case TF_WEAPON_SNIPERRIFLE_CLASSIC: + case TF_WEAPON_COMPOUND_BOW: + case TF_WEAPON_KNIFE: + case TF_WEAPON_PEP_BRAWLER_BLASTER: + return info.GetDamage() * 3.f; + } + } + + // unmodified + return info.GetDamage(); +} + + +//----------------------------------------------------------------------------------------------------- +int CMerasmus::OnTakeDamage_Alive( const CTakeDamageInfo &info ) +{ + if ( !IsRevealed() ) + { + return 0; + } + + if ( IsSelf( info.GetAttacker() ) ) + { + // don't injure myself + return 0; + } + + CTakeDamageInfo modifiedInfo = info; + + if ( RandomInt( 0, 30 ) == 0 ) + { + //EmitSound( "Halloween.MerasmusHurt" ); << add new sound entry + } + + if ( IsStunned() ) + { + DispatchParticleEffect( "merasmus_blood", modifiedInfo.GetDamagePosition(), GetAbsAngles() ); + } + else + { + DispatchParticleEffect( "merasmus_blood_bits", modifiedInfo.GetDamagePosition(), GetAbsAngles() ); + } + + modifiedInfo.SetDamage( MerasmusModifyDamage( modifiedInfo ) ); + if ( m_bIsDoingAOEAttack || m_bStunned ) + { + modifiedInfo.AddDamageType( DMG_CRITICAL ); + } + + int result = BaseClass::OnTakeDamage_Alive( modifiedInfo ); + + // update boss health meter + float healthPercentage = (float)GetHealth() / (float)GetMaxHealth(); + + if ( m_hHealthBar ) + { + if ( healthPercentage <= 0.0f ) + { + m_hHealthBar->HideBossHealthMeter(); + } + else + { + m_hHealthBar->SetBossHealthPercentage( healthPercentage ); + } + } + + // Stats Tracking + CTFPlayer *pAttacker = ToTFPlayer( info.GetAttacker() ); + if ( pAttacker ) + { + int iClass = pAttacker->GetPlayerClass()->GetClassIndex(); + if ( iClass > TF_CLASS_UNDEFINED && iClass < TF_LAST_NORMAL_CLASS ) + { + m_bossStats.m_arrClassDamage[ iClass ] += info.GetDamage(); + } + } + return result; +} + +//--------------------------------------------------------------------------------------------- +void CMerasmus::FireGameEvent( IGameEvent *event) +{ + if ( !V_strcmp( event->GetName(), "player_death" ) ) + { + // Collect Data + int nDmgType = event->GetInt( "customkill", -1 ); + if ( nDmgType == TF_DMG_CUSTOM_MERASMUS_DECAPITATION || nDmgType == TF_DMG_CUSTOM_MERASMUS_ZAP ) + { + m_bossStats.m_nStaffKills++; + } + else if ( nDmgType == TF_DMG_CUSTOM_MERASMUS_GRENADE || nDmgType == TF_DMG_CUSTOM_MERASMUS_PLAYER_BOMB ) + { + m_bossStats.m_nBombKills++; + } + else + { + // Treat as a PvPKill + m_bossStats.m_nPvpKills++; + } + } +} + +//--------------------------------------------------------------------------------------------- +void CMerasmus::Update( void ) +{ + BaseClass::Update(); + + if ( m_damagePoseParameter < 0 ) + { + m_damagePoseParameter = LookupPoseParameter( "damage" ); + } + + if ( m_damagePoseParameter >= 0 ) + { + SetPoseParameter( m_damagePoseParameter, 1.0f - ( (float)GetHealth() / (float)GetMaxHealth() ) ); + } +} + + +Vector CMerasmus::GetCastPosition() const +{ + Vector vForward, vRight, vUp; + AngleVectors( GetAbsAngles(), &vForward, &vRight, &vUp ); + return WorldSpaceCenter() + vForward * 60.f + 30.f * vRight + 60.f * vUp; +} + + +void RemoveAllGrenades( CMerasmus *me ) +{ + const int maxCollectedEntities = 1024; + CBaseEntity *pObjects[ maxCollectedEntities ]; + int count = UTIL_EntitiesInSphere( pObjects, maxCollectedEntities, me->GetAbsOrigin(), 400, FL_GRENADE ); + + for( int i = 0; i < count; ++i ) + { + if ( pObjects[i] == NULL ) + continue; + + if ( pObjects[i]->IsPlayer() ) + continue; + + // Remove the enemy pipe + pObjects[i]->SetThink( &CBaseEntity::SUB_Remove ); + pObjects[i]->SetNextThink( gpGlobals->curtime ); + pObjects[i]->SetTouch( NULL ); + pObjects[i]->AddEffects( EF_NODRAW ); + } +} + +void CMerasmus::OnRevealed(bool bPlaySound) +{ + RecordDisguiseTime(); + m_bRevealed = true; + m_isHiding = false; + m_nRevealedHealth = GetHealth(); + + if (bPlaySound) + { + PlayHighPrioritySound( "Halloween.MerasmusDiscovered" ); + } + + RemoveEffects( EF_NOINTERP | EF_NODRAW ); + + DispatchParticleEffect( "merasmus_spawn", WorldSpaceCenter(), GetAbsAngles() ); + + // don't collide with anything, we do our own collision detection in our think method + SetSolid( m_solidType ); + SetSolidFlags( m_solidFlags ); + + RemoveAllFakeProps(); + TFGameRules()->PushAllPlayersAway( GetAbsOrigin(), 400.f, 500.f, TF_TEAM_RED ); + TFGameRules()->PushAllPlayersAway( GetAbsOrigin(), 400.f, 500.f, TF_TEAM_BLUE ); + RemoveAllGrenades( this ); + + // give player who found merasmus buff + if ( m_hMerasmusRevealer ) + { + // condition was removed by blowing up merasmus, you get buff award + const float buffDuration = 10.0f; + m_hMerasmusRevealer->m_Shared.AddCond( TF_COND_CRITBOOSTED_PUMPKIN, buffDuration ); + m_hMerasmusRevealer->m_Shared.AddCond( TF_COND_SPEED_BOOST, buffDuration ); + m_hMerasmusRevealer->m_Shared.AddCond( TF_COND_INVULNERABLE, buffDuration ); + } + m_hMerasmusRevealer = NULL; + + // show Boss' health meter on HUD + if ( m_hHealthBar ) + { + float healthPercentage = (float)GetHealth() / (float)GetMaxHealth(); + m_hHealthBar->SetBossHealthPercentage( healthPercentage ); + } + + // face towards a nearby player + CUtlVector< CTFPlayer * > playerVector; + CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS ); + CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS ); + + float closeRangeSq = FLT_MAX; + CTFPlayer *close = NULL; + + for( int i=0; i<playerVector.Count(); ++i ) + { + CTFPlayer *player = playerVector[i]; + + float rangeSq = GetRangeSquaredTo( player ); + if ( rangeSq < closeRangeSq ) + { + closeRangeSq = rangeSq; + close = player; + } + } + + QAngle facingAngle; + + if ( close ) + { + Vector toPlayer = close->GetAbsOrigin() - GetAbsOrigin(); + toPlayer.z = 0.0f; + toPlayer.NormalizeInPlace(); + + VectorAngles( toPlayer, Vector(0,0,1), facingAngle ); + } + else + { + facingAngle.x = 0.0f; + facingAngle.y = RandomFloat( 0.0f, 360.0f ); + facingAngle.z = 0.0f; + } + + SetAbsAngles( facingAngle ); +} + + +bool CMerasmus::ShouldReveal() const +{ + int nDestroyedProps = 0; + for ( int i=0; i<m_fakePropVector.Count(); ++i ) + { + if ( m_fakePropVector[i] == NULL ) + { + nDestroyedProps++; + } + } + return nDestroyedProps >= m_nDestroyedPropsToReveal; +} + + +bool CMerasmus::IsNextKilledPropMerasmus() const +{ + int nDestroyedProps = 0; + for ( int i=0; i<m_fakePropVector.Count(); ++i ) + { + if ( m_fakePropVector[i] == NULL ) + { + nDestroyedProps++; + } + } + return nDestroyedProps + 1 == m_nDestroyedPropsToReveal; +} + + +void CMerasmus::OnDisguise() +{ + m_flStartDisguiseTime = gpGlobals->curtime; + m_bRevealed = false; + m_isHiding = true; + + StopAOEAttack(); + + AddEffects( EF_NOINTERP | EF_NODRAW ); + + DispatchParticleEffect( "merasmus_tp", WorldSpaceCenter(), GetAbsAngles() ); + + // don't collide with anything, we do our own collision detection in our think method + SetSolid( SOLID_NONE ); + SetSolidFlags( FSOLID_NOT_SOLID ); + + // fake randomness of when Merasmus should reveal + m_nDestroyedPropsToReveal = RandomInt( MAX( 1, tf_merasmus_min_props_to_reveal.GetFloat() * m_fakePropVector.Count() ), m_fakePropVector.Count() ); +} + + +bool CMerasmus::ShouldDisguise() const +{ + if ( GetHealth() <= 0 ) + { + return false; + } + + float flLostHealthPercentage = (float)( m_nRevealedHealth - GetHealth() ) / (float)GetMaxHealth(); + return flLostHealthPercentage > tf_merasmus_should_disguise_threshold.GetFloat(); +} + + +CTFWeaponBaseGrenadeProj* CMerasmus::CreateMerasmusGrenade( const Vector& vPosition, const Vector& vVelocity, CBaseCombatCharacter* pOwner, float fScale ) +{ + QAngle qAngles = RandomAngle( 0, 360 ); + CTFWeaponBaseMerasmusGrenade *pGrenade = static_cast<CTFWeaponBaseMerasmusGrenade*>( CBaseEntity::Create( "tf_weaponbase_merasmus_grenade", vPosition, qAngles, pOwner ) ); + if ( pGrenade ) + { + pGrenade->SetModel( MERASMUS_BOMB_MODEL ); + DispatchSpawn( pGrenade ); + pGrenade->InitGrenade( vVelocity, AngularImpulse( 600, random->RandomInt( -1200, 1200 ), 0 ), pOwner, 50 * fScale, 300.f * fScale ); + pGrenade->SetDetonateTimerLength( 2.f ); + pGrenade->SetModelScale( fScale ); + pGrenade->SetCollisionGroup( TFCOLLISION_GROUP_ROCKETS ); // we want to use collision_group_rockets so we don't ever collide with players + } + + return pGrenade; +} + + +const char* CMerasmus::GetRandomPropModelName() +{ + int which = RandomInt( 0, ARRAYSIZE( s_pszDisguiseProps ) - 1 ); + return s_pszDisguiseProps[ which ]; +} + + +void CMerasmus::PushPlayer( CTFPlayer* pPlayer, float flPushForce ) const +{ + // send the player flying + // make sure we push players up and away + Vector toPlayer = pPlayer->EyePosition() - GetAbsOrigin(); + toPlayer.z = 0.0f; + toPlayer.NormalizeInPlace(); + toPlayer.z = 1.0f; + + Vector push = flPushForce * toPlayer; + + pPlayer->ApplyAbsVelocityImpulse( push ); +} + + +void CMerasmus::AddStun( CTFPlayer* pPlayer ) +{ + if ( !IsRevealed() ) + { + // don't let bomb head player explode on merasmus while disguise + return; + } + + // first stun + if ( !IsStunned() ) + { + CPVSFilter filter( WorldSpaceCenter() ); + if (RandomInt( 1, 10) == 9 ) + { + PlayLowPrioritySound( filter, "Halloween.MerasmusHitByBombRare" ); + } + else + { + PlayLowPrioritySound( filter, "Halloween.MerasmusHitByBomb" ); + } + } + + // buff the player that stunned me + const float buffDuration = 10.0f; + pPlayer->m_Shared.AddCond( TF_COND_CRITBOOSTED_PUMPKIN, buffDuration ); + pPlayer->m_Shared.AddCond( TF_COND_SPEED_BOOST, buffDuration ); + pPlayer->m_Shared.AddCond( TF_COND_INVULNERABLE, buffDuration ); + + pPlayer->m_Shared.RemoveCond( TF_COND_STUNNED ); + pPlayer->m_Shared.RemoveCond( TF_COND_HALLOWEEN_BOMB_HEAD ); + pPlayer->MerasmusPlayerBombExplode( false ); + + PushPlayer( pPlayer, 300.f ); + DispatchParticleEffect( "merasmus_dazed_explosion", WorldSpaceCenter(), GetAbsAngles() ); + + IGameEvent *pEvent = gameeventmanager->CreateEvent( "merasmus_stunned" ); + if ( pEvent ) + { + pEvent->SetInt( "player", pPlayer->GetUserID() ); + gameeventmanager->FireEvent( pEvent, true ); + } + + // don't stun while doing AOE + if ( !m_bIsDoingAOEAttack ) + { + m_nBombHitCount++; + m_stunTimer.Start( tf_merasmus_stun_duration.GetFloat() ); + } +} + + +void CMerasmus::OnBeginStun() +{ + EmitSound( "Halloween.Merasmus_Stun" ); + + m_bStunned = true; +} + + +void CMerasmus::OnEndStun() +{ + m_stunTimer.Invalidate(); + m_bStunned = false; +} + + +void CMerasmus::AddFakeProp( CTFMerasmusTrickOrTreatProp* pFakeProp ) +{ + m_fakePropVector.AddToTail( pFakeProp ); +} + + +void CMerasmus::RemoveAllFakeProps() +{ + for ( int i=0; i<m_fakePropVector.Count(); ++i ) + { + if ( m_fakePropVector[i] != NULL ) + { + UTIL_Remove( m_fakePropVector[i] ); + } + } + m_fakePropVector.RemoveAll(); +} + + +void BombHeadForTeam( int nTeam, int nBombHeadPlayers ) +{ + // decrease nBombHeadPlayers by the number of existing bomb heads + CUtlVector< CTFPlayer * > playerVector; + CollectPlayers( &playerVector, nTeam, COLLECT_ONLY_LIVING_PLAYERS ); + + if ( playerVector.Count() <= 0 ) + { + // everyone on this team is dead + return; + } + + for( int n=0; n<nBombHeadPlayers; ++n ) + { + // find the living player who was a bombhead the longest time ago and give them the bomb + CTFPlayer *pVictim = NULL; + float oldestTimeStamp = -1.0f; + + for( int i=0; i<playerVector.Count(); ++i ) + { + CTFPlayer *pPlayer = playerVector[i]; + + if ( pPlayer->GetTimeSinceWasBombHead() > oldestTimeStamp && + !pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) && + pPlayer->GetLastKnownArea() && + pPlayer->GetLastKnownArea()->HasFuncNavPrefer() ) + { + pVictim = pPlayer; + oldestTimeStamp = pPlayer->GetTimeSinceWasBombHead(); + } + } + + if ( !pVictim ) + { + // no victims available - try again next time + return; + } + + // give this victim the bomb + float flBuffDuration = tf_merasmus_bomb_head_duration.GetFloat(); + pVictim->m_Shared.StunPlayer( tf_merasmus_bomb_head_duration.GetFloat(), 0.f, TF_STUN_LOSER_STATE ); + pVictim->m_Shared.AddCond( TF_COND_HALLOWEEN_BOMB_HEAD, flBuffDuration ); + pVictim->m_Shared.AddCond( TF_COND_SPEED_BOOST, flBuffDuration ); + + pVictim->SetBombHeadTimestamp(); + + // notify player they are a bomb + ClientPrint( pVictim, HUD_PRINTCENTER, "#TF_HALLOWEEN_MERASMUS_YOU_ARE_BOMB", pVictim->GetPlayerName() ); + } +} + + +void CMerasmus::BombHeadMode() +{ + int nBombHeadPlayers = tf_merasmus_bomb_head_per_team.GetInt(); + BombHeadForTeam( TF_TEAM_RED, nBombHeadPlayers ); + BombHeadForTeam( TF_TEAM_BLUE, nBombHeadPlayers ); +} + + +bool CMerasmus::ShouldLeave() const +{ + return m_lifeTimer.IsElapsed(); +} + + +void CMerasmus::LeaveWarning() +{ + if ( m_lifeTimer.GetRemainingTime() < 10.0f && m_flLastWarnTime > 10.0f ) + { + IGameEvent *event = gameeventmanager->CreateEvent( "merasmus_escape_warning" ); + if ( event ) + { + event->SetInt( "level", GetLevel() ); + event->SetInt( "time_remaining", 10 ); + gameeventmanager->FireEvent( event ); + } + } + else if ( m_lifeTimer.GetRemainingTime() < 30.0f && m_flLastWarnTime > 30.0f ) + { + IGameEvent *event = gameeventmanager->CreateEvent( "merasmus_escape_warning" ); + if ( event ) + { + event->SetInt( "level", GetLevel() ); + event->SetInt( "time_remaining", 30 ); + gameeventmanager->FireEvent( event ); + } + } + else if ( m_lifeTimer.GetRemainingTime() < 60.0f && m_flLastWarnTime > 60.0f ) + { + IGameEvent *event = gameeventmanager->CreateEvent( "merasmus_escape_warning" ); + if ( event ) + { + event->SetInt( "level", GetLevel() ); + event->SetInt( "time_remaining", 60 ); + gameeventmanager->FireEvent( event ); + } + } + m_flLastWarnTime = m_lifeTimer.GetRemainingTime(); +} + + +void CMerasmus::OnLeaveWhileInPropForm() +{ + CUtlVector< CBaseEntity* > validProps; + for ( int i=0; i<m_fakePropVector.Count(); ++i ) + { + if ( m_fakePropVector[i] != NULL ) + { + validProps.AddToTail( m_fakePropVector[i] ); + } + } + + if ( validProps.Count() ) + { + int which = RandomInt( 0, validProps.Count() -1 ); + SetAbsOrigin( validProps[ which ]->GetAbsOrigin() ); + } +} + + +void CMerasmus::TriggerLogicRelay( const char* pszLogicRelayName, bool bSpawn /*= false*/ ) +{ + CLogicRelay* pLogicRelay = assert_cast< CLogicRelay* >( gEntList.FindEntityByName( NULL, pszLogicRelayName ) ); + if ( pLogicRelay ) + { + inputdata_t data; + data.pCaller = this; + data.pActivator = this; + pLogicRelay->InputTrigger( data ); + + if ( bSpawn ) + { + SetAbsOrigin( pLogicRelay->GetAbsOrigin() ); + } + } +} + + +void CMerasmus::PlayLowPrioritySound( IRecipientFilter &filter, const char* pszSoundEntryName ) +{ + CSoundParameters params; + if ( CBaseEntity::GetParametersForSound( pszSoundEntryName, params, NULL ) ) + { + EmitSound_t es( params ); + es.m_nFlags |= SND_DO_NOT_OVERWRITE_EXISTING_ON_CHANNEL; + EmitSound( filter, entindex(), es ); + } +} + +void CMerasmus::PlayHighPrioritySound( const char* pszSoundEntryName ) +{ + CBroadcastRecipientFilter filter; + CSoundParameters params; + if ( CBaseEntity::GetParametersForSound( pszSoundEntryName, params, NULL ) ) + { + EmitSound_t es( params ); + EmitSound( filter, entindex(), es ); + } +} + +//--------------------------------------------------------------------------------------------- +void CMerasmus::RecordDisguiseTime( ) +{ + if ( m_flStartDisguiseTime == 0 ) + return; + + float flTime = ( gpGlobals->curtime - m_flStartDisguiseTime ); + + if ( m_bossStats.m_flPropHuntTime1 == 0 ) + { + m_bossStats.m_flPropHuntTime1 = flTime; + } + else + { + m_bossStats.m_flPropHuntTime2 = flTime; + } + + m_flStartDisguiseTime = 0; +} + +void CMerasmus::StartRespawnTimer() const +{ + if( TFGameRules() ) + { + if( GetLevel() <= 3 ) + { + TFGameRules()->StartHalloweenBossTimer( tf_merasmus_spawn_interval.GetFloat() ,tf_merasmus_spawn_interval_variation.GetFloat() ); + } + else + { + TFGameRules()->StartHalloweenBossTimer( 60.f ); + } + } +} + +//--------------------------------------------------------------------------------------------- +void CMerasmus::SW_ReportMerasmusStats( void ) +{ + if ( !GCClientSystem() ) + return; + + static uint8 unEventCounter = 0; + + GCSDK::CProtoBufMsg<CMsgHalloween_Merasmus2012> msg( k_EMsgGC_Halloween_Merasmus2012 ); + msg.Body().set_time_submitted( CRTime::RTime32TimeCur() ); + msg.Body().set_is_valve_server( false ); + msg.Body().set_boss_level( GetLevel() ); + msg.Body().set_spawned_health( GetMaxHealth() ); + msg.Body().set_remaining_health( GetHealth() ); + msg.Body().set_life_time( (int)m_lifeTimer.GetElapsedTime() ); // Amount of time in seconds, boss was alive for + msg.Body().set_bomb_kills( m_bossStats.m_nBombKills ); // Kills from Bombs + msg.Body().set_staff_kills( m_bossStats.m_nStaffKills ); // kills from staff attack + msg.Body().set_pvp_kills( m_bossStats.m_nPvpKills ); // Number of kills from players while Boss is out (Jerk factor) + msg.Body().set_prophunt_time1( m_bossStats.m_flPropHuntTime1 ); + msg.Body().set_prophunt_time2( m_bossStats.m_flPropHuntTime2 ); + + msg.Body().set_dmg_scout( m_bossStats.m_arrClassDamage[ TF_CLASS_SCOUT ] ); // Amount of damage done by each class + msg.Body().set_dmg_sniper( m_bossStats.m_arrClassDamage[ TF_CLASS_SNIPER] ); + msg.Body().set_dmg_soldier( m_bossStats.m_arrClassDamage[ TF_CLASS_SOLDIER] ); + msg.Body().set_dmg_demo( m_bossStats.m_arrClassDamage[ TF_CLASS_DEMOMAN] ); + msg.Body().set_dmg_medic( m_bossStats.m_arrClassDamage[ TF_CLASS_MEDIC ] ); + msg.Body().set_dmg_heavy( m_bossStats.m_arrClassDamage[ TF_CLASS_HEAVYWEAPONS ] ); + msg.Body().set_dmg_pyro( m_bossStats.m_arrClassDamage[ TF_CLASS_PYRO ] ); + msg.Body().set_dmg_spy( m_bossStats.m_arrClassDamage[ TF_CLASS_SPY ] ); + msg.Body().set_dmg_engineer( m_bossStats.m_arrClassDamage[ TF_CLASS_ENGINEER ] ); + + // Class counts + CUtlVector< CTFPlayer * > playerVector; + CollectPlayers( &playerVector ); + + int nClassCounts[ TF_LAST_NORMAL_CLASS ]; + V_memset( nClassCounts, 0, sizeof( nClassCounts ) ); + FOR_EACH_VEC( playerVector, index ) + { + int iClass = playerVector[index]->GetPlayerClass()->GetClassIndex(); + if ( iClass > TF_CLASS_UNDEFINED && iClass < TF_LAST_NORMAL_CLASS ) + { + nClassCounts[iClass]++; + } + } + + msg.Body().set_scout_count( nClassCounts[TF_CLASS_SCOUT] ); // Class and player break down at the point of boss despawn + msg.Body().set_sniper_count( nClassCounts[TF_CLASS_SNIPER] ); + msg.Body().set_solider_count( nClassCounts[TF_CLASS_SOLDIER] ); + msg.Body().set_demo_count( nClassCounts[TF_CLASS_DEMOMAN] ); + msg.Body().set_medic_count( nClassCounts[TF_CLASS_MEDIC] ); + msg.Body().set_heavy_count( nClassCounts[TF_CLASS_HEAVYWEAPONS] ); + msg.Body().set_pyro_count( nClassCounts[TF_CLASS_PYRO] ); + msg.Body().set_spy_count( nClassCounts[TF_CLASS_SPY] ); + msg.Body().set_engineer_count( nClassCounts[TF_CLASS_ENGINEER] ); + + GCClientSystem()->BSendMessage( msg ); + + +// OGS Version +// +//#if !defined(NO_STEAM) +// KeyValues* pKVData = new KeyValues( "TF2Halloween2012MerasmusBossStats" ); +// +// // Auto Values +// // ID +// // SessionID +// // TimeSubmitted +// //pKVData->SetBool( "IsValveServer", false ); +// +// pKVData->SetInt( "BossLevel", GetLevel() ); +// pKVData->SetInt( "SpawnedHealth", GetMaxHealth() ); +// pKVData->SetInt( "RemainingHealth", GetHealth() ); // 0 == Boss was killed +// +// pKVData->SetInt( "LifeTime", (int)m_lifeTimer.GetElapsedTime() ); // Amount of time in seconds, boss was alive for +// pKVData->SetInt( "BombKills", m_bossStats.m_nBombKills ); // Kills from Bombs +// pKVData->SetInt( "StaffKills", m_bossStats.m_nStaffKills ); // kills from staff account +// pKVData->SetInt( "PvPKills", m_bossStats.m_nPvpKills ); // Number of kills from players while Boss is out (Jerk factor) +// pKVData->SetInt( "PropHuntTime1", m_bossStats.m_flPropHuntTime1 ); +// pKVData->SetInt( "PropHuntTime2", m_bossStats.m_flPropHuntTime2 ); +// +// // Class Damage +// pKVData->SetInt( "DmgFromScout", m_bossStats.m_arrClassDamage[ TF_CLASS_SCOUT ] ); // Amount of damage done by each class +// pKVData->SetInt( "DmgFromSniper", m_bossStats.m_arrClassDamage[ TF_CLASS_SNIPER ] ); +// pKVData->SetInt( "DmgFromSoldier", m_bossStats.m_arrClassDamage[ TF_CLASS_SOLDIER ] ); +// pKVData->SetInt( "DmgFromDemo", m_bossStats.m_arrClassDamage[ TF_CLASS_DEMOMAN ] ); +// pKVData->SetInt( "DmgFromMedic", m_bossStats.m_arrClassDamage[ TF_CLASS_MEDIC ] ); +// pKVData->SetInt( "DmgFromHeavy", m_bossStats.m_arrClassDamage[ TF_CLASS_HEAVYWEAPONS ] ); +// pKVData->SetInt( "DmgFromPyro", m_bossStats.m_arrClassDamage[ TF_CLASS_PYRO ] ); +// pKVData->SetInt( "DmgFromSpy", m_bossStats.m_arrClassDamage[ TF_CLASS_SPY ] ); +// pKVData->SetInt( "DmgFromEngineer", m_bossStats.m_arrClassDamage[ TF_CLASS_ENGINEER ] ); +// +// // Class counts +// CUtlVector< CTFPlayer * > playerVector; +// CollectPlayers( &playerVector ); +// +// int nClassCounts[ TF_LAST_NORMAL_CLASS ]; +// V_memset( nClassCounts, 0, sizeof( nClassCounts ) ); +// FOR_EACH_VEC( playerVector, index ) +// { +// int iClass = playerVector[index]->GetPlayerClass()->GetClassIndex(); +// if ( iClass > TF_CLASS_UNDEFINED && iClass < TF_LAST_NORMAL_CLASS ) +// { +// nClassCounts[iClass]++; +// } +// } +// +// pKVData->SetInt( "ScoutCount", nClassCounts[TF_CLASS_SCOUT] ); // Class and player break down at the point of boss despawn +// pKVData->SetInt( "SniperCount", nClassCounts[TF_CLASS_SNIPER] ); +// pKVData->SetInt( "SoldierCount", nClassCounts[TF_CLASS_SOLDIER] ); +// pKVData->SetInt( "DemoCount", nClassCounts[TF_CLASS_DEMOMAN] ); +// pKVData->SetInt( "MedicCount", nClassCounts[TF_CLASS_MEDIC] ); +// pKVData->SetInt( "HeavyCount", nClassCounts[TF_CLASS_HEAVYWEAPONS] ); +// pKVData->SetInt( "PyroCount", nClassCounts[TF_CLASS_PYRO] ); +// pKVData->SetInt( "SpyCount", nClassCounts[TF_CLASS_SPY] ); +// pKVData->SetInt( "EngineerCount", nClassCounts[TF_CLASS_ENGINEER] ); +// +// //GetSteamWorksSGameStatsUploader().AddStatsForUpload( pKVData ); +//#endif + + m_bossStats.ResetStats(); +} + + +void CollectTargets( CBaseCombatCharacter *pCaster, float flSpellRange, int nTargetTeam, int nMaxTarget, CUtlVector< CHandle< CBaseEntity > > &vecTargets ) +{ + vecTargets.RemoveAll(); + + // collect everyone + CUtlVector< CTFPlayer * > playerVector; + CollectPlayers( &playerVector, nTargetTeam, COLLECT_ONLY_LIVING_PLAYERS ); + + CUtlVector< CTFPlayer * > candidateTargets; + for ( int i=0; i<playerVector.Count(); ++i ) + { + CTFPlayer *pPlayer = playerVector[i]; + Vector toPlayer = pCaster->EyePosition() - pPlayer->WorldSpaceCenter(); + if ( toPlayer.IsLengthLessThan( flSpellRange ) && pCaster->IsLineOfSightClear( pPlayer ) ) + { + candidateTargets.AddToTail( pPlayer ); + } + } + + while ( candidateTargets.Count() != 0 && vecTargets.Count() != nMaxTarget ) + { + int which = RandomInt( 0, candidateTargets.Count() - 1 ); + vecTargets.AddToTail( candidateTargets[ which ] ); + candidateTargets.FastRemove( which ); + } + + // find sentry in range + for ( int i=0; i<IBaseObjectAutoList::AutoList().Count(); ++i ) + { + CBaseObject *pObj = static_cast< CBaseObject* >( IBaseObjectAutoList::AutoList()[i] ); + if ( pObj->ObjectType() == OBJ_SENTRYGUN ) + { + Vector toSentry = pCaster->EyePosition() - pObj->WorldSpaceCenter(); + if ( toSentry.IsLengthLessThan( flSpellRange ) && pCaster->IsLineOfSightClear( pObj ) ) + { + vecTargets.AddToTail( pObj ); + } + } + } +} + + +void CastSpell( CBaseCombatCharacter* pCaster, const char* pszCastingAttachmentName, float flSpellRange, float flMinDamage, float flMaxDamage, CBaseEntity* pTarget ) +{ + float flSpellTime = 5.f; + + if ( pTarget->IsPlayer() ) + { + CTFPlayer *pPlayer = ToTFPlayer( pTarget ); + pPlayer->m_Shared.SelfBurn( flSpellTime ); + pPlayer->ApplyAbsVelocityImpulse( 1000.f * Vector( 0, 0, 1 ) ); + + Vector toPlayer = pCaster->EyePosition() - pPlayer->WorldSpaceCenter(); + float flDistSqr = toPlayer.LengthSqr(); + + float flDmg = RemapValClamped( flDistSqr, 100.f, Square( 0.5f * flSpellRange ), flMaxDamage, flMinDamage ); + CTakeDamageInfo info( pCaster, pCaster, flDmg, DMG_BURN, TF_DMG_CUSTOM_MERASMUS_ZAP ); + pPlayer->TakeDamage( info ); + } + else if ( pTarget->IsBaseObject() ) + { + CBaseObject *pObj = static_cast< CBaseObject* >( pTarget ); + pObj->DetonateObject(); + } + + // Shoot a beam at them + CReliableBroadcastRecipientFilter filter; + Vector vStartPos; + QAngle qStartAngles; + pCaster->GetAttachment( pszCastingAttachmentName, vStartPos, qStartAngles ); + Vector vEnd = pTarget->EyePosition(); + te_tf_particle_effects_control_point_t controlPoint = { PATTACH_ABSORIGIN, vEnd }; + TE_TFParticleEffectComplex( filter, 0.0f, "merasmus_zap", vStartPos, qStartAngles, NULL, &controlPoint, pCaster, PATTACH_CUSTOMORIGIN ); +} + + +/*static*/ bool CMerasmus::Zap( CBaseCombatCharacter *pCaster, const char* pszCastingAttachmentName, float flSpellRange, float flMinDamage, float flMaxDamage, int nMaxTarget, int nTargetTeam /*= TEAM_ANY*/ ) +{ + CUtlVector< CHandle< CBaseEntity > > vecTargets; + CollectTargets( pCaster, flSpellRange, nTargetTeam, nMaxTarget, vecTargets ); + + if ( vecTargets.Count() == 0 ) + return false; + + for ( int i=0; i<vecTargets.Count(); ++i ) + { + CBaseEntity *pTarget = vecTargets[i]; + if ( pTarget ) + { + CastSpell( pCaster, pszCastingAttachmentName, flSpellRange, flMinDamage, flMaxDamage, pTarget ); + } + } + + return true; +} + + +//--------------------------------------------------------------------------------------------- +//--------------------------------------------------------------------------------------------- +class CMerasmusBehavior : public Action< CMerasmus > +{ +public: + virtual Action< CMerasmus > *InitialContainedAction( CMerasmus *me ) + { + return new CMerasmusReveal; + } + + virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ) + { + return Continue(); + } + + virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval ) + { + if ( !me->IsAlive() ) + { + if ( !me->WasSpawnedByCheats() ) + { + // award achievement to everyone who injured me within the last few seconds + const float deathTime = 5.0f; + const CUtlVector< CMerasmus::AttackerInfo > &attackerVector = me->GetAttackerVector(); + for( int i=0; i<attackerVector.Count(); ++i ) + { + if ( attackerVector[i].m_attacker != NULL && + gpGlobals->curtime - attackerVector[i].m_timestamp < deathTime ) + { + CReliableBroadcastRecipientFilter filter; + UTIL_SayText2Filter( filter, attackerVector[i].m_attacker, false, "#TF_Halloween_Merasmus_Killers", attackerVector[i].m_attacker->GetPlayerName() ); + + if ( TFGameRules() && TFGameRules()->IsHalloweenScenario( CTFGameRules::HALLOWEEN_SCENARIO_LAKESIDE ) ) + { + attackerVector[i].m_attacker->AwardAchievement( ACHIEVEMENT_TF_HALLOWEEN_MERASMUS_KILL ); + } + } + } + + // Award hat levels based on Merasmus' level when he dies, but only to people who were + // around when Merasmus spawned. + const CUtlVector< CHandle<CTFPlayer> >& vecStartingAttackers = me->GetStartingAttackers(); + if ( GCClientSystem() ) + { + // GC message + // Notify the GC that this occurred to possibly level up your hat if you have one + GCSDK::CProtoBufMsg<CMsgUpdateHalloweenMerasmusLootLevel> msg( k_EMsgGC_Halloween_UpdateMerasmusLootLevel ); + msg.Body().set_merasmus_level( me->GetLevel() ); + + FOR_EACH_VEC( vecStartingAttackers, i ) + { + CTFPlayer* pPlayer = vecStartingAttackers[i]; + if ( pPlayer ) + { + CSteamID steamID; + if ( pPlayer->GetSteamID( &steamID ) && steamID.IsValid() && steamID.BIndividualAccount() ) + { + CMsgUpdateHalloweenMerasmusLootLevel_Player *pMsgPlayer = msg.Body().add_players(); + pMsgPlayer->set_steam_id( steamID.ConvertToUint64() ); + } + + } + } + GCClientSystem()->BSendMessage( msg ); + } + } + + // nobody is IT any longer + TFGameRules()->SetIT( NULL ); + + return ChangeTo( new CMerasmusDying, "I am dead!" ); + } + else + { + me->LeaveWarning(); + + if ( me->ShouldLeave() && !me->IsStunned() && !me->IsFlying() ) + { + return ChangeTo( new CMerasmusEscape, "Escaping..." ); + } + } + + return Continue(); + } + + + virtual EventDesiredResult< CMerasmus > OnInjured( CMerasmus *me, const CTakeDamageInfo &info ) + { + if ( me->ShouldDisguise() && me->IsRevealed() && !me->IsStunned() && !me->IsFlying() ) + { + return TrySuspendFor( new CMerasmusDisguise, RESULT_IMPORTANT, "Disguise" ); + } + + return TryContinue(); + } + + virtual const char *GetName( void ) const { return "Merasmus Behavior"; } // return name of this action + +private: + + +}; + + +//--------------------------------------------------------------------------------------------- +//--------------------------------------------------------------------------------------------- +CMerasmusIntention::CMerasmusIntention( CMerasmus *me ) : IIntention( me ) +{ + m_behavior = new Behavior< CMerasmus >( new CMerasmusBehavior ); +} + +CMerasmusIntention::~CMerasmusIntention() +{ + delete m_behavior; +} + +void CMerasmusIntention::Reset( void ) +{ + delete m_behavior; + m_behavior = new Behavior< CMerasmus >( new CMerasmusBehavior ); +} + +void CMerasmusIntention::Update( void ) +{ + m_behavior->Update( static_cast< CMerasmus * >( GetBot() ), GetUpdateInterval() ); +} + +// is this a place we can be? +QueryResultType CMerasmusIntention::IsPositionAllowed( const INextBot *meBot, const Vector &pos ) const +{ + return ANSWER_YES; +} + + + +//--------------------------------------------------------------------------------------------- +//--------------------------------------------------------------------------------------------- +void CMerasmusLocomotion::Update( void ) +{ + CMerasmus *me = (CMerasmus *)GetBot()->GetEntity(); + + if ( me->IsFlying() ) + { + // don't update this locomotor since the flying locomotor is active + return; + } + + NextBotGroundLocomotion::Update(); +} + +float CMerasmusLocomotion::GetRunSpeed( void ) const +{ + return tf_merasmus_speed.GetFloat(); +} + + +//--------------------------------------------------------------------------------------------- +// if delta Z is greater than this, we have to jump to get up +float CMerasmusLocomotion::GetStepHeight( void ) const +{ + return 18.0f; +} + + +//--------------------------------------------------------------------------------------------- +// return maximum height of a jump +float CMerasmusLocomotion::GetMaxJumpHeight( void ) const +{ + return 18.0f; +} + + +//--------------------------------------------------------------------------------------------- +// Return max rate of yaw rotation +float CMerasmusLocomotion::GetMaxYawRate( void ) const +{ + return 200.0f; +} + + +//--------------------------------------------------------------------------------------------- +bool CMerasmusLocomotion::ShouldCollideWith( const CBaseEntity *object ) const +{ + if ( !object ) + return false; + + // Don't collide with players + return object->IsPlayer() ? false : true; +} + + +//--------------------------------------------------------------------------------------------- +//--------------------------------------------------------------------------------------------- +#define MERASMUS_ACCELERATION 250.0f //500.0f + +CMerasmusFlyingLocomotion::CMerasmusFlyingLocomotion( INextBot *bot ) : ILocomotion( bot ) +{ + Reset(); +} + + +//--------------------------------------------------------------------------------------------- +CMerasmusFlyingLocomotion::~CMerasmusFlyingLocomotion() +{ +} + + +//--------------------------------------------------------------------------------------------- +// (EXTEND) reset to initial state +void CMerasmusFlyingLocomotion::Reset( void ) +{ + m_velocity = vec3_origin; + m_acceleration = vec3_origin; + m_currentSpeed = 0.0f; + m_forward = vec3_origin; + m_desiredAltitude = 50.0f; +} + + +//--------------------------------------------------------------------------------------------- +void CMerasmusFlyingLocomotion::MaintainAltitude( void ) +{ + CBaseCombatCharacter *me = GetBot()->GetEntity(); + + float groundZ; + TheNavMesh->GetSimpleGroundHeight( me->GetAbsOrigin(), &groundZ ); + + float currentAltitude = me->GetAbsOrigin().z - groundZ; + float error = m_desiredAltitude - currentAltitude; + float accelZ = clamp( error, -MERASMUS_ACCELERATION, MERASMUS_ACCELERATION ); + + m_acceleration.z += accelZ; +} + + +//--------------------------------------------------------------------------------------------- +// (EXTEND) update internal state +void CMerasmusFlyingLocomotion::Update( void ) +{ + CMerasmus *me = (CMerasmus *)GetBot()->GetEntity(); + const float deltaT = GetUpdateInterval(); + + if ( !me->IsFlying() ) + { + // not flying - let the other locomotor run + return; + } + + Vector pos = me->GetAbsOrigin(); + + // always maintain altitude, even if not trying to move (ie: no Approach call) + MaintainAltitude(); + + m_forward = m_velocity; + m_currentSpeed = m_forward.NormalizeInPlace(); + + Vector damping( 1.0f, 1.0f, 1.0f ); + Vector totalAccel = m_acceleration - m_velocity * damping; + + m_velocity += totalAccel * deltaT; + me->SetAbsVelocity( m_velocity ); + + pos += m_velocity * deltaT; + + // Merasmus doesn't collide with players and floats between valid nav areas + // so skip the collision checking + GetBot()->GetEntity()->SetAbsOrigin( pos ); + m_acceleration = vec3_origin; +} + + +//--------------------------------------------------------------------------------------------- +// (EXTEND) move directly towards the given position +void CMerasmusFlyingLocomotion::Approach( const Vector &goalPos, float goalWeight ) +{ + Vector flyGoal = goalPos; + flyGoal.z += m_desiredAltitude; + + Vector toGoal = flyGoal - GetBot()->GetEntity()->GetAbsOrigin(); + // altitude is handled in Update() + toGoal.z = 0.0f; + toGoal.NormalizeInPlace(); + + m_acceleration += MERASMUS_ACCELERATION * toGoal; +} + + +//--------------------------------------------------------------------------------------------- +float CMerasmusFlyingLocomotion::GetDesiredSpeed( void ) const +{ + return tf_merasmus_speed.GetFloat(); +} + + +//--------------------------------------------------------------------------------------------- +void CMerasmusFlyingLocomotion::SetDesiredAltitude( float height ) +{ + m_desiredAltitude = height; +} + + +//--------------------------------------------------------------------------------------------- +float CMerasmusFlyingLocomotion::GetDesiredAltitude( void ) const +{ + return m_desiredAltitude; +} + +//--------------------------------------------------------------------------------------------- +bool CMerasmusFlyingLocomotion::ShouldCollideWith( const CBaseEntity *object ) const +{ + if ( !object ) + return false; + + // Don't collide with players + return object->IsPlayer() ? false : true; +} + +//--------------------------------------------------------------------------------------------- +void CMerasmusFlyingLocomotion::FaceTowards( const Vector &target ) +{ + CMerasmus *me = (CMerasmus *)GetBot()->GetEntity(); + const float deltaT = GetUpdateInterval(); + + QAngle angles = me->GetLocalAngles(); + + float desiredYaw = UTIL_VecToYaw( target - GetFeet() ); + + float angleDiff = UTIL_AngleDiff( desiredYaw, angles.y ); + + const float maxYawRate = 100.0f; + float deltaYaw = maxYawRate * deltaT; + + if ( angleDiff < -deltaYaw ) + { + angles.y -= deltaYaw; + } + else if ( angleDiff > deltaYaw ) + { + angles.y += deltaYaw; + } + else + { + angles.y += angleDiff; + } + + me->SetLocalAngles( angles ); +} + + + diff --git a/game/server/tf/halloween/merasmus/merasmus.h b/game/server/tf/halloween/merasmus/merasmus.h new file mode 100644 index 0000000..2b3f4ea --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus.h @@ -0,0 +1,412 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#ifndef MERASMUS_H +#define MERASMUS_H + +#include "NextBot.h" +#include "NextBotBehavior.h" +#include "NextBotGroundLocomotion.h" +#include "merasmus_body.h" +#include "Path/NextBotPathFollow.h" +#include "../halloween_base_boss.h" + + +extern ConVar tf_merasmus_health_base; +extern ConVar tf_merasmus_health_per_player; +extern ConVar tf_merasmus_min_player_count; + +extern ConVar tf_merasmus_speed; +extern ConVar tf_merasmus_attack_range; +extern ConVar tf_merasmus_speed_recovery_rate; +extern ConVar tf_merasmus_speed_penalty; +extern ConVar tf_merasmus_chase_duration; +extern ConVar tf_merasmus_chase_range; + +extern ConVar tf_merasmus_health_regen_rate; + +extern ConVar tf_merasmus_bomb_head_duration; +extern ConVar tf_merasmus_bomb_head_per_team; + +class CTFPlayer; +class CWheelOfDoom; +class CMerasmus; +class CTFWeaponBaseGrenadeProj; +class CTFMerasmusTrickOrTreatProp; +class CMonsterResource; + +//---------------------------------------------------------------------------- +class CMerasmusSWStats +{ +public: + + void ResetStats () + { + V_memset( m_arrClassDamage, 0, sizeof( m_arrClassDamage ) ); + m_flPropHuntTime1 = 0; + m_flPropHuntTime2 = 0; + m_flLifeTime = 0; + m_nBombKills = 0; + m_nStaffKills = 0; + m_nPvpKills = 0; + } + + int m_arrClassDamage[ TF_LAST_NORMAL_CLASS ]; + float m_flPropHuntTime1; + float m_flPropHuntTime2; + float m_flLifeTime; + int m_nBombKills; + int m_nStaffKills; + int m_nPvpKills; +}; + +//---------------------------------------------------------------------------- +class CMerasmusLocomotion : public NextBotGroundLocomotion +{ +public: + CMerasmusLocomotion( INextBot *bot ) : NextBotGroundLocomotion( bot ) { } + virtual ~CMerasmusLocomotion() { } + + virtual void Update( void ); // (EXTEND) update internal state + + virtual float GetRunSpeed( void ) const; // get maximum running speed + virtual float GetStepHeight( void ) const; // if delta Z is greater than this, we have to jump to get up + virtual float GetMaxJumpHeight( void ) const; // return maximum height of a jump + + /** + * Should we collide with this entity? + */ + virtual bool ShouldCollideWith( const CBaseEntity *object ) const; + +private: + virtual float GetMaxYawRate( void ) const; // return max rate of yaw rotation +}; + + +//---------------------------------------------------------------------------- +class CMerasmusFlyingLocomotion : public ILocomotion +{ +public: + CMerasmusFlyingLocomotion( INextBot *bot ); + virtual ~CMerasmusFlyingLocomotion(); + + virtual void Reset( void ); // (EXTEND) reset to initial state + virtual void Update( void ); // (EXTEND) update internal state + + virtual void Approach( const Vector &goalPos, float goalWeight = 1.0f ); // (EXTEND) move directly towards the given position + + virtual float GetDesiredSpeed( void ) const; // returns the current desired speed + + virtual void SetDesiredAltitude( float height ); // how high above our Approach goal do we float? + virtual float GetDesiredAltitude( void ) const; + + virtual const Vector &GetGroundNormal( void ) const; // surface normal of the ground we are in contact with + + virtual const Vector &GetVelocity( void ) const; // return current world space velocity + void SetVelocity( const Vector &velocity ); + + virtual bool ShouldCollideWith( const CBaseEntity *object ) const; + + virtual void FaceTowards( const Vector &target ); // rotate body to face towards "target" + +protected: + float m_currentSpeed; + Vector m_forward; + + float m_desiredAltitude; + void MaintainAltitude( void ); + + Vector m_velocity; + Vector m_acceleration; +}; + +inline const Vector &CMerasmusFlyingLocomotion::GetGroundNormal( void ) const +{ + static Vector up( 0, 0, 1.0f ); + + return up; +} + +inline const Vector &CMerasmusFlyingLocomotion::GetVelocity( void ) const +{ + return m_velocity; +} + +inline void CMerasmusFlyingLocomotion::SetVelocity( const Vector &velocity ) +{ + m_velocity = velocity; +} + + +//---------------------------------------------------------------------------- +class CMerasmusIntention : public IIntention +{ +public: + CMerasmusIntention( CMerasmus *me ); + virtual ~CMerasmusIntention(); + + virtual void Reset( void ); + virtual void Update( void ); + + virtual QueryResultType IsPositionAllowed( const INextBot *me, const Vector &pos ) const; // is the a place we can be? + + virtual INextBotEventResponder *FirstContainedResponder( void ) const { return m_behavior; } + virtual INextBotEventResponder *NextContainedResponder( INextBotEventResponder *current ) const { return NULL; } + +private: + Behavior< CMerasmus > *m_behavior; +}; + +DECLARE_AUTO_LIST( IMerasmusAutoList ); + +//---------------------------------------------------------------------------- +class CMerasmus : public CHalloweenBaseBoss, public CGameEventListener, public IMerasmusAutoList +{ +public: + DECLARE_CLASS( CMerasmus, CHalloweenBaseBoss ); + DECLARE_SERVERCLASS(); + + CMerasmus(); + virtual ~CMerasmus(); + + static void PrecacheMerasmus(); + virtual void Precache(); + virtual void Spawn( void ); + virtual void UpdateOnRemove(); + + virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info ); + + // CGameEventListener + virtual void FireGameEvent( IGameEvent *event ); + + // INextBot + virtual CMerasmusIntention *GetIntentionInterface( void ) const { return m_intention; } + virtual ILocomotion *GetLocomotionInterface( void ) const { if ( m_isFlying ) return m_flyingLocomotor; return m_locomotor; } + virtual CMerasmusBody *GetBodyInterface( void ) const { return m_body; } + + virtual void Update( void ); + + const Vector &GetHomePosition( void ) const; + + Vector GetCastPosition() const; + + bool IsRevealed() const { return m_bRevealed; } + void OnRevealed(bool bPlaySound = true); + bool ShouldReveal() const; + bool IsNextKilledPropMerasmus() const; + void SetRevealer( CTFPlayer* pPlayer ) { m_hMerasmusRevealer = pPlayer; } + + void OnDisguise(); + bool ShouldDisguise() const; + + void StartAOEAttack() { m_bIsDoingAOEAttack = true; } + void StopAOEAttack() { m_bIsDoingAOEAttack = false; } + bool IsDoingAOEAttack() const { return m_bIsDoingAOEAttack; } + + static CTFWeaponBaseGrenadeProj* CreateMerasmusGrenade( const Vector& vPosition, const Vector& vVelocity, CBaseCombatCharacter* pOwner, float fScale = 1.0f ); + + static const char* GetRandomPropModelName(); + + void PushPlayer( CTFPlayer* pPlayer, float flPushForce ) const; + int GetBombHitCount() const { return m_nBombHitCount; } + void ResetBombHitCount() { m_nBombHitCount = 0; } + void AddStun( CTFPlayer* pPlayer ); + void OnBeginStun(); + void OnEndStun(); + bool HasStunTimer() const { return !m_stunTimer.IsElapsed(); } + bool IsStunned() const { return m_bStunned; } + + void AddFakeProp( CTFMerasmusTrickOrTreatProp* pFakeProp ); + void RemoveAllFakeProps(); + + void BombHeadMode(); + + bool ShouldLeave() const; + void LeaveWarning(); + void OnLeaveWhileInPropForm(); + + void TriggerLogicRelay( const char* pszLogicRelayName, bool bSpawn = false ); + + void StartFlying( void ) { m_isFlying = true; } + void StopFlying( void ) { m_isFlying = false; } + bool IsFlying( void ) const { return m_isFlying; } + bool IsHiding( void ) const { return m_isHiding; } + + void PlayLowPrioritySound( IRecipientFilter &filter, const char* pszSoundEntryName ); + void PlayHighPrioritySound( const char* pszSoundEntryName ); + + void GainLevel( void ); + void ResetLevel( void ); + static int GetMerasmusLevel() { return m_level; } + virtual int GetLevel( void ) const OVERRIDE; + static void DBG_SetLevel( int nLevel ); + + virtual HalloweenBossType GetBossType() const { return HALLOWEEN_BOSS_MERASMUS; } + + void StartRespawnTimer() const; + + const CUtlVector< CHandle<CTFPlayer> >& GetStartingAttackers() const; + + static bool Zap( CBaseCombatCharacter *pCaster, const char* pszCastingAttachmentName, float flSpellRange, float flMinDamage, float flMaxDamage, int nMaxTarget, int nTargetTeam = TEAM_ANY ); + + // Stats + void RecordDisguiseTime( ); + void SW_ReportMerasmusStats( void ); +private: + + + CMerasmusIntention *m_intention; + CMerasmusLocomotion *m_locomotor; + CMerasmusFlyingLocomotion *m_flyingLocomotor; + CMerasmusBody *m_body; + + bool m_isFlying; + + bool m_isHiding; + + CUtlVector< AttackerInfo > m_attackerVector; // list of everyone who injured me, and when + CUtlVector< CHandle<CTFPlayer> > m_startingAttackersVector; + + CountdownTimer m_stunTimer; + int m_nBombHitCount; + + Vector m_homePos; + int m_damagePoseParameter; + + int m_nRevealedHealth; + + CHandle< CWheelOfDoom > m_wheel; + + CHandle< CMonsterResource > m_hHealthBar; + + CNetworkVar( bool, m_bRevealed ); + CNetworkVar( bool, m_bIsDoingAOEAttack ); + CNetworkVar( bool, m_bStunned ); + + CSoundPatch *m_pIdleSound; + + CUtlVector< CHandle< CTFMerasmusTrickOrTreatProp > > m_fakePropVector; + + int m_nDestroyedPropsToReveal; + + SolidType_t m_solidType; + int m_solidFlags; + + CHandle< CTFPlayer > m_hMerasmusRevealer; + + CountdownTimer m_lifeTimer; + float m_flLastWarnTime; + + // For Stats + float m_flStartDisguiseTime; + CMerasmusSWStats m_bossStats; + + static int m_level; +}; + + +inline int CMerasmus::GetLevel( void ) const +{ + return m_level; +} + +inline void CMerasmus::GainLevel( void ) +{ + ++m_level; +} + +inline void CMerasmus::ResetLevel( void ) +{ + m_level = 1; +} + +inline void CMerasmus::DBG_SetLevel( int nLevel ) +{ + m_level = nLevel; +} + +inline const Vector &CMerasmus::GetHomePosition( void ) const +{ + return m_homePos; +} + +inline const CUtlVector< CHandle<CTFPlayer> >& CMerasmus::GetStartingAttackers() const +{ + return m_startingAttackersVector; +} + +//-------------------------------------------------------------------------------------------------------------- +class CMerasmusPathCost : public IPathCost +{ +public: + CMerasmusPathCost( CMerasmus *me ) + { + m_me = me; + } + + // return the cost (weighted distance between) of moving from "fromArea" to "area", or -1 if the move is not allowed + virtual float operator()( CNavArea *area, CNavArea *fromArea, const CNavLadder *ladder, const CFuncElevator *elevator, float length ) const + { + if ( fromArea == NULL ) + { + // first area in path, no cost + return 0.0f; + } + else + { + if ( !m_me->GetLocomotionInterface()->IsAreaTraversable( area ) ) + { + // our locomotor says we can't move here + return -1.0f; + } + + // compute distance traveled along path so far + float dist; + + if ( ladder ) + { + dist = ladder->m_length; + } + else if ( length > 0.0 ) + { + // optimization to avoid recomputing length + dist = length; + } + else + { + dist = ( area->GetCenter() - fromArea->GetCenter() ).Length(); + } + + float cost = dist + fromArea->GetCostSoFar(); + + // check height change + float deltaZ = fromArea->ComputeAdjacentConnectionHeightChange( area ); + if ( deltaZ >= m_me->GetLocomotionInterface()->GetStepHeight() ) + { + if ( deltaZ >= m_me->GetLocomotionInterface()->GetMaxJumpHeight() ) + { + // too high to reach + return -1.0f; + } + + // jumping is slower than flat ground + const float jumpPenalty = 5.0f; + cost += jumpPenalty * dist; + } + else if ( deltaZ < -m_me->GetLocomotionInterface()->GetDeathDropHeight() ) + { + // too far to drop + return -1.0f; + } + + return cost; + } + } + + CMerasmus *m_me; +}; + + +#endif // MERASMUS_H diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_aoe_attack.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_aoe_attack.cpp new file mode 100644 index 0000000..6698be0 --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_aoe_attack.cpp @@ -0,0 +1,248 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#include "cbase.h" + +#include "tf_gamerules.h" +#include "particle_parse.h" +#include "nav_mesh/tf_nav_area.h" +#include "nav_mesh/tf_nav_mesh.h" +#include "tf/halloween/eyeball_boss/teleport_vortex.h" +#include "tf_weaponbase_grenadeproj.h" +#include "sceneentity.h" + +#include "../merasmus.h" +#include "merasmus_aoe_attack.h" + +//--------------------------------------------------------------------------------------------- +#define MAX_BOMBS_PER_TICK 4 + +//--------------------------------------------------------------------------------------------- +ActionResult< CMerasmus > CMerasmusAOEAttack::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ) +{ + m_aoeStartTimer.Start( 4.f ); + m_state = AOE_BEGIN; + + me->StartFlying(); + + CTFNavArea *pointArea = TheTFNavMesh()->GetControlPointCenterArea( 0 ); + + if ( !pointArea ) + { + return Done( "No control point!" ); + } + + const float surroundRange = 400.f; + CollectSurroundingAreas( &m_wanderAreaVector, pointArea, surroundRange, StepHeight, StepHeight ); + + if ( m_wanderAreaVector.Count() == 0 ) + { + return Done( "No nav areas near control point!" ); + } + + me->GetBodyInterface()->StartActivity( ACT_RANGE_ATTACK1 ); + + // This is only to play sound since animation doesn't work with the vcd + CFmtStr vcdName( "scenes/bot/merasmus/low/bomb_attack_00%d.vcd", RandomInt( 1, 9 ) ); + InstancedScriptedScene( me, vcdName.Get(), NULL, 0.0f, false, NULL, true ); + + m_wanderArea = NULL; + + return Continue(); +} + + +#define BOMB_SCALE 1.0f // 0.85f +#define BOMB_VERT_VEL 750.0f +#define BOMB_START_OFFSET Vector( 0.0f, 0.0f, 150.0f ) + +//--------------------------------------------------------------------------------------------- +void CMerasmusAOEAttack::QueueSingleGrenadeForLaunch( const Vector &vecVelocity ) +{ + m_vecGrenadesToCreate.AddToTail( MerasmusGrenadeCreateSpec_t( vecVelocity ) ); +} + +//--------------------------------------------------------------------------------------------- +void CMerasmusAOEAttack::ClearPendingGrenades() +{ + m_vecGrenadesToCreate.RemoveAll(); +} + +//--------------------------------------------------------------------------------------------- +void CMerasmusAOEAttack::LaunchPendingGrenades( CMerasmus *me ) +{ + int nNumGrenadesLeftToCreate = MIN( m_vecGrenadesToCreate.Count(), MAX_BOMBS_PER_TICK ); + while ( nNumGrenadesLeftToCreate > 0 ) + { + // Create the first one in the list + MerasmusGrenadeCreateSpec_t &info = m_vecGrenadesToCreate[0]; + CMerasmus::CreateMerasmusGrenade( me->WorldSpaceCenter() + BOMB_START_OFFSET, info.m_vecVelocity, me, BOMB_SCALE ); + + // Remove the first one in the list + m_vecGrenadesToCreate.Remove( 0 ); + + --nNumGrenadesLeftToCreate; + } +} + +//--------------------------------------------------------------------------------------------- +void CMerasmusAOEAttack::QueueBombRingsForLaunch( CMerasmus *me ) +{ + ClearPendingGrenades(); + + const float bombRingMinHorizVel = 100.0f; + const float bombRingMaxHorizVel = 2000.0f; + + QAngle myAngles = me->EyeAngles(); + + float deltaVel = bombRingMaxHorizVel - bombRingMinHorizVel; + const int ringCount = 2; + + for( int r=0; r<ringCount; ++r ) + { + float u = (float)(r+1)/(float)ringCount; + + float horizVel = bombRingMinHorizVel + u * deltaVel; + +// float angleDelta = 10.0f + 20.0f * ( 1.0f - u ); + float angleDelta = 20.0f + 30.0f * ( 1.0f - u ); + + for( float angle=0.0f; angle<360.0f; angle += angleDelta ) + { + Vector forward; + AngleVectors( myAngles, &forward ); + + Vector vecVelocity( horizVel * forward.x, horizVel * forward.y, BOMB_VERT_VEL ); + + QueueSingleGrenadeForLaunch( vecVelocity ); + + myAngles.y += angleDelta; + } + } +} + + +//--------------------------------------------------------------------------------------------- +void CMerasmusAOEAttack::QueueBombSpokesForLaunch( CMerasmus *me ) +{ + ClearPendingGrenades(); + + const float bombSpokeAngle = 45.0f; + const int bombSpokeCount = 4; // 10; + const float bombSpokeMinHorizVel = 100.0f; + const float bombSpokeMaxHorizVel = 2000.0f; + + float deltaVel = bombSpokeMaxHorizVel - bombSpokeMinHorizVel; + float angleDelta = bombSpokeAngle; + + QAngle myAngles = me->EyeAngles(); + + for( float angle=0.0f; angle<360.0f; angle += angleDelta ) + { + Vector forward; + AngleVectors( myAngles, &forward ); + + int spokeCount = bombSpokeCount; + + for( int i=0; i<spokeCount; ++i ) + { + float u = (float)(i+1)/(float)spokeCount; + + float horizVel = bombSpokeMinHorizVel + u * deltaVel; + + Vector vecVelocity( horizVel * forward.x, horizVel * forward.y, BOMB_VERT_VEL ); + + QueueSingleGrenadeForLaunch( vecVelocity ); + } + + myAngles.y += angleDelta; + } +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CMerasmus > CMerasmusAOEAttack::Update( CMerasmus *me, float interval ) +{ + if ( me->IsActivityFinished() ) + { + me->StopAOEAttack(); + + return Done(); + } + + switch ( m_state ) + { + case AOE_BEGIN: + { + if ( m_aoeStartTimer.IsElapsed() ) + { + m_launchTimer.Start( 0.5f ); + + m_state = AOE_FIRING; + } + } + break; + case AOE_FIRING: + { + // Start the AOE particles + me->StartAOEAttack(); + + CMerasmusFlyingLocomotion *fly = (CMerasmusFlyingLocomotion *)me->GetLocomotionInterface(); + + // float up and down a bit + fly->SetDesiredAltitude( 175.0f + 25.0f * FastCos( gpGlobals->curtime ) ); + + if ( m_launchTimer.IsElapsed() ) + { + if ( RandomInt( 1, 100 ) < 50 ) + { + QueueBombSpokesForLaunch( me ); + } + else + { + QueueBombRingsForLaunch( me ); + } + + m_launchTimer.Start( 2.0f ); + } + + // Go through and launch any pending grenades + LaunchPendingGrenades( me ); + + // wander among nav areas near the cap point + if ( m_wanderTimer.IsElapsed() || m_wanderArea == NULL ) + { + m_wanderTimer.Start( RandomFloat( 1.0f, 3.0f ) ); + + m_wanderArea = (CTFNavArea *)m_wanderAreaVector[ RandomInt( 0, m_wanderAreaVector.Count()-1 ) ]; + } + + if ( m_wanderArea ) + { + Vector flySpot = m_wanderArea->GetCenter(); + flySpot.z = me->GetAbsOrigin().z; + + me->GetLocomotionInterface()->Approach( flySpot ); + me->GetLocomotionInterface()->FaceTowards( flySpot ); + } + } + break; + } + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +void CMerasmusAOEAttack::OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction ) +{ + me->StopFlying(); + + // The animation sometime doesn't turn off the bodygroup correctly. Slam it in code. + int staffBodyGroup = me->FindBodygroupByName( "staff" ); + me->SetBodygroup( staffBodyGroup, 0 ); + + me->ResetBombHitCount(); +} diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_aoe_attack.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_aoe_attack.h new file mode 100644 index 0000000..76c04cf --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_aoe_attack.h @@ -0,0 +1,51 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#ifndef MERASMUS_AOE_ATTACK_H +#define MERASMUS_AOE_ATTACK_H + +class CMerasmusAOEAttack : public Action< CMerasmus > +{ +public: + virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ); + virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval ); + virtual void OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction ); + + virtual const char *GetName( void ) const { return "AOE Attack!"; } // return name of this action +private: + enum AOEState_t + { + AOE_BEGIN, + AOE_FIRING, + }; + AOEState_t m_state; + + CountdownTimer m_aoeStartTimer; + CountdownTimer m_launchTimer; + CountdownTimer m_flyTimer; + CUtlVector< CNavArea * > m_wanderAreaVector; + CountdownTimer m_wanderTimer; + CTFNavArea *m_wanderArea; + + // To save network perf, we don't create all bombs in a single tick. Rather, we fill up a queue of bombs and distribute the creation over time. + // I originally had another property (start position) as part of MerasmusGrenadeCreateSpec_t, but it didn't make sense, since we want to use + // his current position when we actually create -- not the position he was at whenever we actually filled the queue with grenades. I'm leaving + // the struct here, rather than making m_vecGrenadesToCreate a CUtlVector< Vector >, in case we want to add anything else. + struct MerasmusGrenadeCreateSpec_t + { + MerasmusGrenadeCreateSpec_t( const Vector &v ) : m_vecVelocity( v ) {} + Vector m_vecVelocity; + }; + CUtlVector< MerasmusGrenadeCreateSpec_t > m_vecGrenadesToCreate; + + void QueueSingleGrenadeForLaunch( const Vector &vecVelocity ); // Don't call directly - call QueueBombRingsForLaunch() or QueueBombSpokesForLaunch() + void ClearPendingGrenades(); + void LaunchPendingGrenades( CMerasmus *me ); + + void QueueBombRingsForLaunch( CMerasmus *me ); + void QueueBombSpokesForLaunch( CMerasmus *me ); +}; + +#endif // MERASMUS_TELEPORT_AOE_ATTACK_H diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_attack.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_attack.cpp new file mode 100644 index 0000000..9a40377 --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_attack.cpp @@ -0,0 +1,386 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#include "cbase.h" + +#include "tf_player.h" +#include "tf_gamerules.h" +#include "tf_team.h" +#include "nav_mesh/tf_nav_area.h" +#include "particle_parse.h" +#include "team_control_point_master.h" + +#include "../merasmus.h" +#include "../merasmus_trick_or_treat_prop.h" +#include "merasmus_attack.h" +#include "merasmus_staff_attack.h" +#include "merasmus_stunned.h" +#include "merasmus_throwing_grenade.h" +#include "merasmus_zap.h" + +//---------------------------------------------------------------------------------- +ActionResult< CMerasmus > CMerasmusAttack::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ) +{ + // smooth out the bot's path following by moving toward a point farther down the path + m_path.SetMinLookAheadDistance( 100.0f ); + + // start animation + me->GetBodyInterface()->StartActivity( ACT_MP_RUN_ITEM1 ); + + m_attackTimer.Invalidate(); + + m_attackTarget = NULL; + m_attackTargetFocusTimer.Start( 0.f ); + + RandomGrenadeTimer(); + RandomZapTimer(); + + m_homePos = me->GetAbsOrigin(); + m_homePosRecalcTimer.Start( 3.0f ); + + m_bombHeadTimer.Start( 10.f ); + + return Continue(); +} + + +//---------------------------------------------------------------------------------- +bool CMerasmusAttack::IsPotentiallyChaseable( CMerasmus *me, CTFPlayer *victim ) +{ + if ( !victim ) + { + return false; + } + + if ( !victim->IsAlive() ) + { + // victim is dead - pick a new one + return false; + } + + CTFNavArea *victimArea = (CTFNavArea *)victim->GetLastKnownArea(); + if ( !victimArea || victimArea->HasAttributeTF( TF_NAV_SPAWN_ROOM_BLUE | TF_NAV_SPAWN_ROOM_RED ) ) + { + // unreachable - pick a new victim + return false; + } + + if ( victim->GetGroundEntity() != NULL ) + { + Vector victimAreaPos; + victimArea->GetClosestPointOnArea( victim->GetAbsOrigin(), &victimAreaPos ); + if ( ( victim->GetAbsOrigin() - victimAreaPos ).AsVector2D().IsLengthGreaterThan( 50.0f ) ) + { + // off the mesh and unreachable - pick a new victim + return false; + } + } + + if ( victim->m_Shared.IsInvulnerable() ) + { + // invulnerable - pick a new victim + return false; + } + + Vector toHome = m_homePos - victim->GetAbsOrigin(); + if ( toHome.IsLengthGreaterThan( tf_merasmus_chase_range.GetFloat() ) ) + { + // too far from home - pick a new victim + return false; + } + + return true; +} + + +//---------------------------------------------------------------------------------- +void CMerasmusAttack::SelectVictim( CMerasmus *me ) +{ + if ( IsPotentiallyChaseable( me, m_attackTarget ) && !m_attackTargetFocusTimer.IsElapsed() ) + { + return; + } + + // pick a new victim to chase + CTFPlayer *newVictim = NULL; + CUtlVector< CTFPlayer * > playerVector; + + // collect everyone + CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS ); + CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS ); + + float victimRangeSq = FLT_MAX; + for( int i=0; i<playerVector.Count(); ++i ) + { + if ( !IsPotentiallyChaseable( me, playerVector[i] ) ) + { + continue; + } + + float rangeSq = me->GetRangeSquaredTo( playerVector[i] ); + if ( rangeSq < victimRangeSq ) + { + newVictim = playerVector[i]; + victimRangeSq = rangeSq; + } + } + + if ( newVictim ) + { + // we have a new victim + m_attackTargetFocusTimer.Start( tf_merasmus_chase_duration.GetFloat() ); + } + + m_attackTarget = newVictim; +} + + +//---------------------------------------------------------------------------------- +ActionResult< CMerasmus > CMerasmusAttack::Update( CMerasmus *me, float interval ) +{ + if ( !me->IsAlive() ) + { + return Done(); + } + + if ( me->HasStunTimer() ) + { + return SuspendFor( new CMerasmusStunned, "Stunned!" ); + } + + SelectVictim( me ); + RecomputeHomePosition(); + + if ( m_attackTarget == NULL ) + { + // go home + const float atHomeRange = 50.0f; + if ( me->IsRangeGreaterThan( m_homePos, atHomeRange ) ) + { + if ( m_path.GetAge() > 3.0f ) + { + CMerasmusPathCost cost( me ); + m_path.Compute( me, m_homePos, cost ); + } + + m_path.Update( me ); + } +// else +// { +// // at home with nothing to do - taunt! +// if ( !me->IsMoving() && m_tauntTimer.IsElapsed() ) +// { +// m_tauntTimer.Start( RandomFloat( 3.0f, 5.0f ) ); +// +// return SuspendFor( new CMerasmusTaunt, "Taunting because I have nothing to do." ); +// } +// } + } + else + { + // chase after our chase victim + const float standAndSwingRange = 100.0f; + CTFPlayer *chaseVictim = m_attackTarget; + + if ( me->IsRangeGreaterThan( chaseVictim, standAndSwingRange ) || !me->IsLineOfSightClear( chaseVictim ) ) + { + if ( m_path.GetAge() > 1.0f ) + { + CMerasmusPathCost cost( me ); + m_path.Compute( me, chaseVictim, cost ); + } + + m_path.Update( me ); + } + } + + if ( m_bombHeadTimer.IsElapsed() ) + { + // bomb heads last 15 seconds - make sure we don't add more while existing ones are out + m_bombHeadTimer.Start( 16.0f ); + me->BombHeadMode(); + } + + // swing our axe at our attack target if they are in range + if ( m_attackTarget != NULL && m_attackTarget->IsAlive() ) + { + if ( m_zapTimer.IsElapsed() ) + { + RandomZapTimer(); + return SuspendFor( new CMerasmusZap, "Zap!" ); + } + + if ( me->IsRangeLessThan( m_attackTarget, tf_merasmus_attack_range.GetFloat() ) ) + { + if ( m_attackTimer.IsElapsed() ) + { + m_attackTimer.Start( 1.f ); + return SuspendFor( new CMerasmusStaffAttack( m_attackTarget ), "Whack!" ); + } + + me->GetLocomotionInterface()->FaceTowards( m_attackTarget->WorldSpaceCenter() ); + } + + if ( m_grenadeTimer.IsElapsed() ) + { + RandomGrenadeTimer(); + return SuspendFor( new CMerasmusThrowingGrenade( m_attackTarget ), "Fire in the hole!" ); + } + } + + if ( !me->GetBodyInterface()->IsActivity( ACT_MP_RUN_MELEE ) ) + { + me->GetBodyInterface()->StartActivity( ACT_MP_RUN_MELEE ); + } + + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CMerasmus > CMerasmusAttack::OnStuck( CMerasmus *me ) +{ + // we're stuck - just warp to the our next path goal + if ( m_path.GetCurrentGoal() ) + { + me->SetAbsOrigin( m_path.GetCurrentGoal()->pos + Vector( 0, 0, 10.0f ) ); + } + + return TryContinue( RESULT_TRY ); +} + + +//--------------------------------------------------------------------------------------------- +EventDesiredResult< CMerasmus > CMerasmusAttack::OnContact( CMerasmus *me, CBaseEntity *other, CGameTrace *result ) +{ + if ( other->IsPlayer() ) + { + CTFPlayer *pTFPlayer = ToTFPlayer( other ); + if ( pTFPlayer ) + { + if ( pTFPlayer->IsAlive() ) + { + // force attack the thing we bumped into + // this prevents us from being stuck on dispensers, for example + m_attackTarget = pTFPlayer; + m_attackTargetFocusTimer.Start( tf_merasmus_chase_duration.GetFloat() ); + } + } + } + + return TryContinue( RESULT_TRY ); +} + + +//--------------------------------------------------------------------------------------------- +void CMerasmusAttack::RecomputeHomePosition( void ) +{ + if ( !m_homePosRecalcTimer.IsElapsed() ) + { + return; + } + + m_homePosRecalcTimer.Reset(); + + CTeamControlPoint *contestedPoint = NULL; + CTeamControlPointMaster *pMaster = g_hControlPointMasters.Count() ? g_hControlPointMasters[0] : NULL; + if ( pMaster ) + { + for( int i=0; i<pMaster->GetNumPoints(); ++i ) + { + contestedPoint = pMaster->GetControlPoint( i ); + if ( contestedPoint && pMaster->IsInRound( contestedPoint ) ) + { + if ( ObjectiveResource()->GetOwningTeam( contestedPoint->GetPointIndex() ) == TF_TEAM_BLUE ) + continue; + + // blue are the invaders + if ( !TeamplayGameRules()->TeamMayCapturePoint( TF_TEAM_BLUE, contestedPoint->GetPointIndex() ) ) + continue; + + break; + } + } + } + + if ( contestedPoint ) + { + m_homePos = contestedPoint->GetAbsOrigin(); + } +} + + +void CMerasmusAttack::RandomGrenadeTimer() +{ + m_grenadeTimer.Start( RandomFloat( 2.f, 3.f ) ); +} + + +void CMerasmusAttack::RandomZapTimer() +{ + m_zapTimer.Start( RandomFloat( 3.f, 4.f ) ); +} + + +//--------------------------------------------------------------------------------------------- +//--------------------------------------------------------------------------------------------- +ActionResult< CMerasmus > CMerasmusTaunt::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ) +{ + m_timer.Start( 3.0f ); + + const char *taunts[] = + { + "gesture_melee_cheer", + "gesture_melee_go", + "taunt01", // wave + "taunt06", // thriller + "taunt_laugh", + NULL + }; + + // count the available taunts + int count = 0; + while( true ) + { + if ( taunts[ count ] == NULL ) + break; + + ++count; + } + + // pick one and play it + int which = RandomInt( 0, count-1 ); + me->AddGestureSequence( me->LookupSequence( taunts[ which ] ) ); + + int staffBodyGroup = me->FindBodygroupByName( "staff" ); + me->SetBodygroup( staffBodyGroup, 1 ); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CMerasmus > CMerasmusTaunt::Update( CMerasmus *me, float interval ) +{ + if ( m_timer.IsElapsed() ) + { + return Done(); + } + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +void CMerasmusTaunt::OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction ) +{ + // turn the staff back on + int staffBodyGroup = me->FindBodygroupByName( "staff" ); + me->SetBodygroup( staffBodyGroup, 0 ); +} + + + diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_attack.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_attack.h new file mode 100644 index 0000000..dc19cdf --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_attack.h @@ -0,0 +1,63 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#ifndef MERASMUS_ATTACK_H +#define MERASMUS_ATTACK_H + +//--------------------------------------------------------------------------------------------- +//--------------------------------------------------------------------------------------------- +class CMerasmusAttack : public Action< CMerasmus > +{ +public: + virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ); + virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval ); + + virtual EventDesiredResult< CMerasmus > OnStuck( CMerasmus *me ); + virtual EventDesiredResult< CMerasmus > OnContact( CMerasmus *me, CBaseEntity *other, CGameTrace *result = NULL ); + + virtual const char *GetName( void ) const { return "Attack"; } // return name of this action + +private: + PathFollower m_path; + + Vector m_homePos; + CountdownTimer m_homePosRecalcTimer; + void RecomputeHomePosition( void ); + + CountdownTimer m_attackTimer; + + CountdownTimer m_grenadeTimer; + void RandomGrenadeTimer(); + + CountdownTimer m_zapTimer; + void RandomZapTimer(); + + CountdownTimer m_bombHeadTimer; + CountdownTimer m_tauntTimer; + + CHandle< CTFPlayer > m_attackTarget; // the victim I'm momentarily attacking + CountdownTimer m_attackTargetFocusTimer; + bool IsPotentiallyChaseable( CMerasmus *me, CTFPlayer *victim ); + void SelectVictim( CMerasmus *me ); +}; + + + +//--------------------------------------------------------------------------------------------- +class CMerasmusTaunt : public Action< CMerasmus > +{ +public: + virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ); + virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval ); + virtual void OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction ); + + virtual const char *GetName( void ) const { return "Taunt"; } // return name of this action + +private: + CountdownTimer m_timer; +}; + + +#endif // MERASMUS_ATTACK_H diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_disguise.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_disguise.cpp new file mode 100644 index 0000000..063f959 --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_disguise.cpp @@ -0,0 +1,226 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#include "cbase.h" +#include "tf_player.h" +#include "tf_gamerules.h" +#include "tf_team.h" +#include "nav_mesh/tf_nav_area.h" +#include "particle_parse.h" +#include "player_vs_environment/monster_resource.h" + +#include "../merasmus.h" +#include "../merasmus_trick_or_treat_prop.h" +#include "merasmus_disguise.h" +#include "merasmus_reveal.h" + +ConVar tf_merasmus_disguise_debug( "tf_merasmus_disguise_debug", "0", FCVAR_CHEAT ); + +//--------------------------------------------------------------------------------------------- +ActionResult< CMerasmus > CMerasmusDisguise::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ) +{ + m_bSpawnedProps = false; + + TryToDisguiseSpawn( me ); + + m_flStartRegenTime = gpGlobals->curtime; + m_nStartRegenHealth = me->GetHealth(); + + me->PlayHighPrioritySound( "Halloween.MerasmusInitiateHiding" ); + RandomDisguiseTauntTimer(); + + m_findPropsFailTimer.Start( 3 ); + + // set boss inactive + g_pMonsterResource->SetBossState( 1 ); + + return Continue(); +} + + +//---------------------------------------------------------------------------------- +ActionResult< CMerasmus > CMerasmusDisguise::Update( CMerasmus *me, float interval ) +{ + if ( me->ShouldLeave() ) + { + return Done(); + } + me->LeaveWarning(); + + if ( !m_bSpawnedProps ) + { + if ( m_findPropsFailTimer.HasStarted() && m_findPropsFailTimer.IsElapsed() ) + { + // Couldn't find props in time - skip + return Done(); + } + + if ( !m_findSpawnPositionTime.IsElapsed() ) + { + // not ready yet + return Continue(); + } + + TryToDisguiseSpawn( me ); + + return Continue(); + } + + if ( m_disguiseTauntTimer.IsElapsed() ) + { + if (RandomInt(0,10) == 0) + { + me->PlayHighPrioritySound( "Halloween.MerasmusHiddenRare" ); + } + else + { + me->PlayHighPrioritySound( "Halloween.MerasmusHidden" ); + } + + RandomDisguiseTauntTimer(); + } + + // regen health while disguise + if ( me->GetHealth() < me->GetMaxHealth() ) + { + float flHealthRegenPerSec = tf_merasmus_health_regen_rate.GetFloat() * me->GetMaxHealth() * ( me->GetLevel() - 1 ); + int nNewHealth = MIN( ( gpGlobals->curtime - m_flStartRegenTime ) * flHealthRegenPerSec + m_nStartRegenHealth, me->GetMaxHealth() ); + me->SetHealth( nNewHealth ); + + // show Boss' health meter on HUD + if ( g_pMonsterResource ) + { + float healthPercentage = (float)me->GetHealth() / (float)me->GetMaxHealth(); + g_pMonsterResource->SetBossHealthPercentage( healthPercentage ); + } + } + + // should I come out from disguise? + if ( me->ShouldReveal() ) + { + return Done( "Revealed!" ); + } + + return Continue(); +} + + +void CMerasmusDisguise::OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction ) +{ + if ( me->ShouldLeave() ) + { + me->OnLeaveWhileInPropForm(); + } + + // set boss active + g_pMonsterResource->SetBossState( 0 ); + + me->OnRevealed(); +} + + +QAngle GetRandomPropAngles( CTFNavArea* pArea ) +{ + Vector vNormal; + pArea->ComputeNormal( &vNormal ); + Vector vForward = pArea->GetRandomPoint() - pArea->GetCenter(); + QAngle qAngles; + VectorAngles( vForward, vNormal, qAngles ); + + return qAngles; +} + + +void CMerasmusDisguise::TryToDisguiseSpawn( CMerasmus *me ) +{ + m_findSpawnPositionTime.Start( 1 ); + + // face towards a nearby player + CUtlVector< CTFPlayer * > playerVector; + CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS ); + CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS ); + + // pick a random spot + CUtlVector< CTFNavArea * > candidateAreaVector; + for( int i=0; i<TheNavAreas.Count(); ++i ) + { + CTFNavArea *area = (CTFNavArea *)TheNavAreas[i]; + + if ( !area->HasFuncNavPrefer() ) + { + // don't spawn outside nav prefer + continue; + } + + // don't use small nav areas + const float goodSize = 150.f; + if ( area->GetSizeX() < goodSize || area->GetSizeY() < goodSize ) + { + continue; + } + + // don't use area containing player + if ( area->GetPlayerCount( TF_TEAM_BLUE ) || area->GetPlayerCount( TF_TEAM_RED ) ) + { + continue; + } + + // don't use slope area +// Vector vNormal; +// area->ComputeNormal( &vNormal ); +// if ( vNormal.z < 0.9f ) +// { +// continue; +// } + + candidateAreaVector.AddToTail( area ); + } + + if ( candidateAreaVector.Count() == 0 ) + { + // no place to spawn (!) + return; + } + + // spread out the area + CUtlVector< CTFNavArea * > spawnAreaVector; + SelectSeparatedShuffleSet< CTFNavArea >( 10, 500.f, candidateAreaVector, &spawnAreaVector ); + + if ( spawnAreaVector.Count() == 0 ) + { + // no place to spawn (!) + return; + } + + if ( tf_merasmus_disguise_debug.GetBool() ) + { + for ( int i=0; i<spawnAreaVector.Count(); ++i ) + { + // draw all potential areas + spawnAreaVector[i]->DrawFilled( 0, 255, 0, 0, 30.f ); + } + } + + // spawn random props + int nRandomTrickOrTreatProps = spawnAreaVector.Count(); + for ( int i=0; i<nRandomTrickOrTreatProps; ++i ) + { + int propSpawnID = RandomInt( 0, spawnAreaVector.Count()-1 ); + + CTFMerasmusTrickOrTreatProp* pFakeProp = CTFMerasmusTrickOrTreatProp::Create( spawnAreaVector[ propSpawnID ]->GetCenter(), GetRandomPropAngles( spawnAreaVector[ propSpawnID ] ) ); + me->AddFakeProp( pFakeProp ); + + spawnAreaVector.FastRemove( propSpawnID ); + } + + me->OnDisguise(); + m_bSpawnedProps = true; +} + + +void CMerasmusDisguise::RandomDisguiseTauntTimer() +{ + m_disguiseTauntTimer.Start( RandomFloat( 10.f, 25.f ) ); +} diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_disguise.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_disguise.h new file mode 100644 index 0000000..bfed85f --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_disguise.h @@ -0,0 +1,33 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#ifndef MERASMUS_DISGUISE_H +#define MERASMUS_DISGUISE_H + +//--------------------------------------------------------------------------------------------- +//--------------------------------------------------------------------------------------------- +class CMerasmusDisguise : public Action< CMerasmus > +{ +public: + virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ); + virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval ); + virtual void OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction ); + virtual const char *GetName( void ) const { return "Disguise"; } // return name of this action + +private: + void TryToDisguiseSpawn( CMerasmus *me ); + CountdownTimer m_findPropsFailTimer; + CountdownTimer m_findSpawnPositionTime; + bool m_bSpawnedProps; + + void RandomDisguiseTauntTimer(); + CountdownTimer m_disguiseTauntTimer; + + float m_flStartRegenTime; + int m_nStartRegenHealth; +}; + + +#endif // MERASMUS_DISGUISE_H diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_dying.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_dying.cpp new file mode 100644 index 0000000..216ca92 --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_dying.cpp @@ -0,0 +1,60 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#include "cbase.h" + +#include "particle_parse.h" +#include "tf_gamerules.h" + +#include "../merasmus.h" +#include "merasmus_dying.h" +#include "tf/halloween/eyeball_boss/teleport_vortex.h" + +//--------------------------------------------------------------------------------------------- +ActionResult< CMerasmus > CMerasmusDying::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ) +{ + me->GetBodyInterface()->StartActivity( ACT_DIESIMPLE ); + me->PlayHighPrioritySound( "Halloween.MerasmusBanish" ); + TFGameRules()->BroadcastSound( 255, "Halloween.Merasmus_Death" ); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CMerasmus > CMerasmusDying::Update( CMerasmus *me, float interval ) +{ + if ( me->IsActivityFinished() ) + { + me->Break(); + DispatchParticleEffect( "merasmus_spawn", me->GetAbsOrigin(), me->GetAbsAngles() ); + + IGameEvent *event = gameeventmanager->CreateEvent( "merasmus_killed" ); + if ( event ) + { + event->SetInt( "level", me->GetLevel() ); + gameeventmanager->FireEvent( event ); + } + me->TriggerLogicRelay( "boss_dead_relay" ); + + // create vortex to loot + CTeleportVortex *vortex = (CTeleportVortex *)CBaseEntity::Create( "teleport_vortex", me->WorldSpaceCenter(), vec3_angle ); + if ( vortex ) + { + vortex->SetupVortex( true, true ); + } + + me->GainLevel(); + + me->StartRespawnTimer(); + + UTIL_Remove( me ); + + return Done(); + } + + return Continue(); +} + diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_dying.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_dying.h new file mode 100644 index 0000000..97e888c --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_dying.h @@ -0,0 +1,20 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#ifndef MERASMUS_DYING_H +#define MERASMUS_DYING_H + + +//--------------------------------------------------------------------------------------------- +class CMerasmusDying : public Action< CMerasmus > +{ +public: + virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ); + virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval ); + virtual const char *GetName( void ) const { return "Dying"; } // return name of this action +}; + + +#endif // MERASMUS_DYING_H diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_reveal.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_reveal.cpp new file mode 100644 index 0000000..1dc7a62 --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_reveal.cpp @@ -0,0 +1,38 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#include "cbase.h" +#include "tf_player.h" +#include "tf_gamerules.h" +#include "tf_team.h" +#include "nav_mesh/tf_nav_area.h" +#include "particle_parse.h" + +#include "../merasmus.h" +#include "merasmus_reveal.h" +#include "merasmus_attack.h" + + +//--------------------------------------------------------------------------------------------- +ActionResult< CMerasmus > CMerasmusReveal::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ) +{ + me->OnRevealed(false); + + me->GetBodyInterface()->StartActivity( ACT_SHIELD_UP ); + + return Continue(); +} + + +//---------------------------------------------------------------------------------- +ActionResult< CMerasmus > CMerasmusReveal::Update( CMerasmus *me, float interval ) +{ + if ( me->IsActivityFinished() ) + { + return ChangeTo( new CMerasmusAttack, "Here I come!" ); + } + + return Continue(); +} diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_reveal.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_reveal.h new file mode 100644 index 0000000..de56951 --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_reveal.h @@ -0,0 +1,20 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#ifndef MERASMUS_EMERGE_H +#define MERASMUS_EMERGE_H + +//--------------------------------------------------------------------------------------------- +//--------------------------------------------------------------------------------------------- +class CMerasmusReveal : public Action< CMerasmus > +{ +public: + virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ); + virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval ); + virtual const char *GetName( void ) const { return "Reveal"; } // return name of this action +}; + + +#endif // MERASMUS_EMERGE_H diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_staff_attack.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_staff_attack.cpp new file mode 100644 index 0000000..b15e4f7 --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_staff_attack.cpp @@ -0,0 +1,117 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#include "cbase.h" + +#include "tf_gamerules.h" +#include "tf_player.h" + +#include "../merasmus.h" +#include "merasmus_staff_attack.h" +#include "merasmus_stunned.h" + +CMerasmusStaffAttack::CMerasmusStaffAttack( CTFPlayer* pTarget ) +{ + m_hTarget = pTarget; +} + + +ActionResult< CMerasmus > CMerasmusStaffAttack::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ) +{ + // smooth out the bot's path following by moving toward a point farther down the path + m_path.SetMinLookAheadDistance( 100.0f ); + + int iLayer = me->AddGesture( ACT_MP_ATTACK_STAND_MELEE ); + float flDuration = me->GetLayerDuration( iLayer ); + m_staffSwingTimer.Start( flDuration ); + m_hitTimer.Start( 0.5f * flDuration ); + + if ( RandomInt( 0, 2 ) == 0 ) + { + CPVSFilter filter( me->WorldSpaceCenter() ); + if ( RandomInt( 1, 5 ) == 1 ) + { + me->PlayLowPrioritySound( filter, "Halloween.MerasmusStaffAttackRare" ); + } + else + { + me->PlayLowPrioritySound( filter, "Halloween.MerasmusStaffAttack" ); + } + } + + return Continue(); +} + + +ActionResult< CMerasmus > CMerasmusStaffAttack::Update( CMerasmus *me, float interval ) +{ + // Interupt if stunned + if ( me->HasStunTimer() ) + { + return ChangeTo( new CMerasmusStunned, "Stun Interupt!" ); + } + + if ( m_hitTimer.HasStarted() && m_hitTimer.IsElapsed() ) + { + m_hitTimer.Invalidate(); + + if ( m_hTarget != NULL ) + { + Vector forward; + me->GetVectors( &forward, NULL, NULL ); + + Vector toVictim = m_hTarget->WorldSpaceCenter() - me->WorldSpaceCenter(); + toVictim.NormalizeInPlace(); + + // looser tolerance as victim gets closer + const float closeRange = 100.0f; + float range = me->GetRangeTo( m_hTarget ); + float closeness = ( range < closeRange ) ? 0.0f : ( range - closeRange ) / ( tf_merasmus_attack_range.GetFloat() - closeRange ); + float hitAngle = 0.0f + closeness * 0.27f; + + if ( DotProduct( forward, toVictim ) > hitAngle ) + { + if ( me->IsRangeLessThan( m_hTarget, 0.9f * tf_merasmus_attack_range.GetFloat() ) ) + { + if ( me->IsLineOfSightClear( m_hTarget ) ) + { + // CHOP! + CTakeDamageInfo info( me, me, 70, DMG_CLUB, TF_DMG_CUSTOM_MERASMUS_DECAPITATION ); + CalculateMeleeDamageForce( &info, toVictim, me->WorldSpaceCenter(), 5.0f ); + m_hTarget->TakeDamage( info ); + + CPVSFilter filter( me->WorldSpaceCenter() ); + me->PlayLowPrioritySound( filter, "Halloween.HeadlessBossAxeHitFlesh" ); + + me->PushPlayer( m_hTarget, 500.f ); + } + } + } + } + } + + if ( m_hTarget ) + { + if ( me->IsRangeGreaterThan( m_hTarget, 100.f ) || !me->IsLineOfSightClear( m_hTarget ) ) + { + if ( m_path.GetAge() > 1.0f ) + { + CMerasmusPathCost cost( me ); + m_path.Compute( me, m_hTarget, cost ); + } + + m_path.Update( me ); + } + + me->GetLocomotionInterface()->FaceTowards( m_hTarget->WorldSpaceCenter() ); + } + + if ( m_staffSwingTimer.IsElapsed() ) + { + return Done(); + } + + return Continue(); +} diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_staff_attack.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_staff_attack.h new file mode 100644 index 0000000..60629fc --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_staff_attack.h @@ -0,0 +1,28 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#ifndef MERASMUS_STAFF_ATTACK_H +#define MERASMUS_STAFF_ATTACK_H + +//--------------------------------------------------------------------------------------------- +//--------------------------------------------------------------------------------------------- +class CMerasmusStaffAttack : public Action< CMerasmus > +{ +public: + CMerasmusStaffAttack( CTFPlayer* pTarget ); + virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ); + virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval ); + + virtual const char *GetName( void ) const { return "Staff Attack"; } // return name of this action + +private: + CountdownTimer m_staffSwingTimer; + CountdownTimer m_hitTimer; + CHandle< CTFPlayer > m_hTarget; + + PathFollower m_path; +}; + +#endif // MERASMUS_STAFF_ATTACK_H
\ No newline at end of file diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_stunned.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_stunned.cpp new file mode 100644 index 0000000..8efc321 --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_stunned.cpp @@ -0,0 +1,91 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#include "cbase.h" + +#include "../merasmus.h" +#include "merasmus_stunned.h" +#include "merasmus_teleport.h" + +#include "tf_player.h" + +ActionResult< CMerasmus > CMerasmusStunned::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ) +{ + m_nStunStage = STUN_BEGIN; + + int iLayer = me->AddGesture( ACT_MP_STUN_BEGIN ); + float flDuration = me->GetLayerDuration( iLayer ); + m_stunAnimationTimer.Start( flDuration ); + + me->OnBeginStun(); + + return Continue(); +} + + +ActionResult< CMerasmus > CMerasmusStunned::Update( CMerasmus *me, float interval ) +{ + // finished? + if ( m_nStunStage == STUN_END && m_stunFinishTimer.IsElapsed() ) + { + if ( me->ShouldDisguise() ) + { + return Done(); + } + + if ( me->GetBombHitCount() >= 3 ) + { + return ChangeTo( new CMerasmusTeleport( true, true ), "Teleport AOE!" ); + } + else + { + return ChangeTo( new CMerasmusTeleport( false, false ), "Teleport to new area!" ); + } + } + + if ( m_stunAnimationTimer.IsElapsed() ) + { + bool bStunned = me->HasStunTimer(); + + // reset animation if stunned + if ( bStunned ) + { + m_nStunStage = STUN_BEGIN; + } + + switch ( m_nStunStage ) + { + case STUN_BEGIN: + { + int iLayer = me->AddGesture( ACT_MP_STUN_MIDDLE ); + float flDuration = me->GetLayerDuration( iLayer ); + m_stunAnimationTimer.Start( flDuration ); + if ( !bStunned ) + { + m_nStunStage = STUN_MID; + } + } + break; + case STUN_MID: + { + int iLayer = me->AddGesture( ACT_MP_STUN_END ); + float flDuration = me->GetLayerDuration( iLayer ); + m_stunAnimationTimer.Start( flDuration ); + + m_nStunStage = STUN_END; + m_stunFinishTimer.Start( flDuration + 0.5f ); + } + break; + } + } + + return Continue(); +} + + +void CMerasmusStunned::OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction ) +{ + me->OnEndStun(); +} diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_stunned.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_stunned.h new file mode 100644 index 0000000..4f02145 --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_stunned.h @@ -0,0 +1,29 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#ifndef TF_MERASMUS_STUNNED_H +#define TF_MERASMUS_STUNNED_H + +class CMerasmusStunned : public Action< CMerasmus > +{ +public: + virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ); + virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval ); + virtual void OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction ); + + virtual const char *GetName( void ) const { return "Stunned!"; } // return name of this action +private: + enum StunStage_t + { + STUN_BEGIN, + STUN_MID, + STUN_END + }; + StunStage_t m_nStunStage; + CountdownTimer m_stunAnimationTimer; + CountdownTimer m_stunFinishTimer; +}; + +#endif //TF_MERASMUS_STUNNED_H diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_teleport.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_teleport.cpp new file mode 100644 index 0000000..538b77f --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_teleport.cpp @@ -0,0 +1,189 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#include "cbase.h" + +#include "particle_parse.h" +#include "tf/halloween/eyeball_boss/teleport_vortex.h" +#include "player_vs_environment/monster_resource.h" +#include "tf_gamerules.h" +#include "nav_mesh/tf_nav_area.h" + +#include "../merasmus.h" +#include "merasmus_teleport.h" +#include "merasmus_aoe_attack.h" + + +CMerasmusTeleport::CMerasmusTeleport( bool bShouldAOE, bool bGoToCap ) + : m_bShouldAOE( bShouldAOE ), m_bShouldGoToCap( bGoToCap ) +{ +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CMerasmus > CMerasmusTeleport::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ) +{ + // teleport out + m_state = TELEPORTING_OUT; + me->GetBodyInterface()->StartActivity( ACT_SHIELD_DOWN ); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CMerasmus > CMerasmusTeleport::Update( CMerasmus *me, float interval ) +{ + if ( me->IsActivityFinished() ) + { + switch( m_state ) + { + case TELEPORTING_OUT: + { + DispatchParticleEffect( "merasmus_tp", me->WorldSpaceCenter(), me->GetAbsAngles() ); + + me->AddEffects( EF_NOINTERP | EF_NODRAW ); + + me->SetAbsOrigin( GetTeleportPosition( me ) ); + + // wait on the other side for a moment + m_state = TELEPORTING_IN; + } + break; + + case TELEPORTING_IN: + { + me->RemoveEffects( EF_NOINTERP | EF_NODRAW ); + + DispatchParticleEffect( "merasmus_tp", me->WorldSpaceCenter(), me->GetAbsAngles() ); + + me->GetBodyInterface()->StartActivity( ACT_SHIELD_UP ); + + m_state = DONE; + } + break; + + case DONE: + { + if ( m_bShouldAOE ) + { + m_bShouldAOE = false; + return SuspendFor( new CMerasmusAOEAttack, "AOE Attack!" ); + } + } + return Done(); + } + } + + return Continue(); +} + + +Vector CMerasmusTeleport::GetTeleportPosition( CMerasmus *me ) const +{ + Vector vGroundOffset( 0, 0, 75.0f ); + if ( m_bShouldGoToCap ) + { + return me->GetHomePosition() + vGroundOffset; + } + else + { + // pick a random spot + const float goodSize = 100.f; + CUtlVector< CTFNavArea * > spawnAreaVector; + for( int i=0; i<TheNavAreas.Count(); ++i ) + { + CTFNavArea *area = (CTFNavArea *)TheNavAreas[i]; + + if ( !area->HasFuncNavPrefer() ) + { + // don't spawn outside nav prefer + continue; + } + + // don't use small nav areas + if ( area->GetSizeX() < goodSize || area->GetSizeY() < goodSize ) + { + continue; + } + + // don't use area containing player + if ( area->GetPlayerCount( TF_TEAM_BLUE ) || area->GetPlayerCount( TF_TEAM_RED ) ) + { + continue; + } + + spawnAreaVector.AddToTail( area ); + } + + if ( spawnAreaVector.Count() ) + { + int which = RandomInt( 0, spawnAreaVector.Count() - 1 ); + return spawnAreaVector[ which ]->GetCenter(); + } + else + { + return me->GetHomePosition() + vGroundOffset; + } + } +} + + +//--------------------------------------------------------------------------------------------- +//--------------------------------------------------------------------------------------------- +ActionResult< CMerasmus > CMerasmusEscape::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ) +{ + me->GetBodyInterface()->StartActivity( ACT_FLY ); + + if (RandomInt(0,10) == 0) + { + me->PlayHighPrioritySound( "Halloween.MerasmusDepartRare" ); + } + else + { + me->PlayHighPrioritySound( "Halloween.MerasmusDepart" ); + } + + UTIL_LogPrintf( "HALLOWEEN: merasmus_escaped (max_dps %3.2f) (health %d) (level %d)\n", me->GetMaxInjuryRate(), me->GetHealth(), me->GetLevel() ); + + return Continue(); +} + + +//--------------------------------------------------------------------------------------------- +ActionResult< CMerasmus > CMerasmusEscape::Update( CMerasmus *me, float interval ) +{ + if ( me->IsActivityFinished() ) + { + Vector vPos; + QAngle qAngles; + me->GetAttachment( "effect_robe", vPos, qAngles ); + DispatchParticleEffect( "merasmus_tp", vPos, qAngles ); + + if ( g_pMonsterResource ) + { + g_pMonsterResource->HideBossHealthMeter(); + } + + IGameEvent *event = gameeventmanager->CreateEvent( "merasmus_escaped" ); + if ( event ) + { + event->SetInt( "level", me->GetLevel() ); + gameeventmanager->FireEvent( event ); + } + me->TriggerLogicRelay( "boss_exit_relay" ); + + // reset back to normal level + me->ResetLevel(); + + me->StartRespawnTimer(); + + UTIL_Remove( me ); + + return Done(); + } + + return Continue(); +} diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_teleport.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_teleport.h new file mode 100644 index 0000000..1bd5d67 --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_teleport.h @@ -0,0 +1,49 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#ifndef MERASMUS_TELEPORT_H +#define MERASMUS_TELEPORT_H + +//--------------------------------------------------------------------------------------------- +//--------------------------------------------------------------------------------------------- +class CMerasmusTeleport : public Action< CMerasmus > +{ +public: + CMerasmusTeleport( bool bShouldAOE, bool bGoToCap ); + + virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ); + virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval ); + + virtual const char *GetName( void ) const { return "Teleport"; } // return name of this action + +private: + enum TeleportState + { + TELEPORTING_OUT, + TELEPORTING_IN, + DONE + }; + TeleportState m_state; + + bool m_bShouldAOE; + bool m_bShouldGoToCap; + + Vector GetTeleportPosition( CMerasmus *me ) const; +}; + + +//--------------------------------------------------------------------------------------------- +//--------------------------------------------------------------------------------------------- +class CMerasmusEscape : public Action< CMerasmus > +{ +public: + virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ); + virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval ); + + virtual const char *GetName( void ) const { return "Escape"; } // return name of this action +}; + + +#endif // MERASMUS_TELEPORT_H diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_throwing_grenade.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_throwing_grenade.cpp new file mode 100644 index 0000000..f82fdc7 --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_throwing_grenade.cpp @@ -0,0 +1,159 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#include "cbase.h" + +#include "tf_player.h" +#include "tf_gamerules.h" +#include "particle_parse.h" + +#include "../merasmus.h" +#include "merasmus_throwing_grenade.h" +#include "merasmus_stunned.h" + +CMerasmusThrowingGrenade::CMerasmusThrowingGrenade( CTFPlayer* pTarget ) +{ + m_hTarget = pTarget; +} + + +ActionResult< CMerasmus > CMerasmusThrowingGrenade::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ) +{ + if ( m_hTarget == NULL ) + { + return Done( "No Target" ); + } + + if ( !me->IsLineOfSightClear( m_hTarget ) ) + { + CUtlVector< CTFPlayer * > playerVector; + + // collect everyone + CollectPlayers( &playerVector, TF_TEAM_RED, COLLECT_ONLY_LIVING_PLAYERS ); + CollectPlayers( &playerVector, TF_TEAM_BLUE, COLLECT_ONLY_LIVING_PLAYERS, APPEND_PLAYERS ); + + CUtlVector< CTFPlayer * > newTargetVector; + for ( int i=0; i<playerVector.Count(); ++i ) + { + if ( playerVector[i] == m_hTarget ) + { + continue; + } + + if ( !me->IsLineOfSightClear( playerVector[i] ) ) + { + continue; + } + + newTargetVector.AddToTail( playerVector[i] ); + } + + if ( newTargetVector.Count() == 0 ) + { + m_hTarget = NULL; + } + else + { + int which = RandomInt( 0, newTargetVector.Count() - 1 ); + m_hTarget = newTargetVector[ which ]; + } + } + + if ( m_hTarget == NULL ) + { + return Done( "No Target" ); + } + + me->GetLocomotionInterface()->FaceTowards( m_hTarget->WorldSpaceCenter() ); + int iLayer = me->AddGesture( ACT_MP_ATTACK_STAND_ITEM1 ); + float flDuration = me->GetLayerDuration( iLayer ); + m_throwTimer.Start( flDuration ); + + // we want to release the grenade mid-animation + m_releaseGrenadeTimer.Start( 0.25f ); + + // hide his staff + int staffBodyGroup = me->FindBodygroupByName( "staff" ); + me->SetBodygroup( staffBodyGroup, 2 ); + + // smooth out the bot's path following by moving toward a point farther down the path + m_path.SetMinLookAheadDistance( 100.0f ); + + return Continue(); +} + + +ActionResult< CMerasmus > CMerasmusThrowingGrenade::Update( CMerasmus *me, float interval ) +{ + // Interupt if stunned + if ( me->HasStunTimer() ) + { + return ChangeTo( new CMerasmusStunned, "Stun Interupt!" ); + } + + if ( m_releaseGrenadeTimer.HasStarted() && m_releaseGrenadeTimer.IsElapsed() ) + { + m_releaseGrenadeTimer.Invalidate(); + + DispatchParticleEffect( "merasmus_shoot", PATTACH_ABSORIGIN_FOLLOW, me, "effect_hand_R" ); + + Vector vPos; + QAngle qAngles; + me->GetAttachment( "effect_hand_R", vPos, qAngles ); + + Vector vForward, vRight, vUp; + AngleVectors( me->EyeAngles(), &vForward, &vRight, &vUp ); + float flLaunchSpeed = RandomFloat( 1500.f, 2000.f ); + Vector vecVelocity = ( vForward * flLaunchSpeed ) + ( vUp * 200.0f ) + ( RandomFloat( -10.0f, 10.0f ) * vRight ) + ( RandomFloat( -10.0f, 10.0f ) * vUp ); + CTFWeaponBaseGrenadeProj* pGrenade = CMerasmus::CreateMerasmusGrenade( vPos, vecVelocity, me ); + if ( pGrenade ) + { + if ( RandomInt( 0, 6 ) == 0 ) + { + CPVSFilter filter( me->WorldSpaceCenter() ); + if ( RandomInt(1,10) == 1 ) + { + me->PlayLowPrioritySound( filter, "Halloween.MerasmusGrenadeThrowRare" ); + } + else + { + me->PlayLowPrioritySound( filter, "Halloween.MerasmusGrenadeThrow" ); + } + } + } + } + + if ( m_hTarget ) + { + if ( me->IsRangeGreaterThan( m_hTarget, 100.f ) || !me->IsLineOfSightClear( m_hTarget ) ) + { + if ( m_path.GetAge() > 1.0f ) + { + CMerasmusPathCost cost( me ); + m_path.Compute( me, m_hTarget, cost ); + } + + m_path.Update( me ); + } + + me->GetLocomotionInterface()->FaceTowards( m_hTarget->WorldSpaceCenter() ); + } + + + if ( m_throwTimer.IsElapsed() ) + { + return Done( "Fire in the hole!" ); + } + + return Continue(); +} + + +void CMerasmusThrowingGrenade::OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction ) +{ + // turn his staff back on + int staffBodyGroup = me->FindBodygroupByName( "staff" ); + me->SetBodygroup( staffBodyGroup, 0 ); +} diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_throwing_grenade.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_throwing_grenade.h new file mode 100644 index 0000000..7a508cf --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_throwing_grenade.h @@ -0,0 +1,27 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#ifndef MERASMUS_ROCKET_H +#define MERASMUS_ROCKET_H + +class CMerasmusThrowingGrenade : public Action< CMerasmus > +{ +public: + CMerasmusThrowingGrenade( CTFPlayer* pTarget ); + + virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ); + virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval ); + virtual void OnEnd( CMerasmus *me, Action< CMerasmus > *nextAction ); + + virtual const char *GetName( void ) const { return "Rocket"; } // return name of this action +private: + CHandle< CTFPlayer > m_hTarget; + CountdownTimer m_throwTimer; + CountdownTimer m_releaseGrenadeTimer; + + PathFollower m_path; +}; + +#endif // MERASMUS_ROCKET_H diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_zap.cpp b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_zap.cpp new file mode 100644 index 0000000..1be6822 --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_zap.cpp @@ -0,0 +1,73 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#include "cbase.h" + +#include "../merasmus.h" +#include "merasmus_zap.h" +#include "merasmus_stunned.h" + +ActionResult< CMerasmus > CMerasmusZap::OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ) +{ + me->GetBodyInterface()->StartActivity( ACT_RANGE_ATTACK2 ); + m_zapTimer.Start( 1.3f ); + + m_spellType = SpellType_t( RandomInt( 0, SPELL_COUNT - 1 ) ); + PlayCastSound( me ); + + return Continue(); +} + + +ActionResult< CMerasmus > CMerasmusZap::Update( CMerasmus *me, float interval ) +{ + // Interupt if stunned + if ( me->HasStunTimer() ) + { + return ChangeTo( new CMerasmusStunned, "Stun Interupt!" ); + } + + if ( m_zapTimer.HasStarted() && m_zapTimer.IsElapsed() ) + { + m_zapTimer.Invalidate(); + + const float flSpellRange = 600.f + 50.f * ( me->GetLevel() - 1 ); + const int nTargetCount = 6 + ( me->GetLevel() - 1 ); + const float flMaxDamage = 50.f + ( 5 * (me->GetLevel() - 1) ); + const float flMinDamage = 20.f + ( 5 * (me->GetLevel() - 1) ); + + if ( CMerasmus::Zap( me, "effect_staff", flSpellRange, flMinDamage, flMaxDamage, nTargetCount ) ) + { + me->EmitSound( "Halloween.Merasmus_Spell" ); + } + } + + if ( me->IsActivityFinished() ) + { + return Done( "Zapped!" ); + } + + return Continue(); +} + + +void CMerasmusZap::PlayCastSound( CMerasmus* me ) const +{ + CPVSFilter filter( me->WorldSpaceCenter() ); + switch ( m_spellType ) + { + case SPELL_FIRE: + { + me->PlayLowPrioritySound( filter, "Halloween.MerasmusCastFireSpell" ); + } + break; + case SPELL_LAUNCH: + { + me->PlayLowPrioritySound( filter, "Halloween.MerasmusLaunchSpell" ); + } + break; + } +} + diff --git a/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_zap.h b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_zap.h new file mode 100644 index 0000000..559d136 --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_behavior/merasmus_zap.h @@ -0,0 +1,32 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#ifndef TF_MERASMUS_ZAP_H +#define TF_MERASMUS_ZAP_H + +#include "tf_gamerules.h" + +class CMerasmusZap : public Action< CMerasmus > +{ +public: + virtual ActionResult< CMerasmus > OnStart( CMerasmus *me, Action< CMerasmus > *priorAction ); + virtual ActionResult< CMerasmus > Update( CMerasmus *me, float interval ); + + virtual const char *GetName( void ) const { return "Zap!"; } // return name of this action +private: + enum SpellType_t + { + SPELL_FIRE, + SPELL_LAUNCH, + + SPELL_COUNT + }; + SpellType_t m_spellType; + void PlayCastSound( CMerasmus* me ) const; + + CountdownTimer m_zapTimer; +}; + +#endif //TF_MERASMUS_ZAP_H diff --git a/game/server/tf/halloween/merasmus/merasmus_body.cpp b/game/server/tf/halloween/merasmus/merasmus_body.cpp new file mode 100644 index 0000000..a7ec353 --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_body.cpp @@ -0,0 +1,118 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#include "cbase.h" + +#include "NextBot.h" +#include "merasmus.h" +#include "merasmus_body.h" + + +//------------------------------------------------------------------------------------------- +CMerasmusBody::CMerasmusBody( INextBot *bot ) : IBody( bot ) +{ + m_moveXPoseParameter = -1; + m_moveYPoseParameter = -1; + m_currentActivity = -1; +} + + +//------------------------------------------------------------------------------------------- +bool CMerasmusBody::StartActivity( Activity act, unsigned int flags ) +{ + CMerasmus *me = (CMerasmus *)GetBot()->GetEntity(); + + int animSequence = ::SelectWeightedSequence( me->GetModelPtr(), act, me->GetSequence() ); + + if ( animSequence ) + { + m_currentActivity = act; + me->SetSequence( animSequence ); + me->SetPlaybackRate( 1.0f ); + me->SetCycle( 0 ); + me->ResetSequenceInfo(); + + return true; + } + + return false; +} + + +//------------------------------------------------------------------------------------------- +void CMerasmusBody::Update( void ) +{ + CMerasmus *me = (CMerasmus *)GetBot()->GetEntity(); + + if ( m_moveXPoseParameter < 0 ) + { + m_moveXPoseParameter = me->LookupPoseParameter( "move_x" ); + } + + if ( m_moveYPoseParameter < 0 ) + { + m_moveYPoseParameter = me->LookupPoseParameter( "move_y" ); + } + + + // Update the pose parameters + float speed = me->GetLocomotionInterface()->GetGroundSpeed(); // me->GetAbsVelocity().Length(); + + if ( speed < 0.01f ) + { + // stopped + if ( m_moveXPoseParameter >= 0 ) + { + me->SetPoseParameter( m_moveXPoseParameter, 0.0f ); + } + + if ( m_moveYPoseParameter >= 0 ) + { + me->SetPoseParameter( m_moveYPoseParameter, 0.0f ); + } + } + else + { + Vector forward, right, up; + me->GetVectors( &forward, &right, &up ); + + const Vector &motionVector = me->GetLocomotionInterface()->GetGroundMotionVector(); + + // move_x == 1.0 at full forward motion and -1.0 in full reverse + if ( m_moveXPoseParameter >= 0 ) + { + float forwardVel = DotProduct( motionVector, forward ); + + me->SetPoseParameter( m_moveXPoseParameter, forwardVel ); + } + + if ( m_moveYPoseParameter >= 0 ) + { + float sideVel = DotProduct( motionVector, right ); + + me->SetPoseParameter( m_moveYPoseParameter, sideVel ); + } + } + + // adjust animation speed to actual movement speed + if ( me->m_flGroundSpeed > 0.0f ) + { + // Clamp playback rate to avoid datatable warnings. Anything faster would look silly, anyway. + float playbackRate = clamp( speed / me->m_flGroundSpeed, -4.f, 12.f ); + me->SetPlaybackRate( playbackRate ); + } + + // move the animation ahead in time + me->StudioFrameAdvance(); + me->DispatchAnimEvents( me ); +} + + +//--------------------------------------------------------------------------------------------- +// return the bot's collision mask (hack until we get a general hull trace abstraction here or in the locomotion interface) +unsigned int CMerasmusBody::GetSolidMask( void ) const +{ + return MASK_NPCSOLID | CONTENTS_PLAYERCLIP; +} diff --git a/game/server/tf/halloween/merasmus/merasmus_body.h b/game/server/tf/halloween/merasmus/merasmus_body.h new file mode 100644 index 0000000..b542b19 --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_body.h @@ -0,0 +1,51 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#ifndef MERASMUS_BODY_H +#define MERASMUS_BODY_H + +#include "animation.h" +#include "NextBotBodyInterface.h" + +class INextBot; + + +//---------------------------------------------------------------------------------------------------------------- +/** + * The interface for control and information about the bot's body state (posture, animation state, etc) + */ +class CMerasmusBody : public IBody +{ +public: + CMerasmusBody( INextBot *bot ); + virtual ~CMerasmusBody() { } + + virtual void Update( void ); + + virtual bool StartActivity( Activity act, unsigned int flags = 0 ); + virtual Activity GetActivity( void ) const; // return currently animating activity + virtual bool IsActivity( Activity act ) const; // return true if currently animating activity matches the given one + + virtual unsigned int GetSolidMask( void ) const; // return the bot's collision mask (hack until we get a general hull trace abstraction here or in the locomotion interface) + +private: + int m_currentActivity; + int m_moveXPoseParameter; + int m_moveYPoseParameter; +}; + + +inline Activity CMerasmusBody::GetActivity( void ) const +{ + return (Activity)m_currentActivity; +} + +inline bool CMerasmusBody::IsActivity( Activity act ) const +{ + return act == m_currentActivity ? true : false; +} + + +#endif // MERASMUS_BODY_H diff --git a/game/server/tf/halloween/merasmus/merasmus_dancer.cpp b/game/server/tf/halloween/merasmus/merasmus_dancer.cpp new file mode 100644 index 0000000..5e9b7f4 --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_dancer.cpp @@ -0,0 +1,172 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "cbase.h" +#include "tf_gamerules.h" +#include "merasmus_dancer.h" +#include "animation.h" + +//----------------------------------------------------------------------------- + +#include "tf_fx.h" + +//----------------------------------------------------------------------------- + +#define POOF_SOUND "Halloween.Merasmus_Hiding_Explode" + +//----------------------------------------------------------------------------- + +LINK_ENTITY_TO_CLASS( merasmus_dancer, CMerasmusDancer ); + +IMPLEMENT_SERVERCLASS_ST( CMerasmusDancer, DT_MerasmusDancer ) +END_SEND_TABLE() + +//----------------------------------------------------------------------------- + +#define MERASMUS_MODEL_NAME "models/bots/merasmus/merasmus.mdl" + +//----------------------------------------------------------------------------- + +CMerasmusDancer::CMerasmusDancer() +: m_bEmitParticleEffect( false ) +{ +} + +//----------------------------------------------------------------------------- + +CMerasmusDancer::~CMerasmusDancer() +{ +} + +//----------------------------------------------------------------------------- + +void CMerasmusDancer::Spawn() +{ + Precache(); + + m_DieCountdownTimer.Invalidate(); + + BaseClass::Spawn(); + + SetModel( MERASMUS_MODEL_NAME ); + UseClientSideAnimation(); + + SetThink( &CMerasmusDancer::DanceThink ); + SetNextThink( gpGlobals->curtime ); + + m_bEmitParticleEffect = true; +} + +//----------------------------------------------------------------------------- + +void CMerasmusDancer::PlaySequence( const char *pSeqName ) +{ + int iAnimSequence = LookupSequence( pSeqName ); // dance animation + if ( iAnimSequence ) + { + SetSequence( iAnimSequence ); + SetPlaybackRate( 1.0f ); + SetCycle( 0 ); + ResetSequenceInfo(); + + HideStaff(); + } +} + +//----------------------------------------------------------------------------- + +void CMerasmusDancer::PlayActivity( int iActivity ) +{ + int iAnimSequence = ::SelectWeightedSequence( GetModelPtr(), iActivity, GetSequence() ); + if ( iAnimSequence ) + { + SetSequence( iAnimSequence ); + SetPlaybackRate( 1.0f ); + SetCycle( 0 ); + ResetSequenceInfo(); + + HideStaff(); + } +} + +//----------------------------------------------------------------------------- + +void CMerasmusDancer::HideStaff() +{ + int nStaffBodyGroup = FindBodygroupByName( "staff" ); + SetBodygroup( nStaffBodyGroup, 2 ); +} + +//----------------------------------------------------------------------------- + +void CMerasmusDancer::Dance() +{ + PlaySequence( "taunt06" ); +} + +//----------------------------------------------------------------------------- + +void CMerasmusDancer::Vanish() +{ + m_bEmitParticleEffect = true; + m_DieCountdownTimer.Start( 0.0f ); +} + +//----------------------------------------------------------------------------- + +void CMerasmusDancer::BlastOff() +{ + m_bEmitParticleEffect = true; + m_DieCountdownTimer.Start( 0.3f ); + + PlayActivity( ACT_FLY ); +} + +//----------------------------------------------------------------------------- + +void CMerasmusDancer::Precache() +{ + BaseClass::Precache(); + + int model = PrecacheModel( MERASMUS_MODEL_NAME ); + PrecacheGibsForModel( model ); + PrecacheParticleSystem( "merasmus_tp" ); // puff effect + + PrecacheScriptSound( POOF_SOUND ); + + // We deliberately allow late precaches here. + bool bAllowPrecache = CBaseAnimating::IsPrecacheAllowed(); + CBaseAnimating::SetAllowPrecache( bAllowPrecache ); +} + +//----------------------------------------------------------------------------- + +bool CMerasmusDancer::ShouldDelete() const +{ + return m_DieCountdownTimer.HasStarted() && m_DieCountdownTimer.IsElapsed(); +} + +//----------------------------------------------------------------------------- + +void CMerasmusDancer::DanceThink() +{ + // Emit the initial effect here, rather than in Spawn(), since GetAbsOrigin() and GetAbsAngles() don't return useful values then. + if ( m_bEmitParticleEffect ) + { + DispatchParticleEffect( "merasmus_tp", GetAbsOrigin(), GetAbsAngles() ); + m_bEmitParticleEffect = false; + EmitSound( POOF_SOUND ); + } + + if ( ShouldDelete() ) + { + EmitSound( POOF_SOUND ); + UTIL_Remove( this ); + return; + } + + SetNextThink( gpGlobals->curtime ); +} diff --git a/game/server/tf/halloween/merasmus/merasmus_dancer.h b/game/server/tf/halloween/merasmus/merasmus_dancer.h new file mode 100644 index 0000000..305f589 --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_dancer.h @@ -0,0 +1,49 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Teleport vortex for the Eyeball Boss +// +//=============================================================================// +#ifndef MERASMUS_DANCE_H +#define MERASMUS_DANCE_H + +#ifdef _WIN32 +#pragma once +#endif + +#include "baseanimating.h" + +//============================================================================= +// +// Non-AI version of Merasmus that can be spawned during the dance spell. +// +class CMerasmusDancer : public CBaseAnimating +{ + DECLARE_CLASS( CMerasmusDancer, CBaseAnimating ); + DECLARE_SERVERCLASS(); + +public: + CMerasmusDancer(); + virtual ~CMerasmusDancer(); + + void Dance(); + void Vanish(); + void BlastOff(); + +private: + virtual void Spawn(); + virtual void Precache(); + + void HideStaff(); + void PlaySequence( const char *pSeqName ); + void PlayActivity( int iActivity ); + void DanceThink(); + + bool ShouldDelete() const; + + bool m_bEmitParticleEffect; + CountdownTimer m_DieCountdownTimer; +}; + +#endif // MERASMUS_DANCE_H + + diff --git a/game/server/tf/halloween/merasmus/merasmus_trick_or_treat_prop.cpp b/game/server/tf/halloween/merasmus/merasmus_trick_or_treat_prop.cpp new file mode 100644 index 0000000..f343d92 --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_trick_or_treat_prop.cpp @@ -0,0 +1,178 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#include "cbase.h" + +#include "tf_ammo_pack.h" +#include "particle_parse.h" +#include "tf/halloween/ghost/ghost.h" +#include "tf_player.h" +#include "tf_gamerules.h" + +#include "merasmus.h" +#include "merasmus_trick_or_treat_prop.h" + +LINK_ENTITY_TO_CLASS( tf_merasmus_trick_or_treat_prop, CTFMerasmusTrickOrTreatProp ); + +IMPLEMENT_AUTO_LIST( ITFMerasmusTrickOrTreatProp ); + +ConVar tf_merasmus_prop_health( "tf_merasmus_prop_health", "150", FCVAR_CHEAT | FCVAR_GAMEDLL ); + +CTFMerasmusTrickOrTreatProp::CTFMerasmusTrickOrTreatProp() +{ +} + + +void CTFMerasmusTrickOrTreatProp::Spawn() +{ + Precache(); + + SetModel( CMerasmus::GetRandomPropModelName() ); + + BaseClass::Spawn(); + + SetMoveType( MOVETYPE_NONE ); + SetSolid( SOLID_VPHYSICS ); + m_takedamage = DAMAGE_YES; + SetHealth( tf_merasmus_prop_health.GetInt() ); + + DispatchParticleEffect( "merasmus_object_spawn", WorldSpaceCenter(), GetAbsAngles() ); +} + + +void CTFMerasmusTrickOrTreatProp::Event_Killed( const CTakeDamageInfo &info ) +{ + SpawnTrickOrTreatItem(); + + DispatchParticleEffect( "merasmus_object_spawn", WorldSpaceCenter(), GetAbsAngles() ); + EmitSound( "Halloween.Merasmus_Hiding_Explode" ); + + if ( TFGameRules()->GetActiveBoss() ) + { + CMerasmus* pMerasmus = assert_cast< CMerasmus* >( TFGameRules()->GetActiveBoss() ); + if ( pMerasmus ) + { + if ( pMerasmus->IsNextKilledPropMerasmus() ) + { + // move merasmus to the destroyed prop before we reveal him + pMerasmus->SetAbsOrigin( GetAbsOrigin() ); + + if ( info.GetAttacker() && info.GetAttacker()->IsPlayer() ) + { + CTFPlayer* pTFPlayer = assert_cast< CTFPlayer* >( info.GetAttacker() ); + if ( pTFPlayer ) + { + pMerasmus->SetRevealer( pTFPlayer ); + + IGameEvent *pEvent = gameeventmanager->CreateEvent( "merasmus_prop_found" ); + if ( pEvent ) + { + pEvent->SetInt( "player", pTFPlayer->GetUserID() ); + gameeventmanager->FireEvent( pEvent, true ); + } + } + } + } + else + { + CPVSFilter filter( pMerasmus->WorldSpaceCenter() ); + if (RandomInt(1,3) == 1) + { + pMerasmus->PlayLowPrioritySound( filter, "Halloween.MerasmusTauntFakeProp" ); + } + } + } + } + + BaseClass::Event_Killed( info ); +} + + +int CTFMerasmusTrickOrTreatProp::OnTakeDamage( const CTakeDamageInfo &info ) +{ + DispatchParticleEffect( "merasmus_blood", info.GetDamagePosition(), GetAbsAngles() ); + + CTakeDamageInfo newinfo = info; + + CTFPlayer *pTFPlayer = ToTFPlayer( newinfo.GetAttacker() ); + if ( pTFPlayer && ( pTFPlayer->IsPlayerClass( TF_CLASS_SOLDIER ) || pTFPlayer->IsPlayerClass( TF_CLASS_DEMOMAN ) ) && ( newinfo.GetDamageType() & DMG_BLAST ) ) + { + newinfo.SetDamage( GetHealth() * 2.f ); + } + + return BaseClass::OnTakeDamage( newinfo ); +} + + +void CTFMerasmusTrickOrTreatProp::Touch( CBaseEntity *pOther ) +{ + BaseClass::Touch( pOther ); + + if ( pOther && pOther->IsPlayer() ) + { + CTFPlayer *pPlayer = ToTFPlayer( pOther ); + if ( pPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_BOMB_HEAD ) ) + { + pPlayer->m_Shared.RemoveCond( TF_COND_HALLOWEEN_BOMB_HEAD ); + pPlayer->m_Shared.RemoveCond( TF_COND_STUNNED ); + pPlayer->MerasmusPlayerBombExplode( false ); + + // force kill + CTakeDamageInfo info( pPlayer, pPlayer, 99999, DMG_BLAST, TF_DMG_CUSTOM_NONE ); + Event_Killed( info ); + } + } +} + + +CTFMerasmusTrickOrTreatProp* CTFMerasmusTrickOrTreatProp::Create( const Vector& vPosition, const QAngle& qAngles ) +{ + CTFMerasmusTrickOrTreatProp *pTrickOrTreatProp = static_cast<CTFMerasmusTrickOrTreatProp*>( CBaseEntity::Create( "tf_merasmus_trick_or_treat_prop", vPosition, qAngles, NULL ) ); + + // must be on a team different from player(s) in order for some + // weapons to hit (ie: pipe bombs) + if ( pTrickOrTreatProp ) + { + pTrickOrTreatProp->ChangeTeam( TF_TEAM_HALLOWEEN ); + } + + return pTrickOrTreatProp; +} + + +void CTFMerasmusTrickOrTreatProp::SpawnTrickOrTreatItem() +{ + int nNumAmmo = 1/*RandomInt( 1, 3 )*/; + for ( int i=0; i<nNumAmmo; ++i ) + { + // Create the ammo pack. + CTFAmmoPack *pAmmoPack = CTFAmmoPack::Create( WorldSpaceCenter(), vec3_angle, this, "models/items/ammopack_medium.mdl" ); + Assert( pAmmoPack ); + if ( pAmmoPack ) + { + pAmmoPack->MakeHolidayPack(); + pAmmoPack->SetBonusScale( 2.f ); + pAmmoPack->SetModelScale( 1.4f ); + + Vector vecRight, vecUp; + AngleVectors( EyeAngles(), NULL, &vecRight, &vecUp ); + + // Calculate the initial impulse on the weapon. + Vector vecImpulse = RandomVector( 40.f, 80.f ); + vecImpulse.z *= Sign( vecImpulse.z ); // always go up + + pAmmoPack->SetInitialVelocity( vecImpulse ); + pAmmoPack->ApplyAbsVelocityImpulse( vecImpulse ); + + + // Give the ammo pack some health, so that trains can destroy it. + pAmmoPack->SetCollisionGroup( COLLISION_GROUP_DEBRIS ); + pAmmoPack->m_takedamage = DAMAGE_YES; + pAmmoPack->SetHealth( 900 ); + + pAmmoPack->SetBodygroup( 1, 1 ); + } + } +} diff --git a/game/server/tf/halloween/merasmus/merasmus_trick_or_treat_prop.h b/game/server/tf/halloween/merasmus/merasmus_trick_or_treat_prop.h new file mode 100644 index 0000000..b9d48c4 --- /dev/null +++ b/game/server/tf/halloween/merasmus/merasmus_trick_or_treat_prop.h @@ -0,0 +1,30 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// +// +//============================================================================= +#ifndef MERASMUS_TRICK_OR_TREAT_PROP_H +#define MERASMUS_TRICK_OR_TREAT_PROP_H + +DECLARE_AUTO_LIST( ITFMerasmusTrickOrTreatProp ); + +class CTFMerasmusTrickOrTreatProp : public CBaseAnimating, public ITFMerasmusTrickOrTreatProp +{ + DECLARE_CLASS( CTFMerasmusTrickOrTreatProp, CBaseAnimating ); + +public: + CTFMerasmusTrickOrTreatProp(); + ~CTFMerasmusTrickOrTreatProp() {} + + virtual void Spawn( void ); + virtual void Event_Killed( const CTakeDamageInfo &info ); + virtual int OnTakeDamage( const CTakeDamageInfo &info ); + virtual void Touch( CBaseEntity *pOther ); + + static CTFMerasmusTrickOrTreatProp* Create( const Vector& vPosition, const QAngle& qAngles ); + +private: + void SpawnTrickOrTreatItem(); +}; + +#endif // MERASMUS_TRICK_OR_TREAT_PROP_H |