diff --git a/datasrc/network.py b/datasrc/network.py index 5283d7d9b..313ce692f 100644 --- a/datasrc/network.py +++ b/datasrc/network.py @@ -1,9 +1,9 @@ from datatypes import * Emotes = ["NORMAL", "PAIN", "HAPPY", "SURPRISE", "ANGRY", "BLINK"] -PlayerFlags = ["PLAYING", "IN_MENU", "CHATTING", "SCOREBOARD"] +PlayerFlags = ["PLAYING", "IN_MENU", "CHATTING", "SCOREBOARD", "READY"] GameFlags = ["TEAMS", "FLAGS"] -GameStateFlags = ["GAMEOVER", "SUDDENDEATH", "PAUSED"] +GameStateFlags = ["WARMUP", "SUDDENDEATH", "GAMEOVER", "PAUSED"] Emoticons = ["OOP", "EXCLAMATION", "HEARTS", "DROP", "DOTDOT", "MUSIC", "SORRY", "GHOST", "SUSHI", "SPLATTEE", "DEVILTEE", "ZOMG", "ZZZ", "WTF", "EYES", "QUESTION"] @@ -106,7 +106,7 @@ Objects = [ NetIntRange("m_GameFlags", 0, 256), NetIntRange("m_GameStateFlags", 0, 256), NetTick("m_RoundStartTick"), - NetIntRange("m_WarmupTimer", 0, 'max_int'), + NetIntRange("m_GameStateTimer", 0, 'max_int'), NetIntRange("m_ScoreLimit", 0, 'max_int'), NetIntRange("m_TimeLimit", 0, 'max_int'), @@ -145,7 +145,6 @@ Objects = [ ]), NetObject("Character:CharacterCore", [ - NetIntRange("m_PlayerFlags", 0, 256), NetIntRange("m_Health", 0, 10), NetIntRange("m_Armor", 0, 10), NetIntRange("m_AmmoCount", 0, 10), @@ -155,6 +154,7 @@ Objects = [ ]), NetObject("PlayerInfo", [ + NetIntRange("m_PlayerFlags", 0, 256), NetIntRange("m_Local", 0, 1), NetIntRange("m_ClientID", 0, 'MAX_CLIENTS-1'), NetIntRange("m_Team", 'TEAM_SPECTATORS', 'TEAM_BLUE'), @@ -324,6 +324,8 @@ Messages = [ NetMessage("Cl_Kill", []), + NetMessage("Cl_ReadyChange", []), + NetMessage("Cl_Emoticon", [ NetIntRange("m_Emoticon", 0, 'NUM_EMOTICONS-1'), ]), diff --git a/src/game/client/components/binds.cpp b/src/game/client/components/binds.cpp index 3feba98ea..bc94a300c 100644 --- a/src/game/client/components/binds.cpp +++ b/src/game/client/components/binds.cpp @@ -117,6 +117,8 @@ void CBinds::SetDefaults() Bind(KEY_F3, "vote yes"); Bind(KEY_F4, "vote no"); + + Bind('r', "ready_change"); } void CBinds::OnConsoleInit() diff --git a/src/game/client/components/hud.cpp b/src/game/client/components/hud.cpp index 6881728cc..9aefb8de4 100644 --- a/src/game/client/components/hud.cpp +++ b/src/game/client/components/hud.cpp @@ -35,7 +35,7 @@ void CHud::RenderGameTimer() { char Buf[32]; int Time = 0; - if(m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit && !m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer) + if(m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_WARMUP)) { Time = m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit*60 - ((Client()->GameTick()-m_pClient->m_Snap.m_pGameInfoObj->m_RoundStartTick)/Client()->GameTickSpeed()); @@ -49,7 +49,7 @@ void CHud::RenderGameTimer() float FontSize = 10.0f; float w = TextRender()->TextWidth(0, FontSize, Buf, -1); // last 60 sec red, last 10 sec blink - if(m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit && Time <= 60 && !m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer) + if(m_pClient->m_Snap.m_pGameInfoObj->m_TimeLimit && Time <= 60 && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_WARMUP)) { float Alpha = Time <= 10 && (2*time_get()/time_freq()) % 2 ? 0.5f : 1.0f; TextRender()->TextColor(1.0f, 0.25f, 0.25f, Alpha); @@ -59,15 +59,44 @@ void CHud::RenderGameTimer() } } -void CHud::RenderPauseNotification() +void CHud::RenderPauseTimer() { if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_GAMEOVER)) { + char aBuf[256]; const char *pText = Localize("Game paused"); float FontSize = 20.0f; - float w = TextRender()->TextWidth(0, FontSize,pText, -1); - TextRender()->Text(0, 150.0f*Graphics()->ScreenAspect()+-w/2.0f, 50.0f, FontSize, pText, -1); + float w = TextRender()->TextWidth(0, FontSize, pText, -1); + TextRender()->Text(0, 150*Graphics()->ScreenAspect()+-w/2, 50, FontSize, pText, -1); + + FontSize = 16.0f; + if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateTimer == -1) + { + int NotReadyCount = 0; + for(int i = 0; i < MAX_CLIENTS; ++i) + { + if(m_pClient->m_Snap.m_paPlayerInfos[i] && m_pClient->m_Snap.m_paPlayerInfos[i]->m_Team != TEAM_SPECTATORS && + !(m_pClient->m_Snap.m_paPlayerInfos[i]->m_PlayerFlags&PLAYERFLAG_READY)) + ++NotReadyCount; + } + if(NotReadyCount == 1) + str_format(aBuf, sizeof(aBuf), Localize("%d player not ready"), NotReadyCount); + else if(NotReadyCount > 1) + str_format(aBuf, sizeof(aBuf), Localize("%d players not ready"), NotReadyCount); + else + return; + } + else + { + int Seconds = m_pClient->m_Snap.m_pGameInfoObj->m_GameStateTimer/SERVER_TICK_SPEED; + if(Seconds < 5) + str_format(aBuf, sizeof(aBuf), "%d.%d", Seconds, (m_pClient->m_Snap.m_pGameInfoObj->m_GameStateTimer*10/SERVER_TICK_SPEED)%10); + else + str_format(aBuf, sizeof(aBuf), "%d", Seconds); + } + w = TextRender()->TextWidth(0, FontSize, aBuf, -1); + TextRender()->Text(0, 150*Graphics()->ScreenAspect()+-w/2, 75, FontSize, aBuf, -1); } } @@ -240,20 +269,40 @@ void CHud::RenderScoreHud() void CHud::RenderWarmupTimer() { // render warmup timer - if(m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer) + if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_WARMUP) { - char Buf[256]; + char aBuf[256]; float FontSize = 20.0f; float w = TextRender()->TextWidth(0, FontSize, Localize("Warmup"), -1); TextRender()->Text(0, 150*Graphics()->ScreenAspect()+-w/2, 50, FontSize, Localize("Warmup"), -1); - int Seconds = m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer/SERVER_TICK_SPEED; - if(Seconds < 5) - str_format(Buf, sizeof(Buf), "%d.%d", Seconds, (m_pClient->m_Snap.m_pGameInfoObj->m_WarmupTimer*10/SERVER_TICK_SPEED)%10); + FontSize = 16.0f; + if(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateTimer == -1) + { + int NotReadyCount = 0; + for(int i = 0; i < MAX_CLIENTS; ++i) + { + if(m_pClient->m_Snap.m_paPlayerInfos[i] && m_pClient->m_Snap.m_paPlayerInfos[i]->m_Team != TEAM_SPECTATORS && + !(m_pClient->m_Snap.m_paPlayerInfos[i]->m_PlayerFlags&PLAYERFLAG_READY)) + ++NotReadyCount; + } + if(NotReadyCount == 1) + str_format(aBuf, sizeof(aBuf), Localize("%d player not ready"), NotReadyCount); + else if(NotReadyCount > 1) + str_format(aBuf, sizeof(aBuf), Localize("%d players not ready"), NotReadyCount); + else + return; + } else - str_format(Buf, sizeof(Buf), "%d", Seconds); - w = TextRender()->TextWidth(0, FontSize, Buf, -1); - TextRender()->Text(0, 150*Graphics()->ScreenAspect()+-w/2, 75, FontSize, Buf, -1); + { + int Seconds = m_pClient->m_Snap.m_pGameInfoObj->m_GameStateTimer/SERVER_TICK_SPEED; + if(Seconds < 5) + str_format(aBuf, sizeof(aBuf), "%d.%d", Seconds, (m_pClient->m_Snap.m_pGameInfoObj->m_GameStateTimer*10/SERVER_TICK_SPEED)%10); + else + str_format(aBuf, sizeof(aBuf), "%d", Seconds); + } + w = TextRender()->TextWidth(0, FontSize, aBuf, -1); + TextRender()->Text(0, 150*Graphics()->ScreenAspect()+-w/2, 75, FontSize, aBuf, -1); } } @@ -462,7 +511,7 @@ void CHud::OnRender() } RenderGameTimer(); - RenderPauseNotification(); + RenderPauseTimer(); RenderSuddenDeath(); RenderScoreHud(); RenderWarmupTimer(); diff --git a/src/game/client/components/hud.h b/src/game/client/components/hud.h index 347208549..16a518012 100644 --- a/src/game/client/components/hud.h +++ b/src/game/client/components/hud.h @@ -17,7 +17,7 @@ class CHud : public CComponent void RenderVoting(); void RenderHealthAndAmmo(const CNetObj_Character *pCharacter); void RenderGameTimer(); - void RenderPauseNotification(); + void RenderPauseTimer(); void RenderSuddenDeath(); void RenderScoreHud(); void RenderSpectatorHud(); diff --git a/src/game/client/components/menus_settings.cpp b/src/game/client/components/menus_settings.cpp index 553195b11..4dc8e2eda 100644 --- a/src/game/client/components/menus_settings.cpp +++ b/src/game/client/components/menus_settings.cpp @@ -450,13 +450,15 @@ static CKeyInfo gs_aKeys[] = { "Screenshot", "screenshot", 0 }, { "Scoreboard", "+scoreboard", 0 }, { "Respawn", "kill", 0 }, + { "Ready", "ready_change", 0 }, }; /* This is for scripts/update_localization.py to work, don't remove! Localize("Move left");Localize("Move right");Localize("Jump");Localize("Fire");Localize("Hook");Localize("Hammer"); Localize("Pistol");Localize("Shotgun");Localize("Grenade");Localize("Rifle");Localize("Next weapon");Localize("Prev. weapon"); Localize("Vote yes");Localize("Vote no");Localize("Chat");Localize("Team chat");Localize("Show chat");Localize("Emoticon"); - Localize("Spectator mode");Localize("Spectate next");Localize("Spectate previous");Localize("Console");Localize("Remote console");Localize("Screenshot");Localize("Scoreboard");Localize("Respawn"); + Localize("Spectator mode");Localize("Spectate next");Localize("Spectate previous");Localize("Console");Localize("Remote console"); + Localize("Screenshot");Localize("Scoreboard");Localize("Respawn");Localize("Ready"); */ const int g_KeyCount = sizeof(gs_aKeys) / sizeof(CKeyInfo); diff --git a/src/game/client/components/players.cpp b/src/game/client/components/players.cpp index 2eddd8a97..bf270d8a9 100644 --- a/src/game/client/components/players.cpp +++ b/src/game/client/components/players.cpp @@ -485,7 +485,7 @@ void CPlayers::RenderPlayer( RenderInfo.m_ColorFeet.a = 1.0f; RenderTools()->RenderTee(&State, &RenderInfo, Player.m_Emote, Direction, Position); - if(Player.m_PlayerFlags&PLAYERFLAG_CHATTING) + if(pInfo.m_PlayerFlags&PLAYERFLAG_CHATTING) { Graphics()->TextureSet(g_pData->m_aImages[IMAGE_EMOTICONS].m_Id); Graphics()->QuadsBegin(); diff --git a/src/game/client/components/scoreboard.cpp b/src/game/client/components/scoreboard.cpp index 935f7badc..374b45886 100644 --- a/src/game/client/components/scoreboard.cpp +++ b/src/game/client/components/scoreboard.cpp @@ -262,9 +262,13 @@ void CScoreboard::RenderScoreboard(float x, float y, float w, int Team, const ch RenderTools()->RenderTee(CAnimState::GetIdle(), &TeeInfo, EMOTE_NORMAL, vec2(1.0f, 0.0f), vec2(TeeOffset+TeeLength/2, y+LineHeight/2)); // name + // todo: improve visual player ready state + if(!(pInfo->m_PlayerFlags&PLAYERFLAG_READY)) + TextRender()->TextColor(1.0f, 0.5f, 0.5f, 1.0f); TextRender()->SetCursor(&Cursor, NameOffset, y+Spacing, FontSize, TEXTFLAG_RENDER|TEXTFLAG_STOP_AT_END); Cursor.m_LineWidth = NameLength; TextRender()->TextEx(&Cursor, m_pClient->m_aClients[pInfo->m_ClientID].m_aName, -1); + TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); // clan tw = TextRender()->TextWidth(0, FontSize, m_pClient->m_aClients[pInfo->m_ClientID].m_aClan, -1); @@ -382,8 +386,8 @@ bool CScoreboard::Active() if(m_pClient->m_Snap.m_pLocalInfo && m_pClient->m_Snap.m_pLocalInfo->m_Team != TEAM_SPECTATORS) { - // we are not a spectator, check if we are dead - if(!m_pClient->m_Snap.m_pLocalCharacter) + // we are not a spectator, check if we are dead and the game isn't paused + if(!m_pClient->m_Snap.m_pLocalCharacter && !(m_pClient->m_Snap.m_pGameInfoObj->m_GameStateFlags&GAMESTATEFLAG_PAUSED)) return true; } diff --git a/src/game/client/gameclient.cpp b/src/game/client/gameclient.cpp index 86fb5ad0f..c999e9571 100644 --- a/src/game/client/gameclient.cpp +++ b/src/game/client/gameclient.cpp @@ -177,6 +177,7 @@ void CGameClient::OnConsoleInit() // add the some console commands Console()->Register("team", "i", CFGFLAG_CLIENT, ConTeam, this, "Switch team"); Console()->Register("kill", "", CFGFLAG_CLIENT, ConKill, this, "Kill yourself"); + Console()->Register("ready_change", "", CFGFLAG_CLIENT, ConReadyChange, this, "Change ready state"); // register server dummy commands for tab completion Console()->Register("tune", "si", CFGFLAG_SERVER, 0, 0, "Tune variable to value"); @@ -1114,12 +1115,18 @@ void CGameClient::SendInfo(bool Start) } } -void CGameClient::SendKill(int ClientID) +void CGameClient::SendKill() { CNetMsg_Cl_Kill Msg; Client()->SendPackMsg(&Msg, MSGFLAG_VITAL); } +void CGameClient::SendReadyChange() +{ + CNetMsg_Cl_ReadyChange Msg; + Client()->SendPackMsg(&Msg, MSGFLAG_VITAL); +} + void CGameClient::ConTeam(IConsole::IResult *pResult, void *pUserData) { ((CGameClient*)pUserData)->SendSwitchTeam(pResult->GetInteger(0)); @@ -1127,7 +1134,12 @@ void CGameClient::ConTeam(IConsole::IResult *pResult, void *pUserData) void CGameClient::ConKill(IConsole::IResult *pResult, void *pUserData) { - ((CGameClient*)pUserData)->SendKill(-1); + ((CGameClient*)pUserData)->SendKill(); +} + +void CGameClient::ConReadyChange(IConsole::IResult *pResult, void *pUserData) +{ + ((CGameClient*)pUserData)->SendReadyChange(); } void CGameClient::ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData) diff --git a/src/game/client/gameclient.h b/src/game/client/gameclient.h index e8c7e7bd8..1506147c6 100644 --- a/src/game/client/gameclient.h +++ b/src/game/client/gameclient.h @@ -60,6 +60,7 @@ class CGameClient : public IGameClient static void ConTeam(IConsole::IResult *pResult, void *pUserData); static void ConKill(IConsole::IResult *pResult, void *pUserData); + static void ConReadyChange(IConsole::IResult *pResult, void *pUserData); static void ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData); @@ -221,7 +222,8 @@ public: // TODO: move these void SendSwitchTeam(int Team); void SendInfo(bool Start); - void SendKill(int ClientID); + void SendKill(); + void SendReadyChange(); // pointers to all systems class CGameConsole *m_pGameConsole; diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index dea775750..9e9c8235b 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -861,6 +861,4 @@ void CCharacter::Snap(int SnappingClient) if(250 - ((Server()->Tick() - m_LastAction)%(250)) < 5) pCharacter->m_Emote = EMOTE_BLINK; } - - pCharacter->m_PlayerFlags = GetPlayer()->m_PlayerFlags; } diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index 2a6ee3122..ce2208288 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -523,8 +523,7 @@ void CGameContext::OnTick() // Server hooks void CGameContext::OnClientDirectInput(int ClientID, void *pInput) { - if(!m_World.m_Paused) - m_apPlayers[ClientID]->OnDirectInput((CNetObj_PlayerInput *)pInput); + m_apPlayers[ClientID]->OnDirectInput((CNetObj_PlayerInput *)pInput); } void CGameContext::OnClientPredictedInput(int ClientID, void *pInput) @@ -843,7 +842,7 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) } else if (MsgID == NETMSGTYPE_CL_STARTINFO) { - if(pPlayer->m_IsReady) + if(pPlayer->m_IsReadyToEnter) return; CNetMsg_Cl_StartInfo *pMsg = (CNetMsg_Cl_StartInfo *)pRawMsg; @@ -935,7 +934,7 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) SendTuningParams(ClientID); // client is ready to enter - pPlayer->m_IsReady = true; + pPlayer->m_IsReadyToEnter = true; CNetMsg_Sv_ReadyToEnter m; Server()->SendPackMsg(&m, MSGFLAG_VITAL|MSGFLAG_FLUSH, ClientID); } @@ -984,6 +983,14 @@ void CGameContext::OnMessage(int MsgID, CUnpacker *pUnpacker, int ClientID) pPlayer->m_LastKill = Server()->Tick(); pPlayer->KillCharacter(WEAPON_SELF); } + else if (MsgID == NETMSGTYPE_CL_READYCHANGE && g_Config.m_SvPlayerReadyMode && !m_pController->IsGameOver()) + { + if(pPlayer->m_LastReadyChange && pPlayer->m_LastReadyChange+Server()->TickSpeed()*1 > Server()->Tick()) + return; + + pPlayer->m_LastReadyChange = Server()->Tick(); + pPlayer->m_IsReadyToPlay ^= 1; + } } void CGameContext::ConTuneParam(IConsole::IResult *pResult, void *pUserData) @@ -1029,10 +1036,10 @@ void CGameContext::ConPause(IConsole::IResult *pResult, void *pUserData) { CGameContext *pSelf = (CGameContext *)pUserData; - if(pSelf->m_pController->IsGameOver()) - return; - - pSelf->m_World.m_Paused ^= 1; + if(pResult->NumArguments()) + pSelf->m_pController->DoPause(clamp(pResult->GetInteger(0), -1, 1000)); + else + pSelf->m_pController->DoPause(pSelf->m_pController->IsPaused() ? 0 : -1); } void CGameContext::ConChangeMap(IConsole::IResult *pResult, void *pUserData) @@ -1045,7 +1052,7 @@ void CGameContext::ConRestart(IConsole::IResult *pResult, void *pUserData) { CGameContext *pSelf = (CGameContext *)pUserData; if(pResult->NumArguments()) - pSelf->m_pController->DoWarmup(pResult->GetInteger(0)); + pSelf->m_pController->DoWarmup(clamp(pResult->GetInteger(0), -1, 1000)); else pSelf->m_pController->StartRound(); } @@ -1408,7 +1415,7 @@ void CGameContext::OnConsoleInit() Console()->Register("tune_reset", "", CFGFLAG_SERVER, ConTuneReset, this, "Reset tuning"); Console()->Register("tune_dump", "", CFGFLAG_SERVER, ConTuneDump, this, "Dump tuning"); - Console()->Register("pause", "", CFGFLAG_SERVER, ConPause, this, "Pause/unpause game"); + Console()->Register("pause", "?i", CFGFLAG_SERVER|CFGFLAG_STORE, ConPause, this, "Pause/unpause game"); Console()->Register("change_map", "?r", CFGFLAG_SERVER|CFGFLAG_STORE, ConChangeMap, this, "Change map"); Console()->Register("restart", "?i", CFGFLAG_SERVER|CFGFLAG_STORE, ConRestart, this, "Restart in x seconds (0 = abort)"); Console()->Register("broadcast", "r", CFGFLAG_SERVER, ConBroadcast, this, "Broadcast message"); @@ -1516,7 +1523,7 @@ void CGameContext::OnPostSnap() bool CGameContext::IsClientReady(int ClientID) { - return m_apPlayers[ClientID] && m_apPlayers[ClientID]->m_IsReady ? true : false; + return m_apPlayers[ClientID] && m_apPlayers[ClientID]->m_IsReadyToEnter ? true : false; } bool CGameContext::IsClientPlayer(int ClientID) diff --git a/src/game/server/gamecontroller.cpp b/src/game/server/gamecontroller.cpp index 21e95f1f8..55b00cbe8 100644 --- a/src/game/server/gamecontroller.cpp +++ b/src/game/server/gamecontroller.cpp @@ -21,6 +21,7 @@ IGameController::IGameController(CGameContext *pGameServer) // game m_GameOverTick = -1; + m_Paused = 0; m_RoundCount = 0; m_RoundStartTick = Server()->Tick(); m_SuddenDeath = 0; @@ -83,6 +84,26 @@ void IGameController::DoActivityCheck() } } +bool IGameController::GetPlayersReadyState() +{ + for(int i = 0; i < MAX_CLIENTS; ++i) + { + if(GameServer()->m_apPlayers[i] && GameServer()->m_apPlayers[i]->GetTeam() != TEAM_SPECTATORS && !GameServer()->m_apPlayers[i]->m_IsReadyToPlay) + return false; + } + + return true; +} + +void IGameController::SetPlayersReadyState(bool ReadyState) +{ + for(int i = 0; i < MAX_CLIENTS; ++i) + { + if(GameServer()->m_apPlayers[i] && GameServer()->m_apPlayers[i]->GetTeam() != TEAM_SPECTATORS) + GameServer()->m_apPlayers[i]->m_IsReadyToPlay = ReadyState; + } +} + // balancing bool IGameController::CanBeMovedOnBalance(int ClientID) const { @@ -316,18 +337,55 @@ void IGameController::OnReset() GameServer()->m_apPlayers[i]->m_RespawnTick = Server()->Tick()+Server()->TickSpeed()/2; GameServer()->m_apPlayers[i]->m_Score = 0; GameServer()->m_apPlayers[i]->m_ScoreStartTick = Server()->Tick(); + GameServer()->m_apPlayers[i]->m_IsReadyToPlay = true; } } } // game +void IGameController::DoPause(int Seconds) +{ + if(IsGameOver() || IsWarmup()) + return; + + if(Seconds != 0) + { + if(Seconds < 0) + { + m_Paused = -1; + SetPlayersReadyState(false); + } + else + m_Paused = Seconds*Server()->TickSpeed(); + + GameServer()->m_World.m_Paused = true; + } + else + { + m_Paused = 0; + SetPlayersReadyState(true); + GameServer()->m_World.m_Paused = false; + } +} + void IGameController::DoWarmup(int Seconds) { - if(Seconds < 0) - m_Warmup = 0; + if(IsGameOver() || IsPaused()) + return; + + if(Seconds < 0 && g_Config.m_SvPlayerReadyMode) + { + m_Warmup = -1; + SetPlayersReadyState(false); + } + else if(Seconds > 0) + m_Warmup = Seconds*Server()->TickSpeed(); else - m_Warmup = Seconds*Server()->TickSpeed(); -} + { + m_Warmup = 0; + SetPlayersReadyState(true); + } + } void IGameController::DoWincheck() { @@ -376,7 +434,7 @@ void IGameController::DoWincheck() void IGameController::EndRound() { - if(m_Warmup) // game can't end when we are running warmup + if(IsWarmup() || IsPaused()) // game can't end when we are running warmup or pause return; GameServer()->m_World.m_Paused = true; @@ -394,10 +452,13 @@ void IGameController::StartRound() ResetGame(); m_GameOverTick = -1; + m_Paused = 0; m_RoundStartTick = Server()->Tick(); m_SuddenDeath = 0; m_aTeamscore[TEAM_RED] = 0; m_aTeamscore[TEAM_BLUE] = 0; + m_Warmup = 0; + SetPlayersReadyState(true); if(m_UnbalancedTick == TBALANCE_CHECK) CheckTeamBalance(); @@ -420,15 +481,25 @@ void IGameController::Snap(int SnappingClient) return; pGameInfoObj->m_GameFlags = m_GameFlags; + pGameInfoObj->m_GameStateFlags = 0; - if(m_GameOverTick != -1) + pGameInfoObj->m_GameStateTimer = 0; + if(IsGameOver()) pGameInfoObj->m_GameStateFlags |= GAMESTATEFLAG_GAMEOVER; if(m_SuddenDeath) pGameInfoObj->m_GameStateFlags |= GAMESTATEFLAG_SUDDENDEATH; - if(GameServer()->m_World.m_Paused) + if(IsWarmup()) + { + pGameInfoObj->m_GameStateFlags |= GAMESTATEFLAG_WARMUP; + pGameInfoObj->m_GameStateTimer = m_Warmup; + } + if(IsPaused()) + { pGameInfoObj->m_GameStateFlags |= GAMESTATEFLAG_PAUSED; + pGameInfoObj->m_GameStateTimer |= m_Paused; + } + pGameInfoObj->m_RoundStartTick = m_RoundStartTick; - pGameInfoObj->m_WarmupTimer = m_Warmup; pGameInfoObj->m_ScoreLimit = g_Config.m_SvScorelimit; pGameInfoObj->m_TimeLimit = g_Config.m_SvTimelimit; @@ -440,14 +511,18 @@ void IGameController::Snap(int SnappingClient) void IGameController::Tick() { // do warmup - if(m_Warmup) + if(IsWarmup()) { - m_Warmup--; - if(!m_Warmup) + if(m_Warmup > 0) + --m_Warmup; + else if(!g_Config.m_SvPlayerReadyMode || GetPlayersReadyState()) + m_Warmup = 0; + + if(!IsWarmup()) StartRound(); } - if(m_GameOverTick != -1) + if(IsGameOver()) { // game over.. wait for restart if(Server()->Tick() > m_GameOverTick+Server()->TickSpeed()*10) @@ -458,9 +533,23 @@ void IGameController::Tick() } } - // game is Paused - if(GameServer()->m_World.m_Paused) - ++m_RoundStartTick; + // check if the game needs to be paused + if(!IsPaused() && g_Config.m_SvPlayerReadyMode && !GetPlayersReadyState()) + DoPause(-1); + + // do pause + if(IsPaused()) + { + if(m_Paused > 0) + --m_Paused; + else if(g_Config.m_SvPlayerReadyMode && GetPlayersReadyState()) + m_Paused = 0; + + if(!IsPaused()) + DoPause(0); + else + ++m_RoundStartTick; + } // do team-balancing if(IsTeamplay()) @@ -475,7 +564,7 @@ void IGameController::Tick() DoActivityCheck(); // win check - if(m_GameOverTick == -1 && !m_Warmup && !GameServer()->m_World.m_ResetRequested) + if(!IsGameOver() && !IsWarmup() && !IsPaused() && !GameServer()->m_World.m_ResetRequested) DoWincheck(); } @@ -497,6 +586,11 @@ bool IGameController::IsFriendlyFire(int ClientID1, int ClientID2) const return false; } +bool IGameController::IsPlayerReadyMode() const +{ + return g_Config.m_SvPlayerReadyMode != 0 && (m_Warmup == -1 || m_Paused == -1); +} + const char *IGameController::GetTeamName(int Team) const { if(IsTeamplay()) @@ -753,6 +847,7 @@ void IGameController::DoTeamChange(CPlayer *pPlayer, int Team, bool DoChatMsg) GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", aBuf); m_UnbalancedTick = TBALANCE_CHECK; + pPlayer->m_IsReadyToPlay = IsWarmup() || IsPaused() ? false : true; OnPlayerInfoChange(pPlayer); } diff --git a/src/game/server/gamecontroller.h b/src/game/server/gamecontroller.h index 2aa9ba421..0344862d4 100644 --- a/src/game/server/gamecontroller.h +++ b/src/game/server/gamecontroller.h @@ -19,6 +19,8 @@ class IGameController // activity void DoActivityCheck(); + bool GetPlayersReadyState(); + void SetPlayersReadyState(bool ReadyState); // balancing enum @@ -70,6 +72,7 @@ protected: // game int m_GameOverTick; + int m_Paused; int m_RoundCount; int m_RoundStartTick; int m_SuddenDeath; @@ -128,6 +131,7 @@ public: void OnReset(); // game + void DoPause(int Seconds); void DoWarmup(int Seconds); void StartRound(); @@ -139,7 +143,10 @@ public: // info bool IsFriendlyFire(int ClientID1, int ClientID2) const; bool IsGameOver() const { return m_GameOverTick != -1; } + bool IsPaused() const { return m_Paused != 0; } + bool IsPlayerReadyMode() const; bool IsTeamplay() const { return m_GameFlags&GAMEFLAG_TEAMS; } + bool IsWarmup() const { return m_Warmup != 0; } const char *GetGameType() const { return m_pGameType; } const char *GetTeamName(int Team) const; diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp index ffb0bf59f..713ad6a68 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -24,6 +24,7 @@ CPlayer::CPlayer(CGameContext *pGameServer, int ClientID, bool Dummy) m_LastActionTick = Server()->Tick(); m_TeamChangeTick = Server()->Tick(); m_Dummy = Dummy; + m_IsReadyToPlay = GameServer()->m_pController->IsWarmup() || GameServer()->m_pController->IsPaused() ? false : true; } CPlayer::~CPlayer() @@ -131,6 +132,9 @@ void CPlayer::Snap(int SnappingClient) if(!pPlayerInfo) return; + pPlayerInfo->m_PlayerFlags = m_PlayerFlags&PLAYERFLAG_CHATTING; + if(!GameServer()->m_pController->IsPlayerReadyMode() || m_IsReadyToPlay) + pPlayerInfo->m_PlayerFlags |= PLAYERFLAG_READY; pPlayerInfo->m_Latency = SnappingClient == -1 ? m_Latency.m_Min : GameServer()->m_apPlayers[SnappingClient]->m_aActLatency[m_ClientID]; pPlayerInfo->m_Local = 0; pPlayerInfo->m_ClientID = m_ClientID; @@ -169,6 +173,12 @@ void CPlayer::OnPredictedInput(CNetObj_PlayerInput *NewInput) void CPlayer::OnDirectInput(CNetObj_PlayerInput *NewInput) { + if(GameServer()->m_World.m_Paused) + { + m_PlayerFlags = NewInput->m_PlayerFlags; + return; + } + if(NewInput->m_PlayerFlags&PLAYERFLAG_CHATTING) { // skip the input if chat is active diff --git a/src/game/server/player.h b/src/game/server/player.h index 902c0ab16..e7e9a827c 100644 --- a/src/game/server/player.h +++ b/src/game/server/player.h @@ -55,7 +55,8 @@ public: // used for spectator mode int m_SpectatorID; - bool m_IsReady; + bool m_IsReadyToEnter; + bool m_IsReadyToPlay; // int m_Vote; @@ -69,6 +70,7 @@ public: int m_LastChangeInfo; int m_LastEmote; int m_LastKill; + int m_LastReadyChange; // TODO: clean this up struct diff --git a/src/game/variables.h b/src/game/variables.h index e57a09545..3fdfc8498 100644 --- a/src/game/variables.h +++ b/src/game/variables.h @@ -56,7 +56,7 @@ MACRO_CONFIG_INT(UiColorAlpha, ui_color_alpha, 228, 0, 255, CFGFLAG_CLIENT|CFGFL MACRO_CONFIG_INT(GfxNoclip, gfx_noclip, 0, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Disable clipping") // server -MACRO_CONFIG_INT(SvWarmup, sv_warmup, 0, 0, 0, CFGFLAG_SERVER, "Number of seconds to do warmup before round starts") +MACRO_CONFIG_INT(SvWarmup, sv_warmup, 0, -1, 1000, CFGFLAG_SERVER, "Number of seconds to do warmup before round starts (0 disables, -1 all players ready)") MACRO_CONFIG_STR(SvMotd, sv_motd, 900, "", CFGFLAG_SERVER, "Message of the day to display for the clients") MACRO_CONFIG_INT(SvTeamdamage, sv_teamdamage, 0, 0, 1, CFGFLAG_SERVER, "Team damage") MACRO_CONFIG_STR(SvMaprotation, sv_maprotation, 768, "", CFGFLAG_SERVER, "Maps to rotate between") @@ -67,6 +67,7 @@ MACRO_CONFIG_INT(SvScorelimit, sv_scorelimit, 20, 0, 1000, CFGFLAG_SERVER, "Scor MACRO_CONFIG_INT(SvTimelimit, sv_timelimit, 0, 0, 1000, CFGFLAG_SERVER, "Time limit in minutes (0 disables)") MACRO_CONFIG_STR(SvGametype, sv_gametype, 32, "dm", CFGFLAG_SERVER, "Game type (dm, tdm, ctf)") 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(SvPlayerReadyMode, sv_player_ready_mode, 0, 0, 1, CFGFLAG_SERVER, "When enabled, players can pause/unpause the game and start the game on warmup via their ready state") MACRO_CONFIG_INT(SvSpamprotection, sv_spamprotection, 1, 0, 1, CFGFLAG_SERVER, "Spam protection") MACRO_CONFIG_INT(SvRespawnDelayTDM, sv_respawn_delay_tdm, 3, 0, 10, CFGFLAG_SERVER, "Time needed to respawn after death in tdm gametype")