diff --git a/AMBuilder b/AMBuilder index f61c5142..d5f6d3dc 100644 --- a/AMBuilder +++ b/AMBuilder @@ -62,6 +62,7 @@ for sdk_name in MMSPlugin.sdks: 'src/ctimer.cpp', 'src/playermanager.cpp', 'src/gameconfig.cpp', + 'src/votemanager.cpp', 'src/httpmanager.cpp', ] diff --git a/CS2Fixes.vcxproj b/CS2Fixes.vcxproj index 68c44003..2769e2f5 100644 --- a/CS2Fixes.vcxproj +++ b/CS2Fixes.vcxproj @@ -181,6 +181,7 @@ + @@ -215,6 +216,7 @@ + diff --git a/CS2Fixes.vcxproj.filters b/CS2Fixes.vcxproj.filters index 5dbd9978..f2925f90 100644 --- a/CS2Fixes.vcxproj.filters +++ b/CS2Fixes.vcxproj.filters @@ -98,6 +98,9 @@ Source Files + + Source Files + @@ -196,5 +199,8 @@ Header Files + + Header Files + \ No newline at end of file diff --git a/src/adminsystem.cpp b/src/adminsystem.cpp index 30abe87c..b0132b66 100644 --- a/src/adminsystem.cpp +++ b/src/adminsystem.cpp @@ -39,8 +39,6 @@ CAdminSystem* g_pAdminSystem = nullptr; CUtlMap g_CommandList(0, 0, DefLessFunc(uint32)); -#define ADMIN_PREFIX "Admin %s has " - void PrintSingleAdminAction(const char *pszAdminName, const char *pszTargetName, const char *pszAction, const char *pszAction2 = "") { ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX ADMIN_PREFIX "%s %s%s.", pszAdminName, pszAction, pszTargetName, pszAction2); @@ -839,6 +837,41 @@ CON_COMMAND_CHAT_FLAGS(rcon, "send a command to server console", ADMFLAG_RCON) g_pEngineServer2->ServerCommand(args.ArgS()); } +CON_COMMAND_CHAT_FLAGS(extend, "extend current map (negative value reduces map duration)", ADMFLAG_CHANGEMAP) +{ + if (args.ArgC() < 3) + { + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Usage: !extend "); + return; + } + + int iExtendTime = V_StringToInt32(args[1], 0); + + // CONVAR_TODO + ConVar* cvar = g_pCVar->GetConVar(g_pCVar->FindConVar("mp_timelimit")); + + float flTimelimit; + memcpy(&flTimelimit, &cvar->values, sizeof(flTimelimit)); + + if (gpGlobals->curtime - g_pGameRules->m_flGameStartTime > flTimelimit * 60) + flTimelimit = (gpGlobals->curtime - g_pGameRules->m_flGameStartTime) / 60.0f + iExtendTime; + else + { + if (flTimelimit == 1) + flTimelimit = 0; + flTimelimit += iExtendTime; + } + + if (flTimelimit <= 0) + flTimelimit = 1; + + char buf[32]; + V_snprintf(buf, sizeof(buf), "mp_timelimit %.6f", flTimelimit); + + // CONVAR_TODO + g_pEngineServer2->ServerCommand(buf); +} + bool CAdminSystem::LoadAdmins() { m_vecAdmins.Purge(); diff --git a/src/adminsystem.h b/src/adminsystem.h index eba29c51..64713439 100644 --- a/src/adminsystem.h +++ b/src/adminsystem.h @@ -51,6 +51,7 @@ #define ADMFLAG_CUSTOM11 (1 << 24) // y #define ADMFLAG_ROOT (1 << 25) // z +#define ADMIN_PREFIX "Admin %s has " class CInfractionBase { diff --git a/src/cs2fixes.cpp b/src/cs2fixes.cpp index 336b8f50..87c857f5 100644 --- a/src/cs2fixes.cpp +++ b/src/cs2fixes.cpp @@ -45,6 +45,7 @@ #include "commands.h" #include "eventlistener.h" #include "gameconfig.h" +#include "votemanager.h" #include "httpmanager.h" #include "entity/cgamerules.h" @@ -302,6 +303,24 @@ void CS2Fixes::Hook_StartupServer(const GameSessionConfiguration_t& config, ISou g_bHasTicked = false; RegisterEventListeners(); + + // Disable RTV and Extend votes after map has just started + g_RTVState = ERTVState::MAP_START; + g_ExtendState = EExtendState::MAP_START; + + // Allow RTV and Extend votes after 2 minutes post map start + new CTimer(120.0f, false, []() + { + if (g_RTVState != ERTVState::BLOCKED_BY_ADMIN) + g_RTVState = ERTVState::RTV_ALLOWED; + + if (g_ExtendState != EExtendState::NO_EXTENDS) + g_ExtendState = EExtendState::EXTEND_ALLOWED; + return -1.0f; + }); + + // Set amount of Extends left + g_ExtendsLeft = 1; } void CS2Fixes::Hook_GameServerSteamAPIActivated() diff --git a/src/playermanager.h b/src/playermanager.h index 394abbd3..b12d48d7 100644 --- a/src/playermanager.h +++ b/src/playermanager.h @@ -49,6 +49,8 @@ class ZEPlayer m_iHideDistance = 0; m_bConnected = false; m_iTotalDamage = 0; + m_bVotedRTV = false; + m_bVotedExtend = false; } bool IsFakeClient() { return m_bFakeClient; } @@ -69,6 +71,8 @@ class ZEPlayer void ClearTransmit() { m_shouldTransmit.ClearAll(); } void SetHideDistance(int distance) { m_iHideDistance = distance; } void SetTotalDamage(int damage) { m_iTotalDamage = damage; } + void SetRTVVote(bool bRTVVote) { m_bVotedRTV = bRTVVote; } + void SetExtendVote(bool bExtendVote) { m_bVotedExtend = bExtendVote; } bool IsMuted() { return m_bMuted; } bool IsGagged() { return m_bGagged; } @@ -76,6 +80,8 @@ class ZEPlayer int GetHideDistance() { return m_iHideDistance; } CPlayerSlot GetPlayerSlot() { return m_slot; } int GetTotalDamage() { return m_iTotalDamage; } + bool GetRTVVote() { return m_bVotedRTV; } + bool GetExtendVote() { return m_bVotedExtend; } void OnAuthenticated(); void CheckAdmin(); @@ -93,6 +99,8 @@ class ZEPlayer int m_iHideDistance; CBitVec m_shouldTransmit; int m_iTotalDamage; + bool m_bVotedRTV; + bool m_bVotedExtend; }; class CPlayerManager diff --git a/src/votemanager.cpp b/src/votemanager.cpp new file mode 100644 index 00000000..a6e7ffd5 --- /dev/null +++ b/src/votemanager.cpp @@ -0,0 +1,452 @@ +/** + * ============================================================================= + * CS2Fixes + * Copyright (C) 2023 Source2ZE + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "votemanager.h" +#include "commands.h" +#include "playermanager.h" +#include "ctimer.h" +#include "entity/cgamerules.h" + +#include "tier0/memdbgon.h" + +extern CEntitySystem *g_pEntitySystem; +extern IVEngineServer2 *g_pEngineServer2; +extern CGlobalVars *gpGlobals; +extern CCSGameRules *g_pGameRules; + +ERTVState g_RTVState = ERTVState::MAP_START; +EExtendState g_ExtendState = EExtendState::MAP_START; +int g_ExtendsLeft = 1; + +// CONVAR_TODO +float g_RTVSucceedRatio = 0.6f; +float g_ExtendSucceedRatio = 0.5f; +int g_ExtendTimeToAdd = 20; + +int GetCurrentRTVCount() +{ + int iVoteCount = 0; + + for (int i = 0; i < gpGlobals->maxClients; i++) + { + ZEPlayer* pPlayer = g_playerManager->GetPlayer(i); + + if (pPlayer && pPlayer->GetRTVVote()) + iVoteCount++; + } + + return iVoteCount; +} + +int GetNeededRTVCount() +{ + int iOnlinePlayers = 0.0f; + int iVoteCount = 0; + + for (int i = 0; i < gpGlobals->maxClients; i++) + { + ZEPlayer* pPlayer = g_playerManager->GetPlayer(i); + + if (pPlayer) + { + iOnlinePlayers++; + if (pPlayer->GetRTVVote()) + iVoteCount++; + } + } + + return (int)(iOnlinePlayers * g_RTVSucceedRatio) + 1; +} + +int GetCurrentExtendCount() +{ + int iVoteCount = 0; + + for (int i = 0; i < gpGlobals->maxClients; i++) + { + ZEPlayer* pPlayer = g_playerManager->GetPlayer(i); + + if (pPlayer && pPlayer->GetExtendVote()) + iVoteCount++; + } + + return iVoteCount; +} + +int GetNeededExtendCount() +{ + int iOnlinePlayers = 0.0f; + int iVoteCount = 0; + + for (int i = 0; i < gpGlobals->maxClients; i++) + { + ZEPlayer* pPlayer = g_playerManager->GetPlayer(i); + + if (pPlayer) + { + iOnlinePlayers++; + if (pPlayer->GetExtendVote()) + iVoteCount++; + } + } + + return (int)(iOnlinePlayers * g_ExtendSucceedRatio) + 1; +} + +CON_COMMAND_CHAT(rtv, "Vote to end the current map sooner.") +{ + if (!player) + { + ClientPrint(player, HUD_PRINTCONSOLE, CHAT_PREFIX "You cannot use this command from the server console."); + return; + } + + int iPlayer = player->GetPlayerSlot(); + + ZEPlayer* pPlayer = g_playerManager->GetPlayer(iPlayer); + + // Something has to really go wrong for this to happen + if (!pPlayer) + { + Warning("%s Tried to access a null ZEPlayer!!\n", player->GetPlayerName()); + return; + } + + switch (g_RTVState) + { + case ERTVState::MAP_START: + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "RTV is not open yet."); + return; + case ERTVState::POST_RTV_SUCCESSFULL: + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "RTV vote already succeeded."); + return; + case ERTVState::POST_LAST_ROUND_END: + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "RTV is closed during next map selection."); + return; + case ERTVState::BLOCKED_BY_ADMIN: + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "RTV has been blocked by an Admin."); + return; + } + + int iCurrentRTVCount = GetCurrentRTVCount(); + int iNeededRTVCount = GetNeededRTVCount(); + + if (pPlayer->GetRTVVote()) + { + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You have already rocked the vote (%i voted, %i needed).", iCurrentRTVCount, iNeededRTVCount); + return; + } + + if (iCurrentRTVCount + 1 >= iNeededRTVCount) + { + g_RTVState = ERTVState::POST_RTV_SUCCESSFULL; + ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "RTV succeeded! This is the last round of the map!"); + // CONVAR_TODO + g_pEngineServer2->ServerCommand("mp_timelimit 1"); + + for (int i = 0; i < gpGlobals->maxClients; i++) + { + ZEPlayer* pPlayer2 = g_playerManager->GetPlayer(i); + if (pPlayer2) + pPlayer2->SetRTVVote(false); + } + + return; + } + + pPlayer->SetRTVVote(true); + ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "%s wants to rock the vote (%i voted, %i needed).", player->GetPlayerName(), iCurrentRTVCount+1, iNeededRTVCount); +} + +CON_COMMAND_CHAT(unrtv, "Remove your vote to end the current map sooner.") +{ + if (!player) + { + ClientPrint(player, HUD_PRINTCONSOLE, CHAT_PREFIX "You cannot use this command from the server console."); + return; + } + + int iPlayer = player->GetPlayerSlot(); + + ZEPlayer* pPlayer = g_playerManager->GetPlayer(iPlayer); + + // Something has to really go wrong for this to happen + if (!pPlayer) + { + Warning("%s Tried to access a null ZEPlayer!!\n", player->GetPlayerName()); + return; + } + + if (!pPlayer->GetRTVVote()) + { + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You have not voted to RTV current map."); + return; + } + + pPlayer->SetRTVVote(false); + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You no longer want to RTV current map."); +} + + +CON_COMMAND_CHAT(ve, "Vote to extend the current map.") +{ + if (!player) + { + ClientPrint(player, HUD_PRINTCONSOLE, CHAT_PREFIX "You cannot use this command from the server console."); + return; + } + + int iPlayer = player->GetPlayerSlot(); + + ZEPlayer* pPlayer = g_playerManager->GetPlayer(iPlayer); + + // Something has to really go wrong for this to happen + if (!pPlayer) + { + Warning("%s Tried to access a null ZEPlayer!!\n", player->GetPlayerName()); + return; + } + + switch (g_ExtendState) + { + case EExtendState::MAP_START: + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Extend vote is not open yet."); + return; + case EExtendState::POST_EXTEND_COOLDOWN: + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Extend vote is not open yet."); + return; + case EExtendState::POST_EXTEND_NO_EXTENDS_LEFT: + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "There are no extends left for the current map."); + return; + case EExtendState::POST_LAST_ROUND_END: + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Extend vote is closed during next map selection."); + return; + case EExtendState::NO_EXTENDS: + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Extend vote is not allowed for current map."); + return; + } + + int iCurrentExtendCount = GetCurrentExtendCount(); + int iNeededExtendCount = GetNeededExtendCount(); + + if (pPlayer->GetExtendVote()) + { + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You have already voted to extend the map (%i voted, %i needed).", iCurrentExtendCount, iNeededExtendCount); + return; + } + + if (iCurrentExtendCount + 1 >= iNeededExtendCount) + { + // mimic behaviour of !extend + // CONVAR_TODO + ConVar* cvar = g_pCVar->GetConVar(g_pCVar->FindConVar("mp_timelimit")); + + float flTimelimit; + // type punning + memcpy(&flTimelimit, &cvar->values, sizeof(flTimelimit)); + + if (gpGlobals->curtime - g_pGameRules->m_flGameStartTime > flTimelimit * 60) + flTimelimit = (gpGlobals->curtime - g_pGameRules->m_flGameStartTime) / 60.0f + g_ExtendTimeToAdd; + else + { + if (flTimelimit == 1) + flTimelimit = 0; + flTimelimit += g_ExtendTimeToAdd; + } + + if (flTimelimit <= 0) + flTimelimit = 1; + + char buf[32]; + V_snprintf(buf, sizeof(buf), "mp_timelimit %.6f", flTimelimit + g_ExtendTimeToAdd); + + // CONVAR_TODO + g_pEngineServer2->ServerCommand(buf); + + if (g_ExtendsLeft == 1) + // there are no extends left after a successfull extend vote + g_ExtendState = EExtendState::POST_EXTEND_NO_EXTENDS_LEFT; + else + { + // there's an extend left after a successfull extend vote + g_ExtendState = EExtendState::POST_EXTEND_COOLDOWN; + + // Allow another extend vote after 2 minutes + new CTimer(120.0f, false, []() + { + if (g_ExtendState == EExtendState::POST_EXTEND_COOLDOWN) + g_ExtendState = EExtendState::EXTEND_ALLOWED; + return -1.0f; + }); + } + + for (int i = 0; i < gpGlobals->maxClients; i++) + { + ZEPlayer* pPlayer2 = g_playerManager->GetPlayer(i); + if (pPlayer2) + pPlayer2->SetExtendVote(false); + } + + g_ExtendsLeft--; + ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "Extend vote succeeded! Current map has been extended by %i minutes.", g_ExtendTimeToAdd); + + return; + } + + pPlayer->SetExtendVote(true); + ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX "%s wants to extend the map (%i voted, %i needed).", player->GetPlayerName(), iCurrentExtendCount+1, iNeededExtendCount); +} + +CON_COMMAND_CHAT(unve, "Remove your vote to extend current map.") +{ + if (!player) + { + ClientPrint(player, HUD_PRINTCONSOLE, CHAT_PREFIX "You cannot use this command from the server console."); + return; + } + + int iPlayer = player->GetPlayerSlot(); + + ZEPlayer* pPlayer = g_playerManager->GetPlayer(iPlayer); + + // Something has to really go wrong for this to happen + if (!pPlayer) + { + Warning("%s Tried to access a null ZEPlayer!!\n", player->GetPlayerName()); + return; + } + + if (!pPlayer->GetExtendVote()) + { + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You have not voted to extend current map."); + return; + } + + pPlayer->SetExtendVote(false); + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "You no longer want to extend current map."); +} + +CON_COMMAND_CHAT_FLAGS(blockrtv, "Block the ability for players to vote to end current map sooner.", ADMFLAG_CHANGEMAP) +{ + if (g_RTVState == ERTVState::BLOCKED_BY_ADMIN) + { + if (player) + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "RTV is already blocked."); + else + ConMsg("RTV is already blocked."); + return; + } + + const char* pszCommandPlayerName = player ? player->GetPlayerName() : "Console"; + + g_RTVState = ERTVState::BLOCKED_BY_ADMIN; + + ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX ADMIN_PREFIX "blocked vote for RTV.", pszCommandPlayerName); +} + +CON_COMMAND_CHAT_FLAGS(unblockrtv, "Restore the ability for players to vote to end current map sooner.", ADMFLAG_CHANGEMAP) +{ + if (g_RTVState == ERTVState::RTV_ALLOWED) + { + if (player) + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "RTV is not blocked."); + else + ConMsg("RTV is not blocked."); + return; + } + + const char* pszCommandPlayerName = player ? player->GetPlayerName() : "Console"; + + g_RTVState = ERTVState::RTV_ALLOWED; + + ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX ADMIN_PREFIX "restored vote for RTV.", pszCommandPlayerName); +} + +CON_COMMAND_CHAT_FLAGS(addextend, "Add another extend to the current map for players to vote.", ADMFLAG_CHANGEMAP) +{ + const char* pszCommandPlayerName = player ? player->GetPlayerName() : "Console"; + + if (g_ExtendState == EExtendState::POST_EXTEND_NO_EXTENDS_LEFT || g_ExtendState == EExtendState::NO_EXTENDS) + g_ExtendState = EExtendState::EXTEND_ALLOWED; + + g_ExtendsLeft += 1; + + ClientPrintAll(HUD_PRINTTALK, CHAT_PREFIX ADMIN_PREFIX "allowed for an additional extend.", pszCommandPlayerName); +} + +CON_COMMAND_CHAT(extendsleft, "Display amount of extends left for the current map") +{ + char message[64]; + + switch (g_ExtendsLeft) + { + case 0: + strcpy(message, "There are no extends left."); + break; + case 1: + strcpy(message, "There's 1 extend left"); + break; + default: + V_snprintf(message, sizeof(message), "There are %i extends left.", g_ExtendsLeft); + break; + } + + if (player) + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "%s", message); + else + ConMsg("%s", message); +} + +CON_COMMAND_CHAT(timeleft, "Display time left to end of current map.") +{ + if (!player) + { + ClientPrint(player, HUD_PRINTCONSOLE, CHAT_PREFIX "You cannot use this command from the server console."); + return; + } + + // CONVAR_TODO + ConVar* cvar = g_pCVar->GetConVar(g_pCVar->FindConVar("mp_timelimit")); + + float flTimelimit; + memcpy(&flTimelimit, &cvar->values, sizeof(flTimelimit)); + + if (flTimelimit == 0) + { + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "No time limit"); + return; + } + + int iTimeleft = (int) ((g_pGameRules->m_flGameStartTime + flTimelimit * 60.0f) - gpGlobals->curtime); + + if (iTimeleft < 0) + { + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Last round!"); + return; + } + + div_t div = std::div(iTimeleft, 60); + int iMinutesLeft = div.quot; + int iSecondsLeft = div.rem; + + if (iMinutesLeft > 0) + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Timeleft: %i minutes %i seconds", iMinutesLeft, iSecondsLeft); + else + ClientPrint(player, HUD_PRINTTALK, CHAT_PREFIX "Timeleft: %i seconds", iSecondsLeft); +} diff --git a/src/votemanager.h b/src/votemanager.h new file mode 100644 index 00000000..f92a7e9e --- /dev/null +++ b/src/votemanager.h @@ -0,0 +1,46 @@ + +/** + * ============================================================================= + * CS2Fixes + * Copyright (C) 2023 Source2ZE + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +enum class ERTVState +{ + MAP_START, + RTV_ALLOWED, + POST_RTV_SUCCESSFULL, + POST_LAST_ROUND_END, + BLOCKED_BY_ADMIN, +}; + +enum class EExtendState +{ + MAP_START, + EXTEND_ALLOWED, + POST_EXTEND_COOLDOWN, + POST_EXTEND_NO_EXTENDS_LEFT, + POST_LAST_ROUND_END, + NO_EXTENDS, +}; + +extern ERTVState g_RTVState; +extern EExtendState g_ExtendState; +extern int g_ExtendsLeft; + +void SetExtendsLeft(); \ No newline at end of file