diff --git a/core/PlayerManager.cpp b/core/PlayerManager.cpp index 2df4d4a2..9fc5ddb3 100644 --- a/core/PlayerManager.cpp +++ b/core/PlayerManager.cpp @@ -44,6 +44,7 @@ #include "HalfLife2.h" #include #include +#include #include #include "ExtensionSys.h" #include @@ -113,6 +114,9 @@ PlayerManager::PlayerManager() m_FirstPass = false; m_maxClients = 0; + m_SourceTVUserId = -1; + m_ReplayUserId = -1; + m_UserIdLookUp = new int[USHRT_MAX+1]; memset(m_UserIdLookUp, 0, sizeof(int) * (USHRT_MAX+1)); } @@ -232,8 +236,20 @@ ConfigResult PlayerManager::OnSourceModConfigChanged(const char *key, void PlayerManager::OnServerActivate(edict_t *pEdictList, int edictCount, int clientMax) { + static ConVar *tv_enable = icvar->FindVar("tv_enable"); +#if SOURCE_ENGINE == SE_ORANGEBOXVALVE + static ConVar *replay_enable = icvar->FindVar("replay_enable"); +#endif + // clientMax will not necessarily be correct here (such as on late SourceTV enable) m_maxClients = gpGlobals->maxClients; + + m_bIsSourceTVActive = (tv_enable && tv_enable->GetBool() && CommandLine()->FindParm("-nohltv") == 0); + m_bIsReplayActive = false; +#if SOURCE_ENGINE == SE_ORANGEBOXVALVE + m_bIsReplayActive = (replay_enable && replay_enable->GetBool()); +#endif + m_PlayersSinceActive = 0; if (!m_FirstPass) { @@ -411,6 +427,7 @@ bool PlayerManager::OnClientConnect(edict_t *pEntity, const char *pszName, const { int client = IndexOfEdict(pEntity); CPlayer *pPlayer = &m_Players[client]; + ++m_PlayersSinceActive; pPlayer->Initialize(pszName, pszAddress, pEntity); @@ -518,6 +535,40 @@ void PlayerManager::OnClientPutInServer(edict_t *pEntity, const char *playername const char *authid = engine->GetPlayerNetworkIDString(pEntity); pPlayer->Authorize(authid); pPlayer->m_bFakeClient = true; + + /* + * While we're already filtered to just bots, we'll do other checks to + * make sure that the requisite services are enabled and that the bots + * have joined at the expected time. + * + * Checking playerinfo's IsHLTV and IsReplay would be better and less + * error-prone but will always show false until later in the frame, + * after PutInServer and Activate, and we want it now! + */ + + // This doesn't actually get incremented until OnClientConnect. Fake it to check. + int newCount = m_PlayersSinceActive + 1; + int userId = engine->GetPlayerUserId(pEntity); + +#if SOURCE_ENGINE == SE_ORANGEBOXVALVE + if (m_bIsReplayActive && newCount == 1 + && (m_ReplayUserId == userId || strcmp(playername, "Replay") == 0)) + { + pPlayer->m_bIsReplay = true; + m_ReplayUserId = userId; + } +#endif + + if (m_bIsSourceTVActive + && ((!m_bIsReplayActive && newCount == 1) + || (m_bIsReplayActive && newCount == 2)) + && (m_SourceTVUserId == userId || strcmp(playername, "SourceTV") == 0) + ) + { + pPlayer->m_bIsSourceTV = true; + m_SourceTVUserId = userId; + } + if (!OnClientConnect(pEntity, playername, "127.0.0.1", error, sizeof(error))) { /* :TODO: kick the bot if it's rejected */ @@ -1452,6 +1503,8 @@ CPlayer::CPlayer() m_LastPassword.clear(); m_LangId = SOURCEMOD_LANGUAGE_ENGLISH; m_bFakeClient = false; + m_bIsSourceTV = false; + m_bIsReplay = false; m_Serial.value = -1; } @@ -1524,6 +1577,8 @@ void CPlayer::Disconnect() m_UserId = -1; m_bIsInKickQueue = false; m_bFakeClient = false; + m_bIsSourceTV = false; + m_bIsReplay = false; m_Serial.value = -1; } @@ -1592,6 +1647,16 @@ bool CPlayer::IsFakeClient() return m_bFakeClient; } +bool CPlayer::IsSourceTV() const +{ + return m_bIsSourceTV; +} + +bool CPlayer::IsReplay() const +{ + return m_bIsReplay; +} + void CPlayer::SetAdminId(AdminId id, bool temporary) { if (!m_IsConnected) diff --git a/core/PlayerManager.h b/core/PlayerManager.h index ce31287c..ba90352d 100644 --- a/core/PlayerManager.h +++ b/core/PlayerManager.h @@ -76,6 +76,8 @@ public: bool IsConnected(); bool IsAuthorized(); bool IsFakeClient(); + bool IsSourceTV() const; + bool IsReplay() const; void SetAdminId(AdminId id, bool temporary); AdminId GetAdminId(); void Kick(const char *str); @@ -119,6 +121,8 @@ private: unsigned int m_LangId; int m_UserId; bool m_bFakeClient; + bool m_bIsSourceTV; + bool m_bIsReplay; serial_t m_Serial; }; @@ -208,12 +212,17 @@ private: int *m_UserIdLookUp; int m_maxClients; int m_PlayerCount; + int m_PlayersSinceActive; bool m_FirstPass; unsigned int *m_AuthQueue; String m_PassInfoVar; bool m_QueryLang; bool m_bIsListenServer; int m_ListenClient; + bool m_bIsSourceTVActive; + bool m_bIsReplayActive; + int m_SourceTVUserId; + int m_ReplayUserId; }; #if SOURCE_ENGINE >= SE_ORANGEBOX diff --git a/core/smn_player.cpp b/core/smn_player.cpp index b3bac734..5d7e328f 100644 --- a/core/smn_player.cpp +++ b/core/smn_player.cpp @@ -200,6 +200,40 @@ static cell_t sm_IsClientFakeClient(IPluginContext *pCtx, const cell_t *params) return (pPlayer->IsFakeClient()) ? 1 : 0; } +static cell_t sm_IsClientSourceTV(IPluginContext *pCtx, const cell_t *params) +{ + int index = params[1]; + if ((index < 1) || (index > g_Players.GetMaxClients())) + { + return pCtx->ThrowNativeError("Client index %d is invalid", index); + } + + CPlayer *pPlayer = g_Players.GetPlayerByIndex(index); + if (!pPlayer->IsConnected()) + { + return pCtx->ThrowNativeError("Client %d is not connected", index); + } + + return (pPlayer->IsSourceTV()) ? 1 : 0; +} + +static cell_t sm_IsClientReplay(IPluginContext *pCtx, const cell_t *params) +{ + int index = params[1]; + if ((index < 1) || (index > g_Players.GetMaxClients())) + { + return pCtx->ThrowNativeError("Client index %d is invalid", index); + } + + CPlayer *pPlayer = g_Players.GetPlayerByIndex(index); + if (!pPlayer->IsConnected()) + { + return pCtx->ThrowNativeError("Client %d is not connected", index); + } + + return (pPlayer->IsReplay()) ? 1 : 0; +} + static cell_t IsClientObserver(IPluginContext *pContext, const cell_t *params) { int client = params[1]; @@ -1605,6 +1639,8 @@ REGISTER_NATIVES(playernatives) {"IsClientAuthorized", sm_IsClientAuthorized}, {"IsClientConnected", sm_IsClientConnected}, {"IsFakeClient", sm_IsClientFakeClient}, + {"IsClientSourceTV", sm_IsClientSourceTV}, + {"IsClientReplay", sm_IsClientReplay}, {"IsClientInGame", sm_IsClientInGame}, {"IsClientObserver", IsClientObserver}, {"RemoveUserFlags", RemoveUserFlags}, diff --git a/plugins/include/clients.inc b/plugins/include/clients.inc index 314ae96d..2a28f894 100644 --- a/plugins/include/clients.inc +++ b/plugins/include/clients.inc @@ -314,6 +314,22 @@ native bool:IsClientAuthorized(client); */ native bool:IsFakeClient(client); +/** + * Returns if a certain player is the SourceTV bot. + * + * @param client Player index. + * @return True if player is the SourceTV bot, false otherwise. + */ +native bool:IsClientSourceTV(client); + +/** + * Returns if a certain player is the Replay bot. + * + * @param client Player index. + * @return True if player is the Replay bot, false otherwise. + */ +native bool:IsClientReplay(client); + /** * Returns if a certain player is an observer/spectator. * diff --git a/public/IPlayerHelpers.h b/public/IPlayerHelpers.h index 54855899..7c9055f1 100644 --- a/public/IPlayerHelpers.h +++ b/public/IPlayerHelpers.h @@ -41,7 +41,7 @@ #include #define SMINTERFACE_PLAYERMANAGER_NAME "IPlayerManager" -#define SMINTERFACE_PLAYERMANAGER_VERSION 14 +#define SMINTERFACE_PLAYERMANAGER_VERSION 15 struct edict_t; class IPlayerInfo; @@ -222,6 +222,20 @@ namespace SourceMod virtual void MarkAsBeingKicked() =0; virtual void SetLanguageId(unsigned int id) =0; + + /** + * @brief Returns whether the player is the SourceTV bot. + * + * @return True if the SourceTV bot, false otherwise. + */ + virtual bool IsSourceTV() const =0; + + /** + * @brief Returns whether the player is the Replay bot. + * + * @return True if the Replay bot, false otherwise. + */ + virtual bool IsReplay() const =0; }; /**