From 00f47c18c4e1b380316e540d9aa643d91dc997d6 Mon Sep 17 00:00:00 2001 From: fisted Date: Thu, 3 Feb 2011 13:02:32 +0100 Subject: [PATCH 1/2] player muting by ip --- src/game/server/gamecontext.cpp | 122 ++++++++++++++++++++++++++++++++ src/game/server/gamecontext.h | 14 ++++ 2 files changed, 136 insertions(+) diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index d0e13f47c..60af8855a 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -14,6 +14,8 @@ #include "gamemodes/ctf.h" #include "gamemodes/mod.h" +struct CMute CGameContext::m_aMutes[MAX_MUTES]; + enum { RESET, @@ -34,7 +36,11 @@ void CGameContext::Construct(int Resetting) m_pVoteOptionLast = 0; if(Resetting==NO_RESET) + { m_pVoteOptionHeap = new CHeap(); + for(int z = 0; z < MAX_MUTES; ++z) + m_aMutes[z].m_IP[0] = 0; + } } CGameContext::CGameContext(int Resetting) @@ -577,6 +583,23 @@ void CGameContext::OnMessage(int MsgId, CUnpacker *pUnpacker, int ClientId) p->m_Last_Chat = Server()->Tick(); + char aIP[16]; + int MuteTicks = 0; + + Server()->GetClientIP(ClientId, aIP, sizeof aIP); + + for(int z = 0; z < MAX_MUTES && MuteTicks <= 0; ++z) //find a mute, remove it, if expired. + if (m_aMutes[z].m_IP[0] && str_comp(aIP, m_aMutes[z].m_IP) == 0 && (MuteTicks = m_aMutes[z].m_Expire - Server()->Tick()) <= 0) + m_aMutes[z].m_IP[0] = 0; + + if (MuteTicks > 0) + { + char aBuf[128]; + str_format(aBuf, sizeof aBuf, "You are not permitted to talk for the next %d seconds.", MuteTicks / Server()->TickSpeed()); + SendChatTarget(ClientId, aBuf); + return; + } + // check for invalid chars unsigned char *pMessage = (unsigned char *)pMsg->m_pMessage; while (*pMessage) @@ -992,6 +1015,67 @@ void CGameContext::ConVote(IConsole::IResult *pResult, void *pUserData) pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); } +void CGameContext::ConMute(IConsole::IResult *pResult, void *pUserData) +{ + ((CGameContext *)pUserData)->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "Use either 'muteid ' or 'muteip '"); +} + +// mute through client id +void CGameContext::ConMuteID(IConsole::IResult *pResult, void *pUserData) +{ + CGameContext *pSelf = (CGameContext *)pUserData; + int ClientId = pResult->GetInteger(0); + if (ClientId < 0 || ClientId >= MAX_CLIENTS || !pSelf->m_apPlayers[ClientId]) + return; + + char aIP[16]; + pSelf->Server()->GetClientIP(ClientId, aIP, sizeof aIP); + + pSelf->Mute(aIP, clamp(pResult->GetInteger(1), 1, 60*60*24*365), pSelf->Server()->ClientName(ClientId)); +} + +// mute through ip, arguments reversed to workaround parsing +void CGameContext::ConMuteIP(IConsole::IResult *pResult, void *pUserData) +{ + CGameContext *pSelf = (CGameContext *)pUserData; + pSelf->Mute(pResult->GetString(0), clamp(pResult->GetInteger(1), 1, 60*60*24*365), pResult->GetString(0)); +} + +// unmute by mute list index +void CGameContext::ConUnmute(IConsole::IResult *pResult, void *pUserData) +{ + char aBuf[32]; + CGameContext *pSelf = (CGameContext *)pUserData; + int Ind = pResult->GetInteger(0); + + if (Ind < 0 || Ind >= MAX_MUTES || !m_aMutes[Ind].m_IP[0]) + return; + + str_format(aBuf, sizeof aBuf, "Unmuted %s" , m_aMutes[Ind].m_IP); + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); + + m_aMutes[Ind].m_IP[0] = 0; +} + +// list mutes +void CGameContext::ConMutes(IConsole::IResult *pResult, void *pUserData) +{ + char aBuf[128]; + CGameContext *pSelf = (CGameContext *)pUserData; + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "Active mutes:"); + for(int z = 0; z < MAX_MUTES; ++z) + if (m_aMutes[z].m_IP[0]) + { + int Exp = (m_aMutes[z].m_Expire - pSelf->Server()->Tick()) / pSelf->Server()->TickSpeed(); + if (Exp > 0) + str_format(aBuf, sizeof aBuf, "%d: \"%s\", %d seconds left", z, m_aMutes[z].m_IP, Exp); + else + str_format(aBuf, sizeof aBuf, "%d: \"%s\", expired (pending)", z, m_aMutes[z].m_IP); + + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf); + } +} + void CGameContext::ConchainSpecialMotdupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) { pfnCallback(pResult, pCallbackUserData); @@ -1026,6 +1110,12 @@ void CGameContext::OnConsoleInit() Console()->Register("clear_votes", "", CFGFLAG_SERVER, ConClearVotes, this, ""); Console()->Register("vote", "r", CFGFLAG_SERVER, ConVote, this, ""); + Console()->Register("mute", "", CFGFLAG_SERVER, ConMute, this, ""); + Console()->Register("muteid", "ii", CFGFLAG_SERVER, ConMuteID, this, ""); + Console()->Register("muteip", "si", CFGFLAG_SERVER, ConMuteIP, this, ""); + Console()->Register("unmute", "i", CFGFLAG_SERVER, ConUnmute, this, ""); + Console()->Register("mutes", "", CFGFLAG_SERVER, ConMutes, this, ""); + Console()->Chain("sv_motd", ConchainSpecialMotdupdate, this); } @@ -1134,3 +1224,35 @@ const char *CGameContext::Version() { return GAME_VERSION; } const char *CGameContext::NetVersion() { return GAME_NETVERSION; } IGameServer *CreateGameServer() { return new CGameContext; } + +void CGameContext::Mute(const char *pIP, int Secs, const char *pDisplayName) +{ + char aBuf[128]; + // purge expired mutes first + for(int z = 0; z < MAX_MUTES; ++z) + if (m_aMutes[z].m_IP[0] && m_aMutes[z].m_Expire <= Server()->Tick()) + m_aMutes[z].m_IP[0] = 0; + + int FoundInd = -1; + // find a matching mute for this ip, update expiration time if found + for(int z = 0; z < MAX_MUTES; ++z) + if (m_aMutes[z].m_IP[0] && str_comp(m_aMutes[z].m_IP, pIP) == 0) + m_aMutes[FoundInd = z].m_Expire = Server()->Tick() + Secs * Server()->TickSpeed(); + + if (FoundInd == -1) // nothing found so far, find a free slot.. + for(int z = 0; z < MAX_MUTES; ++z) //..in order to add a new mute + if (!m_aMutes[z].m_IP[0]) + { + str_copy(m_aMutes[z].m_IP, pIP, str_length(pIP)+1); + m_aMutes[FoundInd = z].m_Expire = Server()->Tick() + Secs * Server()->TickSpeed(); + break; + } + + if (FoundInd != -1) + { + str_format(aBuf, sizeof aBuf, "'%s' has been muted for %d seconds.", pDisplayName, Secs); + SendChat(-1, CHAT_ALL, aBuf); + } + else // no free slot found + Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "mute array is full"); +} diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index 99e7e56db..d8be7b29f 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -14,6 +14,8 @@ #include "gameworld.h" #include "player.h" +#define MAX_MUTES 32 + /* Tick Game Context (CGameContext::tick) @@ -35,6 +37,10 @@ All players (CPlayer::snap) */ +struct CMute { + char m_IP[16];// TODO ipv6 + int m_Expire; +}; class CGameContext : public IGameServer { IServer *m_pServer; @@ -56,12 +62,20 @@ class CGameContext : public IGameServer static void ConAddVote(IConsole::IResult *pResult, void *pUserData); static void ConClearVotes(IConsole::IResult *pResult, void *pUserData); static void ConVote(IConsole::IResult *pResult, void *pUserData); + static void ConMute(IConsole::IResult *pResult, void *pUserData); + static void ConMuteID(IConsole::IResult *pResult, void *pUserData); + static void ConMuteIP(IConsole::IResult *pResult, void *pUserData); + static void ConUnmute(IConsole::IResult *pResult, void *pUserData); + static void ConMutes(IConsole::IResult *pResult, void *pUserData); static void ConchainSpecialMotdupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); CGameContext(int Resetting); void Construct(int Resetting); bool m_Resetting; + + static struct CMute m_aMutes[MAX_MUTES]; + void Mute(const char *pIP, int Secs, const char *pDisplayName); public: IServer *Server() const { return m_pServer; } class IConsole *Console() { return m_pConsole; } From addbf8e3d60dce4c4e73a15d01638cf79e25b0de Mon Sep 17 00:00:00 2001 From: fisted Date: Thu, 3 Feb 2011 14:39:00 +0100 Subject: [PATCH 2/2] automatic mute on spam --- src/game/server/gamecontext.cpp | 9 +++++++++ src/game/server/player.cpp | 4 ++++ src/game/server/player.h | 2 ++ src/game/variables.h | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 60af8855a..bcba0208d 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -600,6 +600,15 @@ void CGameContext::OnMessage(int MsgId, CUnpacker *pUnpacker, int ClientId) return; } + if ((p->m_ChatScore += g_Config.m_SvChatPenalty) > g_Config.m_SvChatThreshold) + { + char aIP[16]; + Server()->GetClientIP(ClientId, aIP, sizeof aIP); + Mute(aIP, g_Config.m_SvSpamMuteDuration, Server()->ClientName(ClientId)); + p->m_ChatScore = 0; + return; + } + // check for invalid chars unsigned char *pMessage = (unsigned char *)pMsg->m_pMessage; while (*pMessage) diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp index 3f32d6af1..c2cdb82bb 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -19,6 +19,7 @@ CPlayer::CPlayer(CGameContext *pGameServer, int CID, int Team) this->m_ClientID = CID; m_Team = GameServer()->m_pController->ClampTeam(Team); m_LastActionTick = Server()->Tick(); + m_ChatScore = 0; } CPlayer::~CPlayer() @@ -35,6 +36,9 @@ void CPlayer::Tick() if(!Server()->ClientIngame(m_ClientID)) return; + if (m_ChatScore > 0) + m_ChatScore--; + Server()->SetClientScore(m_ClientID, m_Score); // do latency stuff diff --git a/src/game/server/player.h b/src/game/server/player.h index c4c413fc1..2bb07d2f9 100644 --- a/src/game/server/player.h +++ b/src/game/server/player.h @@ -70,6 +70,8 @@ public: int m_TargetX; int m_TargetY; } m_LatestActivity; + + int m_ChatScore; private: CCharacter *Character; diff --git a/src/game/variables.h b/src/game/variables.h index 0621f6afa..bcf5c6463 100644 --- a/src/game/variables.h +++ b/src/game/variables.h @@ -64,6 +64,11 @@ MACRO_CONFIG_STR(SvGametype, sv_gametype, 32, "dm", CFGFLAG_SERVER, "Game type ( MACRO_CONFIG_INT(SvTournamentMode, sv_tournament_mode, 0, 0, 1, CFGFLAG_SERVER, "Tournament mode. When enabled, players joins the server as spectator") MACRO_CONFIG_INT(SvSpamprotection, sv_spamprotection, 1, 0, 1, CFGFLAG_SERVER, "Spam protection") +// these might need some fine tuning +MACRO_CONFIG_INT(SvChatPenalty, sv_chat_penalty, 250, 50, 1000, CFGFLAG_SERVER, "chatscore will be increased by this on every message, and decremented by 1 on every tick.") +MACRO_CONFIG_INT(SvChatThreshold, sv_chat_threshold, 1000, 50, 10000 , CFGFLAG_SERVER, "if chatscore exceeds this, the player will be muted for sv_spam_mute_duration seconds") +MACRO_CONFIG_INT(SvSpamMuteDuration, sv_spam_mute_duration, 60, 0, 3600 , CFGFLAG_SERVER, "how many seconds to mute, if player triggers mute on spam. 0 = off") + MACRO_CONFIG_INT(SvSpectatorSlots, sv_spectator_slots, 0, 0, MAX_CLIENTS, CFGFLAG_SERVER, "Number of slots to reserve for spectators") MACRO_CONFIG_INT(SvTeambalanceTime, sv_teambalance_time, 1, 0, 1000, CFGFLAG_SERVER, "How many minutes to wait before autobalancing teams") MACRO_CONFIG_INT(SvInactiveKickTime, sv_inactivekick_time, 3, 0, 1000, CFGFLAG_SERVER, "How many minutes to wait before taking care of inactive players")