diff --git a/src/game/client/clientmode_shared.cpp b/src/game/client/clientmode_shared.cpp index 8784ce70cbb..f80cbc270c8 100644 --- a/src/game/client/clientmode_shared.cpp +++ b/src/game/client/clientmode_shared.cpp @@ -399,6 +399,10 @@ void ClientModeShared::Init() ListenForGameEvent( "game_newmap" ); #endif +#ifdef MAPBASE_MP + ListenForGameEvent( "player_afk" ); +#endif + #ifndef _XBOX HLTVCamera()->Init(); #if defined( REPLAY_ENABLED ) @@ -1632,6 +1636,36 @@ void ClientModeShared::FireGameEvent( IGameEvent *event ) CReplayMessagePanel::RemoveAll(); } #endif +#ifdef MAPBASE_MP + else if ( V_strcmp( "player_afk", eventname ) == 0 ) + { + if ( !hudChat ) + return; + + int iPlayerIndex = engine->GetPlayerForUserID( event->GetInt( "userid" ) ); + C_BasePlayer *pPlayer = UTIL_PlayerByIndex( iPlayerIndex ); + if ( pPlayer ) + { + const char *pszPlayerName = pPlayer->GetPlayerName(); + if (PlayerNameNotSetYet( pszPlayerName )) + return; + + CSteamID steamID; + pPlayer->GetSteamID( &steamID ); + + wchar_t wszPlayerName[MAX_PLAYER_NAME_LENGTH]; + UTIL_GetFilteredPlayerNameAsWChar( steamID, pszPlayerName, wszPlayerName ); + + wchar_t wszLocalized[100]; + g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#game_idle_spectator" ), 1, wszPlayerName ); + + char szLocalized[100]; + g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalized, szLocalized, sizeof( szLocalized ) ); + + hudChat->Printf( CHAT_FILTER_TEAMCHANGE, "%s", szLocalized ); + } + } +#endif else { diff --git a/src/game/client/hl2mp/clientmode_hl2mpnormal.cpp b/src/game/client/hl2mp/clientmode_hl2mpnormal.cpp index 35bfd1c1fc7..619774a79c8 100644 --- a/src/game/client/hl2mp/clientmode_hl2mpnormal.cpp +++ b/src/game/client/hl2mp/clientmode_hl2mpnormal.cpp @@ -18,6 +18,10 @@ #include "hl2mpclientscoreboard.h" #include "hl2mptextwindow.h" #include "ienginevgui.h" +#ifdef MAPBASE +#include "hl2mp_gamerules.h" +#include "usermessages.h" +#endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" @@ -109,6 +113,18 @@ ClientModeHL2MPNormal::~ClientModeHL2MPNormal() } +#ifdef MAPBASE +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int g_nNumHideBots = 0; +static void __MsgFunc_HideBotsJoin( bf_read &msg ) +{ + g_nNumHideBots = msg.ReadChar(); +} +#endif + + //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- @@ -122,6 +138,46 @@ void ClientModeHL2MPNormal::Init() { Warning( "Couldn't load combine panel scheme!\n" ); } + +#ifdef MAPBASE + usermessages->HookMessage( "HideBotsJoin", __MsgFunc_HideBotsJoin ); +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ClientModeHL2MPNormal::FireGameEvent( IGameEvent *event ) +{ + const char *eventname = event->GetName(); + + if (!eventname || !eventname[0]) + return; + +#ifdef MAPBASE + if ( FStrEq( "player_connect_client", eventname ) || FStrEq( "player_disconnect", eventname ) ) + { + if ( event->GetInt( "bot" ) != 0 ) + { + if (g_nNumHideBots > 0) + { + g_nNumHideBots--; + return; + } + } + } + else if ( FStrEq( "player_team", eventname ) ) + { + // Don't show player team messages during this + if (g_nNumHideBots > 0) + { + return; + } + } +#endif + + BaseClass::FireGameEvent( event ); } diff --git a/src/game/client/hl2mp/clientmode_hl2mpnormal.h b/src/game/client/hl2mp/clientmode_hl2mpnormal.h index 69bf5fc2f72..e2587db87e9 100644 --- a/src/game/client/hl2mp/clientmode_hl2mpnormal.h +++ b/src/game/client/hl2mp/clientmode_hl2mpnormal.h @@ -32,6 +32,8 @@ class ClientModeHL2MPNormal : public ClientModeShared virtual void Init(); virtual int GetDeathMessageStartHeight( void ); + + virtual void FireGameEvent( IGameEvent *event ); }; extern IClientMode *GetClientModeNormal(); diff --git a/src/game/server/hl2mp/bot/hl2mp_bot.cpp b/src/game/server/hl2mp/bot/hl2mp_bot.cpp index 45f331db94c..e68279c452f 100644 --- a/src/game/server/hl2mp/bot/hl2mp_bot.cpp +++ b/src/game/server/hl2mp/bot/hl2mp_bot.cpp @@ -668,6 +668,10 @@ void CHL2MPBot::Spawn() SetBrokenFormation( false ); GetVisionInterface()->ForgetAllKnownEntities(); + +#ifdef MAPBASE + SetUse( &CHL2MPBot::BotUse ); +#endif } @@ -847,6 +851,45 @@ void CHL2MPBot::Event_Killed( const CTakeDamageInfo &info ) } +#ifdef MAPBASE +//----------------------------------------------------------------------------------------------------- +int CHL2MPBot::ObjectCaps( void ) +{ + int nCaps = BaseClass::ObjectCaps(); + + if ( ( HasAttribute( CHL2MPBot::CAN_POSSESS ) || HasAttribute( CHL2MPBot::IS_NPC ) ) && IsAlive() ) + nCaps |= FCAP_IMPULSE_USE; + + return nCaps; +} + +//----------------------------------------------------------------------------------------------------- +void CHL2MPBot::BotUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( HasAttribute( CHL2MPBot::CAN_POSSESS ) ) + { + CHL2MP_Player *pPlayer = ToHL2MPPlayer( pActivator ); + if ( pPlayer ) + { + CRecipientFilter user; + user.AddAllPlayers(); + user.MakeReliable(); + + // Make sure these bots come and go seamlessly + UserMessageBegin( user, "HideBotsJoin" ); + WRITE_CHAR( 2 ); + MessageEnd(); + + if (TheHL2MPBots().BotTakeOverPlayer( pPlayer, false )) + { + TheHL2MPBots().PlayerTakeOverBot( pPlayer, this ); + } + } + } +} +#endif + + //--------------------------------------------------------------------------------------------- class CCollectReachableObjects : public ISearchSurroundingAreasFunctor { diff --git a/src/game/server/hl2mp/bot/hl2mp_bot.h b/src/game/server/hl2mp/bot/hl2mp_bot.h index eeb343b13db..cf4141e9586 100644 --- a/src/game/server/hl2mp/bot/hl2mp_bot.h +++ b/src/game/server/hl2mp/bot/hl2mp_bot.h @@ -75,6 +75,11 @@ class CHL2MPBot: public NextBotPlayer< CHL2MP_Player >, public CGameEventListene virtual CNavArea *GetLastKnownArea( void ) const { return static_cast< CNavArea * >( BaseClass::GetLastKnownArea() ); } // return the last nav area the player occupied - NULL if unknown +#ifdef MAPBASE + virtual int ObjectCaps( void ); + virtual void BotUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +#endif + // NextBotPlayer static CBasePlayer *AllocatePlayerEntity( edict_t *pEdict, const char *playerName ); @@ -192,6 +197,9 @@ class CHL2MPBot: public NextBotPlayer< CHL2MP_Player >, public CGameEventListene ALWAYS_FIRE_WEAPON = 1<<13, // constantly fire our weapon TELEPORT_TO_HINT = 1<<14, // bot will teleport to hint target instead of walking out from the spawn point AUTO_JUMP = 1<<18, // auto jump +#ifdef MAPBASE + CAN_POSSESS = 1<<19, // player can switch to this bot +#endif }; void SetAttribute( int attributeFlag ); void ClearAttribute( int attributeFlag ); diff --git a/src/game/server/hl2mp/bot/hl2mp_bot_manager.cpp b/src/game/server/hl2mp/bot/hl2mp_bot_manager.cpp index 1e6437bedee..d70e0f30e90 100644 --- a/src/game/server/hl2mp/bot/hl2mp_bot_manager.cpp +++ b/src/game/server/hl2mp/bot/hl2mp_bot_manager.cpp @@ -8,6 +8,9 @@ #include "team.h" #include "hl2mp_bot.h" #include "hl2mp_gamerules.h" +#ifdef MAPBASE +#include "ammodef.h" +#endif //---------------------------------------------------------------------------------------------------------------- @@ -24,6 +27,10 @@ ConVar hl2mp_bot_offline_practice( "hl2mp_bot_offline_practice", "0", FCVAR_NONE ConVar hl2mp_bot_melee_only( "hl2mp_bot_melee_only", "0", FCVAR_GAMEDLL, "If nonzero, HL2MPBots will only use melee weapons" ); ConVar hl2mp_bot_gravgun_only( "hl2mp_bot_gravgun_only", "0", FCVAR_GAMEDLL, "If nonzero, HL2MPBots will only use gravity gun weapon" ); +#ifdef MAPBASE +ConVar hl2mp_bot_takeover_min_slots( "hl2mp_bot_takeover_min_slots", "2", FCVAR_GAMEDLL, "How many free client slots are needed in order to perform bot takeover. 0 will disable" ); +#endif + extern const char *GetRandomBotName( void ); extern void CreateBotName( int iTeam, CHL2MPBot::DifficultyType skill, char* pBuffer, int iBufferSize ); @@ -511,6 +518,326 @@ void CHL2MPBotManager::OnForceKickedBots( int iNumKicked ) } +#ifdef MAPBASE + +//---------------------------------------------------------------------------------------------------------------- +bool CHL2MPBotManager::CanDoBotTakeover() const +{ + // Only allow if we have enough free client slots + int nFreeSlots = 0; + for (int i = 1; i < gpGlobals->maxClients; i++) + { + if (!UTIL_PlayerByIndex( i )) + nFreeSlots++; + } + + if (nFreeSlots < hl2mp_bot_takeover_min_slots.GetInt()) + return false; + + return true; +} + +//---------------------------------------------------------------------------------------------------------------- +bool CHL2MPBotManager::CanDoBotTakeoverOn( CHL2MP_Player *pPlayer ) const +{ + if ( pPlayer->IsFakeClient() ) + return false; + + return true; +} + +//---------------------------------------------------------------------------------------------------------------- +void CHL2MPBotManager::HideBotsJoining( int nNumBots ) +{ + CRecipientFilter user; + user.AddAllPlayers(); + user.MakeReliable(); + + // Make sure these bots come and go seamlessly + UserMessageBegin( user, "HideBotsJoin" ); + WRITE_CHAR( nNumBots ); + MessageEnd(); +} + +//---------------------------------------------------------------------------------------------------------------- + +static void ExchangePlayerData( CHL2MP_Player *pFrom, CHL2MP_Player *pTo ) +{ + pTo->CopyAnimationDataFrom( pFrom ); + + Vector vecOrigin = pFrom->GetAbsOrigin(); + QAngle angAngles = pFrom->GetAbsAngles(); + Vector vecVelocity = pFrom->GetAbsVelocity(); + pTo->Teleport( &vecOrigin, &angAngles, &vecVelocity ); + + // Transfer all of the player's weapons and ammo + CBaseCombatWeapon *pActiveWeapon = NULL; + for (int i = 0; i < MAX_WEAPONS; i++) + { + CBaseCombatWeapon *pWeapon = pFrom->GetWeapon( i ); + if (pWeapon) + { + if (pWeapon == pFrom->GetActiveWeapon()) + { + pActiveWeapon = pWeapon; + pWeapon->Holster(); + } + + pFrom->Weapon_Detach( pWeapon ); + + // Add SF_WEAPON_PRESERVE_AMMO briefly so that the weapon doesn't refill itself + bool bPreserveAmmo = pWeapon->HasSpawnFlags( SF_WEAPON_PRESERVE_AMMO ); + if (!bPreserveAmmo) + pWeapon->AddSpawnFlags( SF_WEAPON_PRESERVE_AMMO ); + + pTo->Weapon_Equip( pWeapon ); + + if (!bPreserveAmmo) + pWeapon->RemoveSpawnFlags( SF_WEAPON_PRESERVE_AMMO ); + } + } + + if ( pActiveWeapon ) + pTo->Weapon_Switch( pActiveWeapon ); + + CAmmoDef *pAmmoDef = GetAmmoDef(); + if (pAmmoDef) + { + for (int i = 1; i < pAmmoDef->m_nAmmoIndex; i++) + { + pTo->SetAmmoCount( pFrom->GetAmmoCount( i ), i ); + } + } + + // Health and armor + pTo->SetMaxHealth( pFrom->GetMaxHealth() ); + pTo->SetHealth( pFrom->GetHealth() ); + pTo->SetArmorValue( pFrom->ArmorValue() ); + + // Player stats + pTo->ResetFragCount(); + pTo->IncrementFragCount( pFrom->FragCount() ); + pTo->ResetDeathCount(); + pTo->IncrementDeathCount( pFrom->DeathCount() ); + + pTo->SetProtagonist( pFrom->GetProtagonistName() ); + + // Response contexts + for (int i = 0; i < pFrom->GetContextCount(); i++) + { + if (!pFrom->ContextExpired( i )) + { + const char *pszName = pFrom->GetContextName( i ); + pTo->AddContext( pszName, pFrom->GetContextValue( i ), pFrom->GetContextExpireTime( pszName ) ); + } + } + + // Transfer generic entity properties + //pTo->SetSolid( pFrom->GetSolid() ); + pTo->SetSolidFlags( pFrom->GetSolidFlags() ); + pTo->SetCollisionGroup( pFrom->GetCollisionGroup() ); + pTo->SetCollisionBounds( pFrom->CollisionProp()->OBBMinsPreScaled(), pFrom->CollisionProp()->OBBMaxsPreScaled() ); + pTo->SetMoveType( pFrom->GetMoveType() ); + pTo->SetEFlags( pFrom->GetEFlags() ); + pTo->AddFlag( pFrom->GetFlags() & ~FL_FAKECLIENT ); + //pTo->SetEffects( pFrom->GetEffects() ); + pTo->SetBloodColor( pFrom->BloodColor() ); + pTo->m_lifeState = pFrom->m_lifeState; + pTo->m_takedamage = pFrom->m_takedamage; + + pTo->m_Local.m_iHideHUD = pFrom->m_Local.m_iHideHUD; +} + +//---------------------------------------------------------------------------------------------------------------- + +CHL2MPBot *CHL2MPBotManager::BotTakeOverPlayer( CHL2MP_Player *pPlayer, bool bForIdle ) +{ + char szBotName[MAX_PLAYER_NAME_LENGTH]; + if (bForIdle) + { + V_snprintf( szBotName, sizeof( szBotName ), "%s [IDLE]", pPlayer->GetPlayerName() ); + } + else + { + // Try to ascertain a generic label from the classname context + // This can be assigned by the protagonist system to link classnames with models + const char *pszContextClass = pPlayer->GetContextValue( "classname" ); + if (pszContextClass && *pszContextClass) + { + V_strncpy( szBotName, pszContextClass, sizeof( szBotName ) ); + } + else + { + V_strncpy( szBotName, GetRandomBotName(), sizeof( szBotName ) ); + } + } + + m_bPerformingBotTakeover = true; + + CHL2MPBot *pBot = NextBotCreatePlayerBot< CHL2MPBot >( szBotName, false ); + if (pBot) + { + engine->SetFakeClientConVarValue( pBot->edict(), "cl_playermodel", STRING( pPlayer->GetModelName() ) ); + engine->SetFakeClientConVarValue( pBot->edict(), "name", szBotName ); + + pBot->m_Local.m_iHideHUD = 0; + + if ( pPlayer->IsSuitEquipped() ) + pBot->EquipSuit( false ); + + pBot->ChangeTeam( pPlayer->GetTeamNumber() ); + ExchangePlayerData( pPlayer, pBot ); + + if (bForIdle) + { + pPlayer->SetBotTakeOverAvatar( pBot ); + pBot->SetBotTakeOverAvatar( pPlayer ); + + // Now move the player to spectator + pPlayer->ChangeTeam( TEAM_SPECTATOR ); + pPlayer->SetObserverTarget( pBot ); + pPlayer->SetObserverMode( OBS_MODE_CHASE ); + } + else + { + pBot->SetAttribute( CHL2MPBot::CAN_POSSESS ); + } + } + + m_bPerformingBotTakeover = false; + + return pBot; +} + + +//---------------------------------------------------------------------------------------------------------------- +void CHL2MPBotManager::PlayerTakeOverBot( CHL2MP_Player *pPlayer, CHL2MPBot *pBot, bool bKickBot ) +{ + m_bPerformingBotTakeover = true; + + pPlayer->ChangeTeam( pBot->GetTeamNumber() ); + + if (!pPlayer->IsAlive()) + pPlayer->Spawn(); + + // Restore the player's team and data + ExchangePlayerData( pBot, pPlayer ); + + pPlayer->SetBotTakeOverAvatar( NULL ); + pBot->SetBotTakeOverAvatar( NULL ); + + /*pPlayer->StopObserverMode(); + pPlayer->pl.deadflag = false; + pPlayer->SetPlayerDrownTime( gpGlobals->curtime + 1.0f );*/ // Fixes players playing drown sound upon repossessing bot + + if ( pBot->IsSuitEquipped() ) + pPlayer->EquipSuit( false ); + + if (bKickBot) + { + // Remove the bot + engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", pBot->GetUserID() ) ); + } + + m_bPerformingBotTakeover = false; +} + + +//---------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------- +CON_COMMAND_F( hl2mp_bot_takeover, "Makes a bot take over the given player", FCVAR_GAMEDLL | FCVAR_CHEAT ) +{ + CHL2MP_Player *pPlayer = ToHL2MPPlayer( UTIL_GetCommandClient() ); + if ( !pPlayer || !pPlayer->IsAlive() ) + return; + + if ( !TheHL2MPBots().CanDoBotTakeover() ) + { + Warning( "Cannot do bot takeover right now\n" ); + return; + } + + if ( !TheHL2MPBots().CanDoBotTakeoverOn( pPlayer ) ) + { + Warning( "Cannot do bot takeover on this player\n" ); + return; + } + + TheHL2MPBots().HideBotsJoining( 1 ); + TheHL2MPBots().BotTakeOverPlayer( pPlayer ); +} + + +//---------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------- +CON_COMMAND_F( hl2mp_bot_takeover_end, "Takes over a bot taking over the given player", FCVAR_GAMEDLL | FCVAR_CHEAT ) +{ + CHL2MP_Player *pPlayer = ToHL2MPPlayer( UTIL_GetCommandClient() ); + if ( !pPlayer ) + return; + + CHL2MPBot *pBot = ToHL2MPBot( pPlayer->GetBotTakeOverAvatar() ); + if ( !pBot ) + return; + + TheHL2MPBots().HideBotsJoining( 1 ); + TheHL2MPBots().PlayerTakeOverBot( pPlayer, pBot ); +} + + +//---------------------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------------------- +extern CBaseEntity *FindPickerEntity( CBasePlayer *pPlayer ); + +CON_COMMAND_F( hl2mp_bot_possess, "Makes a player take over a bot and leaves another bot in its place", FCVAR_GAMEDLL | FCVAR_CHEAT ) +{ + CHL2MP_Player *pPlayer = ToHL2MPPlayer( UTIL_GetCommandClient() ); + if ( !pPlayer || !pPlayer->IsAlive() ) + return; + + if ( !TheHL2MPBots().CanDoBotTakeover() ) + { + Warning( "Cannot do bot takeover right now\n" ); + return; + } + + if ( !TheHL2MPBots().CanDoBotTakeoverOn( pPlayer ) ) + { + Warning( "Cannot do bot takeover on this player\n" ); + return; + } + + CHL2MPBot *pBot = NULL; + if ( args.ArgC() > 1 ) + { + pBot = ToHL2MPBot( UTIL_PlayerByIndex( atoi( args.Arg( 1 ) ) ) ); + } + else + { + pBot = ToHL2MPBot( FindPickerEntity( pPlayer ) ); + } + + if ( !pBot ) + { + Warning( "No bot found\n" ); + return; + } + + if ( !pBot->IsFriend( pPlayer ) && !sv_cheats->GetBool() ) + { + Warning( "Bot not friendly\n" ); + return; + } + + TheHL2MPBots().HideBotsJoining( 2 ); + if (TheHL2MPBots().BotTakeOverPlayer( pPlayer, false )) + { + TheHL2MPBots().PlayerTakeOverBot( pPlayer, pBot ); + } +} +#endif + + //---------------------------------------------------------------------------------------------------------------- CHL2MPBotManager &TheHL2MPBots( void ) { diff --git a/src/game/server/hl2mp/bot/hl2mp_bot_manager.h b/src/game/server/hl2mp/bot/hl2mp_bot_manager.h index 6bce6321018..764ea0ff58e 100644 --- a/src/game/server/hl2mp/bot/hl2mp_bot_manager.h +++ b/src/game/server/hl2mp/bot/hl2mp_bot_manager.h @@ -98,6 +98,16 @@ class CHL2MPBotManager : public NextBotManager bool RemoveBotFromTeamAndKick( int nTeam ); +#ifdef MAPBASE + bool CanDoBotTakeover() const; + bool CanDoBotTakeoverOn( CHL2MP_Player *pPlayer ) const; + bool IsPerformingBotTakeover() const { return m_bPerformingBotTakeover; } + void HideBotsJoining( int nNumBots ); + + CHL2MPBot *BotTakeOverPlayer( CHL2MP_Player *pPlayer, bool bForIdle = true ); + void PlayerTakeOverBot( CHL2MP_Player *pPlayer, CHL2MPBot *pBot, bool bKickBot = true ); +#endif + protected: void MaintainBotQuota(); void SetIsInOfflinePractice( bool bIsInOfflinePractice ); @@ -107,6 +117,10 @@ class CHL2MPBotManager : public NextBotManager CUtlVector< CStuckBot * > m_stuckBotVector; CountdownTimer m_stuckDisplayTimer; + +#ifdef MAPBASE + bool m_bPerformingBotTakeover; +#endif }; // singleton accessor diff --git a/src/game/server/hl2mp/hl2mp_player.cpp b/src/game/server/hl2mp/hl2mp_player.cpp index 9457928a0c5..0ccf03f6600 100644 --- a/src/game/server/hl2mp/hl2mp_player.cpp +++ b/src/game/server/hl2mp/hl2mp_player.cpp @@ -22,6 +22,9 @@ #include "gamestats.h" #include "ammodef.h" #include "NextBot.h" +#ifdef MAPBASE +#include "bot/hl2mp_bot_manager.h" +#endif #include "engine/IEngineSound.h" #include "SoundEmitterSystem/isoundemittersystembase.h" @@ -241,8 +244,16 @@ void CHL2MP_Player::GiveAllItems( void ) void CHL2MP_Player::GiveDefaultItems( void ) { +#ifdef MAPBASE + if (HL2MPRules()->AllowDefaultSuit()) +#endif EquipSuit(); +#ifdef MAPBASE + if (!HL2MPRules()->AllowDefaultItems()) + return; +#endif + CBasePlayer::GiveAmmo( 255, "Pistol"); CBasePlayer::GiveAmmo( 45, "SMG1"); CBasePlayer::GiveAmmo( 1, "grenade" ); @@ -332,6 +343,15 @@ void CHL2MP_Player::PickDefaultSpawnTeam( void ) //----------------------------------------------------------------------------- void CHL2MP_Player::Spawn(void) { +#ifdef MAPBASE + // If this is a bot being created to take over a player, or vice versa, then only perform base spawn + if ( TheHL2MPBots().IsPerformingBotTakeover() ) + { + BaseClass::Spawn(); + return; + } +#endif + m_flNextModelChangeTime = 0.0f; m_flNextTeamChangeTime = 0.0f; @@ -605,6 +625,10 @@ void CHL2MP_Player::PreThink( void ) BaseClass::PreThink(); State_PreThink(); +#ifdef MAPBASE + CheckForIdle(); +#endif + //Reset bullet force accumulator, only lasts one frame m_vecTotalBulletForce = vec3_origin; SetLocalAngles( vOldAngles ); @@ -1053,6 +1077,10 @@ bool CHL2MP_Player::HandleCommand_JoinTeam( int team ) bool CHL2MP_Player::ClientCommand( const CCommand &args ) { +#ifdef MAPBASE + m_flLastAction = gpGlobals->curtime; +#endif + if ( FStrEq( args[0], "spectate" ) ) { if ( ShouldRunRateLimitedCommand( args ) ) @@ -1630,6 +1658,10 @@ bool CHL2MP_Player::StartObserverMode(int mode) //we only want to go into observer mode if the player asked to, not on a death timeout if ( m_bEnterObserver == true ) { +#ifdef MAPBASE + if (!GetBotTakeOverAvatar()) + m_flLastAction = gpGlobals->curtime; +#endif VPhysicsDestroyObject(); return BaseClass::StartObserverMode( mode ); } @@ -1684,6 +1716,15 @@ void CHL2MP_Player::State_Enter_ACTIVE() // RemoveSolidFlags( FSOLID_NOT_SOLID ); m_Local.m_iHideHUD = 0; + +#ifdef MAPBASE + m_flLastAction = gpGlobals->curtime; + + if (GetBotTakeOverAvatar() && !IsFakeClient()) + { + TheHL2MPBots().PlayerTakeOverBot( this, (CHL2MPBot*)GetBotTakeOverAvatar() ); + } +#endif } @@ -1745,3 +1786,103 @@ bool CHL2MP_Player::IsThreatFiringAtMe( CBaseEntity* threat ) const return false; } + +#ifdef MAPBASE +ConVar mp_idledealmethod( "mp_idledealmethod", "0", FCVAR_GAMEDLL, "Deals with Idle Players. 1 = Sends them into Spectator mode then kicks them if they're still idle, 2 = Kicks them out of the game, 3 = Sends them into Spectator and temporarily adds a bot in their place;" ); +ConVar mp_idlemaxtime( "mp_idlemaxtime", "3", FCVAR_GAMEDLL, "Maximum time a player is allowed to be idle (in minutes)" ); + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CHL2MP_Player::CheckForIdle( void ) +{ + if ( m_afButtonLast != m_nButtons ) + m_flLastAction = gpGlobals->curtime; + + //if ( mp_idledealmethod.GetInt() ) + { + if ( IsHLTV() || IsReplay() ) + return; + + if ( IsFakeClient() ) + return; + + if ( HL2MPRules() && HL2MPRules()->IsIntermission() ) + return; + + // Assign AFK + { + // Cannot possibly get out of the spawn room in 0 seconds--so if the ConVar says 0, let's assume 30 seconds. + float flIdleTime = Max( mp_idlemaxtime.GetFloat() * 60, 30.0f ); + + m_bIsAFK = (gpGlobals->curtime - m_flLastAction) > flIdleTime; + } + + if ( m_bIsAFK == true && mp_idledealmethod.GetInt() ) + { + if (mp_idledealmethod.GetInt() != 3) + { + //Don't mess with the host on a listen server (probably one of us debugging something) + if (engine->IsDedicatedServer() == false && entindex() == 1) + return; + + if (IsAutoKickDisabled()) + return; + } + + bool bKickPlayer = false; + + ConVarRef mp_allowspectators( "mp_allowspectators" ); + if ( mp_allowspectators.IsValid() && mp_allowspectators.GetBool() == false ) + { + // just kick the player if this server doesn't allow spectators + bKickPlayer = true; + } + else if ( mp_idledealmethod.GetInt() == 1 ) + { + //if ( GetTeamNumber() < FIRST_GAME_TEAM ) + if ( GetTeamNumber() == TEAM_SPECTATOR ) + { + bKickPlayer = true; + } + else + { + //First send them into spectator mode then kick him. + ChangeTeam( TEAM_SPECTATOR ); + m_flLastAction = gpGlobals->curtime; + return; + } + } + else if ( mp_idledealmethod.GetInt() == 2 ) + { + bKickPlayer = true; + } + else if ( mp_idledealmethod.GetInt() == 3 ) + { + if ( GetTeamNumber() != TEAM_SPECTATOR ) + { + UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "#game_idle_spectator", GetPlayerName() ); + HL2MPRules()->PlayerIdle( this ); + + if (TheHL2MPBots().CanDoBotTakeover() && TheHL2MPBots().CanDoBotTakeoverOn( this )) + { + TheHL2MPBots().HideBotsJoining( 1 ); + TheHL2MPBots().BotTakeOverPlayer( this ); + } + else + { + ChangeTeam( TEAM_SPECTATOR ); + } + } + } + + if ( bKickPlayer == true ) + { + UTIL_ClientPrintAll( HUD_PRINTCONSOLE, "#game_idle_kick", GetPlayerName() ); + engine->ServerCommand( UTIL_VarArgs( "kickid %d %s\n", GetUserID(), "#game_idle_kicked" ) ); + m_flLastAction = gpGlobals->curtime; + } + } + } +} +#endif diff --git a/src/game/server/hl2mp/hl2mp_player.h b/src/game/server/hl2mp/hl2mp_player.h index 5fad238637f..d82662960de 100644 --- a/src/game/server/hl2mp/hl2mp_player.h +++ b/src/game/server/hl2mp/hl2mp_player.h @@ -107,6 +107,13 @@ class CHL2MP_Player : public CHL2_Player const char *GetPlayerModelSoundPrefix( void ); int GetPlayerModelType( void ) { return m_iPlayerSoundType; } +#ifdef MAPBASE + // TF2-style AFK checking + void CheckForIdle( void ); + void ResetIdleCheck( void ) { m_flLastAction = gpGlobals->curtime; } + bool IsAwayFromKeyboard( void ) const { return m_bIsAFK; } +#endif + int GetMaxAmmo( int iAmmoIndex ) const; void DetonateTripmines( void ); @@ -143,6 +150,12 @@ class CHL2MP_Player : public CHL2_Player bool IsThreatAimingTowardMe( CBaseEntity* threat, float cosTolerance = 0.8f ) const; bool IsThreatFiringAtMe( CBaseEntity* threat ) const; + +#ifdef MAPBASE + void SetBotTakeOverAvatar( CHL2MP_Player *pAvatar ) { m_pBotTakeOverAvatar = pAvatar; } + CHL2MP_Player *GetBotTakeOverAvatar() const { return m_pBotTakeOverAvatar; } +#endif + private: CNetworkQAngle( m_angEyeAngles ); @@ -168,6 +181,14 @@ class CHL2MP_Player : public CHL2_Player bool m_bEnterObserver; bool m_bReady; + +#ifdef MAPBASE + bool m_bIsAFK; + float m_flLastAction; + + // Used by bot takeover. If we are a bot, this is our real player. If we are the real player, this is the bot that's taken us over. + CHL2MP_Player *m_pBotTakeOverAvatar; +#endif }; inline CHL2MP_Player *ToHL2MPPlayer( CBaseEntity *pEntity ) diff --git a/src/game/shared/hl2mp/hl2mp_gamerules.cpp b/src/game/shared/hl2mp/hl2mp_gamerules.cpp index 87a3cf6e3b3..d3d40aeca8c 100644 --- a/src/game/shared/hl2mp/hl2mp_gamerules.cpp +++ b/src/game/shared/hl2mp/hl2mp_gamerules.cpp @@ -33,6 +33,9 @@ #include "voice_gamemgr.h" #include "hl2mp_gameinterface.h" #include "hl2mp_cvars.h" +#ifdef MAPBASE + #include "bot/hl2mp_bot_manager.h" +#endif extern void respawn(CBaseEntity *pEdict, bool fCopyCorpse); @@ -286,6 +289,62 @@ void CHL2MPRules::PlayerKilled( CBasePlayer *pVictim, const CTakeDamageInfo &inf #endif } +#if defined(MAPBASE) && !defined(CLIENT_DLL) +void CHL2MPRules::PlayerSpawn( CBasePlayer *pPlayer ) +{ + // Don't spawn with items when a bot is taking over a player, or vice versa + if (TheHL2MPBots().IsPerformingBotTakeover()) + return; + + BaseClass::PlayerSpawn( pPlayer ); +} + +void CHL2MPRules::PlayerIdle( CBasePlayer *pPlayer ) +{ + IGameEvent *event = gameeventmanager->CreateEvent( "player_afk" ); + if( event ) + { + event->SetInt( "userid", pPlayer->GetUserID() ); + event->SetInt( "priority", 5 ); + gameeventmanager->FireEvent( event ); + } + + //BaseClass::PlayerIdle( pPlayer ); +} + +//----------------------------------------------------------------------------- +// Gets our default items setting. +//----------------------------------------------------------------------------- +bool CHL2MPRules::AllowDefaultItems() +{ + return m_bAllowDefaultItems; +} + +//----------------------------------------------------------------------------- +// Sets our default items setting. +//----------------------------------------------------------------------------- +void CHL2MPRules::SetAllowDefaultItems( bool toggle ) +{ + m_bAllowDefaultItems = toggle; +} + +//----------------------------------------------------------------------------- +// Gets our default suit setting. +//----------------------------------------------------------------------------- +bool CHL2MPRules::AllowDefaultSuit() +{ + return m_bAllowDefaultSuit; +} + +//----------------------------------------------------------------------------- +// Sets our default suit setting. +//----------------------------------------------------------------------------- +void CHL2MPRules::SetAllowDefaultSuit( bool toggle ) +{ + m_bAllowDefaultSuit = toggle; +} +#endif + void CHL2MPRules::Think( void ) { @@ -641,6 +700,29 @@ void CHL2MPRules::ClientDisconnected( edict_t *pClient ) CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pClient ); if ( pPlayer ) { +#ifdef MAPBASE + CHL2MP_Player *pHL2MPPlayer = ToHL2MPPlayer( pPlayer ); + if ( pHL2MPPlayer && pHL2MPPlayer->GetBotTakeOverAvatar() ) + { + if ( pHL2MPPlayer->IsFakeClient() ) + { + // This was a bot taking over for a real player. Add the real player back in before we leave + CHL2MP_Player *pAvatar = pHL2MPPlayer->GetBotTakeOverAvatar(); + TheHL2MPBots().PlayerTakeOverBot( pAvatar, (CHL2MPBot *)pHL2MPPlayer, false ); + + // Remove flags set in CServerGameClients::ClientDisconnect + pAvatar->AddFlag( FL_AIMTARGET ); + pAvatar->RemoveFlag( FL_DONTTOUCH | FL_NOTARGET ); + pAvatar->RemoveSolidFlags( FSOLID_NOT_SOLID ); + } + else + { + // This was a real player with a bot taking over. Kick the bot + engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", pHL2MPPlayer->GetBotTakeOverAvatar()->GetUserID() ) ); + } + } +#endif + // Remove the player from his team if ( pPlayer->GetTeam() ) { diff --git a/src/game/shared/hl2mp/hl2mp_gamerules.h b/src/game/shared/hl2mp/hl2mp_gamerules.h index 78da2179dec..99551316566 100644 --- a/src/game/shared/hl2mp/hl2mp_gamerules.h +++ b/src/game/shared/hl2mp/hl2mp_gamerules.h @@ -135,6 +135,17 @@ class CHL2MPRules : public CTeamplayRules void ManageObjectRelocation( void ); void CheckChatForReadySignal( CHL2MP_Player *pPlayer, const char *chatmsg ); const char *GetChatFormat( bool bTeamOnly, CBasePlayer *pPlayer ); + +#ifdef MAPBASE + void PlayerSpawn( CBasePlayer *pPlayer ); + void PlayerIdle( CBasePlayer *pPlayer ); + + bool AllowDefaultItems(); + void SetAllowDefaultItems( bool toggle ); + + bool AllowDefaultSuit(); + void SetAllowDefaultSuit( bool toggle ); +#endif #endif @@ -166,6 +177,13 @@ class CHL2MPRules : public CTeamplayRules #ifndef CLIENT_DLL bool m_bChangelevelDone; + +#ifdef MAPBASE + bool m_bAllowDefaultItems = true; + bool m_bAllowDefaultSuit = true; + + CNetworkVar( bool, m_bHideBotJoinGame ); // Hides when bots connect and disconnect from the game +#endif #endif }; diff --git a/src/game/shared/mapbase/mapbase_usermessages.cpp b/src/game/shared/mapbase/mapbase_usermessages.cpp index f0a71fd410a..fe1c9599c38 100644 --- a/src/game/shared/mapbase/mapbase_usermessages.cpp +++ b/src/game/shared/mapbase/mapbase_usermessages.cpp @@ -32,6 +32,10 @@ void RegisterMapbaseUserMessages( void ) usermessages->Register( "ShowMenuComplex", -1 ); // CHudMenu +#ifdef HL2MP + usermessages->Register( "HideBotsJoin", 1 ); // ClientModeHL2MPNormal +#endif + #ifdef CLIENT_DLL // TODO: Better placement? HookMapbaseUserMessages(); diff --git a/src/game/shared/multiplay_gamerules.cpp b/src/game/shared/multiplay_gamerules.cpp index 6ed275b253f..7c3a67ab2c8 100644 --- a/src/game/shared/multiplay_gamerules.cpp +++ b/src/game/shared/multiplay_gamerules.cpp @@ -680,6 +680,9 @@ ConVarRef suitcharger( "sk_suitcharger" ); bool addDefault; CBaseEntity *pWeaponEntity = NULL; +#ifdef MAPBASE + if (AllowDefaultSuit()) +#endif pPlayer->EquipSuit(); addDefault = true; diff --git a/src/game/shared/multiplay_gamerules.h b/src/game/shared/multiplay_gamerules.h index b297aefcef2..c7854f6bd74 100644 --- a/src/game/shared/multiplay_gamerules.h +++ b/src/game/shared/multiplay_gamerules.h @@ -168,6 +168,10 @@ class CMultiplayRules : public CGameRules virtual bool CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ); virtual void PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ); +#ifdef MAPBASE + virtual bool AllowDefaultSuit() { return true; } +#endif + // Item spawn/respawn control virtual int ItemShouldRespawn( CItem *pItem ); virtual float FlItemRespawnTime( CItem *pItem );