diff --git a/src/game/server/ddracechat.cpp b/src/game/server/ddracechat.cpp index 4e2b95ce1..1a18c9b12 100644 --- a/src/game/server/ddracechat.cpp +++ b/src/game/server/ddracechat.cpp @@ -635,6 +635,15 @@ void CGameContext::ConPractice(IConsole::IResult *pResult, void *pUserData) return; } + if(Teams.TeamMode(Team)) + { + pSelf->Console()->Print( + IConsole::OUTPUT_LEVEL_STANDARD, + "chatresp", + "Practice mode can't be enabled in team 0 mode."); + return; + } + if(Teams.IsPractice(Team)) { pSelf->Console()->Print( @@ -772,7 +781,7 @@ void CGameContext::ConSwap(IConsole::IResult *pResult, void *pUserData) } CPlayer *pSwapPlayer = pSelf->m_apPlayers[TargetClientId]; - if(Team == TEAM_FLOCK && g_Config.m_SvTeam != 3) + if((Team == TEAM_FLOCK || Teams.TeamMode(Team)) && g_Config.m_SvTeam != 3) { CCharacter *pChr = pPlayer->GetCharacter(); CCharacter *pSwapChr = pSwapPlayer->GetCharacter(); @@ -782,7 +791,7 @@ void CGameContext::ConSwap(IConsole::IResult *pResult, void *pUserData) return; } } - else if(!Teams.IsStarted(Team)) + else if(!Teams.IsStarted(Team) && !Teams.TeamMode(Team)) { pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", "Need to have started the map to swap with a player."); return; @@ -926,7 +935,10 @@ void CGameContext::ConLock(IConsole::IResult *pResult, void *pUserData) { pSelf->m_pController->Teams().SetTeamLock(Team, true); - str_format(aBuf, sizeof(aBuf), "'%s' locked your team. After the race starts, killing will kill everyone in your team.", pSelf->Server()->ClientName(pResult->m_ClientId)); + if(pSelf->m_pController->Teams().TeamMode(Team)) + str_format(aBuf, sizeof(aBuf), "'%s' locked your team.", pSelf->Server()->ClientName(pResult->m_ClientId)); + else + str_format(aBuf, sizeof(aBuf), "'%s' locked your team. After the race starts, killing will kill everyone in your team.", pSelf->Server()->ClientName(pResult->m_ClientId)); pSelf->SendChatTeam(Team, aBuf); } } @@ -1015,7 +1027,7 @@ void CGameContext::AttemptJoinTeam(int ClientId, int Team) "This team is locked using /lock. Only members of the team can unlock it using /lock." : "This team is locked using /lock. Only members of the team can invite you or unlock it using /lock."); } - else if(Team > 0 && Team < MAX_CLIENTS && m_pController->Teams().Count(Team) >= g_Config.m_SvMaxTeamSize) + else if(Team > 0 && Team < MAX_CLIENTS && m_pController->Teams().Count(Team) >= g_Config.m_SvMaxTeamSize && !m_pController->Teams().TeamMode(Team)) { char aBuf[512]; str_format(aBuf, sizeof(aBuf), "This team already has the maximum allowed size of %d players", g_Config.m_SvMaxTeamSize); @@ -1036,6 +1048,9 @@ void CGameContext::AttemptJoinTeam(int ClientId, int Team) if(m_pController->Teams().IsPractice(Team)) SendChatTarget(pPlayer->GetCid(), "Practice mode enabled for your team, happy practicing!"); + + if(m_pController->Teams().TeamMode(Team)) + SendChatTarget(pPlayer->GetCid(), "Team 0 mode enabled for your team, happy team 0-ing!"); } } } @@ -1104,6 +1119,70 @@ void CGameContext::ConInvite(IConsole::IResult *pResult, void *pUserData) pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", "Can't invite players to this team"); } +void CGameContext::ConMode(IConsole::IResult *pResult, void *pUserData) +{ + CGameContext *pSelf = (CGameContext *)pUserData; + auto *pController = pSelf->m_pController; + + if(!CheckClientId(pResult->m_ClientId)) + return; + + if(g_Config.m_SvTeam == SV_TEAM_FORBIDDEN || g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO || g_Config.m_SvTeam == SV_TEAM_MANDATORY) + { + pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "chatresp", + "Team mode change disabled"); + return; + } + + int Team = pController->Teams().m_Core.Team(pResult->m_ClientId); + bool Mode = pController->Teams().TeamMode(Team); + + if(Team <= TEAM_FLOCK || Team >= TEAM_SUPER) + { + pSelf->Console()->Print( + IConsole::OUTPUT_LEVEL_STANDARD, + "chatresp", + "This team can't have the mode changed"); + return; + } + + if(pController->Teams().GetTeamState(Team) != CGameTeams::TEAMSTATE_OPEN) + { + pSelf->SendChatTarget(pResult->m_ClientId, "Team mode can't be changed while racing"); + return; + } + + if(pResult->NumArguments() > 0) + Mode = !pResult->GetInteger(0); + + if(pSelf->ProcessSpamProtection(pResult->m_ClientId, false)) + return; + + char aBuf[512]; + if(Mode) + { + if(pController->Teams().Count(Team) > g_Config.m_SvMaxTeamSize) + { + str_format(aBuf, sizeof(aBuf), "Can't disable team 0 mode. This team exceeds the maximum allowed size of %d players for regular team", g_Config.m_SvMaxTeamSize); + pSelf->SendChatTarget(pResult->m_ClientId, aBuf); + } + else + { + pController->Teams().SetTeamMode(Team, false); + + str_format(aBuf, sizeof(aBuf), "'%s' disabled team 0 mode.", pSelf->Server()->ClientName(pResult->m_ClientId)); + pSelf->SendChatTeam(Team, aBuf); + } + } + else + { + pController->Teams().SetTeamMode(Team, true); + + str_format(aBuf, sizeof(aBuf), "'%s' enabled team 0 mode.", pSelf->Server()->ClientName(pResult->m_ClientId)); + pSelf->SendChatTeam(Team, aBuf); + } +} + void CGameContext::ConTeam(IConsole::IResult *pResult, void *pUserData) { CGameContext *pSelf = (CGameContext *)pUserData; diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index a022d4cc0..612d58b79 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -942,7 +942,7 @@ void CCharacter::Die(int Killer, int Weapon, bool SendKillMsg) GameServer()->Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "game", aBuf); // send the kill message - if(SendKillMsg && (Team() == TEAM_FLOCK || Teams()->Count(Team()) == 1 || Teams()->GetTeamState(Team()) == CGameTeams::TEAMSTATE_OPEN || Teams()->TeamLocked(Team()) == false)) + if(SendKillMsg && (Team() == TEAM_FLOCK || Teams()->TeamMode(Team()) || Teams()->Count(Team()) == 1 || Teams()->GetTeamState(Team()) == CGameTeams::TEAMSTATE_OPEN || Teams()->TeamLocked(Team()) == false)) { CNetMsg_Sv_KillMsg Msg; Msg.m_Killer = Killer; @@ -1773,7 +1773,7 @@ void CCharacter::HandleTiles(int Index) m_StartTime -= (min * 60 + sec) * Server()->TickSpeed(); - if((g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO || Team != TEAM_FLOCK) && Team != TEAM_SUPER) + if((g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO || (Team != TEAM_FLOCK && !Teams()->TeamMode(Team))) && Team != TEAM_SUPER) { for(int i = 0; i < MAX_CLIENTS; i++) { @@ -1799,7 +1799,7 @@ void CCharacter::HandleTiles(int Index) if(m_StartTime > Server()->Tick()) m_StartTime = Server()->Tick(); - if((g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO || Team != TEAM_FLOCK) && Team != TEAM_SUPER) + if((g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO || (Team != TEAM_FLOCK && !Teams()->TeamMode(Team))) && Team != TEAM_SUPER) { for(int i = 0; i < MAX_CLIENTS; i++) { diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index 3f46c0a98..43e256270 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -444,6 +444,7 @@ private: static void ConUnlock(IConsole::IResult *pResult, void *pUserData); static void ConInvite(IConsole::IResult *pResult, void *pUserData); static void ConJoin(IConsole::IResult *pResult, void *pUserData); + static void ConMode(IConsole::IResult *pResult, void *pUserData); static void ConMe(IConsole::IResult *pResult, void *pUserData); static void ConWhisper(IConsole::IResult *pResult, void *pUserData); static void ConConverse(IConsole::IResult *pResult, void *pUserData); diff --git a/src/game/server/gamemodes/DDRace.cpp b/src/game/server/gamemodes/DDRace.cpp index b80a19efd..37b0246a3 100644 --- a/src/game/server/gamemodes/DDRace.cpp +++ b/src/game/server/gamemodes/DDRace.cpp @@ -67,7 +67,7 @@ void CGameControllerDDRace::HandleCharacterTiles(CCharacter *pChr, int MapIndex) pChr->Die(ClientId, WEAPON_WORLD); return; } - if(g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO && Team > TEAM_FLOCK && Team < TEAM_SUPER && Teams().Count(Team) < g_Config.m_SvMinTeamSize) + if(g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO && Team > TEAM_FLOCK && Team < TEAM_SUPER && Teams().Count(Team) < g_Config.m_SvMinTeamSize && !Teams().TeamMode(Team)) { char aBuf[128]; str_format(aBuf, sizeof(aBuf), "Your team has fewer than %d players, so your team rank won't count", g_Config.m_SvMinTeamSize); diff --git a/src/game/server/save.cpp b/src/game/server/save.cpp index 666352f5c..7593941e4 100644 --- a/src/game/server/save.cpp +++ b/src/game/server/save.cpp @@ -481,6 +481,11 @@ int CSaveTeam::Save(CGameContext *pGameServer, int Team, bool Dry) IGameController *pController = pGameServer->m_pController; CGameTeams *pTeams = &pController->Teams(); + if(pTeams->TeamMode(Team)) + { + return 5; + } + m_MembersCount = pTeams->Count(Team); if(m_MembersCount <= 0) { @@ -554,6 +559,9 @@ bool CSaveTeam::HandleSaveError(int Result, int ClientId, CGameContext *pGameCon case 4: pGameContext->SendChatTarget(ClientId, "Your team has not started yet"); break; + case 5: + pGameContext->SendChatTarget(ClientId, "Team can't be saved while in team 0 mode"); + break; default: // this state should never be reached pGameContext->SendChatTarget(ClientId, "Unknown error while saving"); break; diff --git a/src/game/server/score.cpp b/src/game/server/score.cpp index e55e268b8..0f11f62c1 100644 --- a/src/game/server/score.cpp +++ b/src/game/server/score.cpp @@ -345,6 +345,11 @@ void CScore::LoadTeam(const char *pCode, int ClientId) GameServer()->SendChatTarget(ClientId, "Team can't be loaded while racing"); return; } + if(pController->Teams().TeamMode(Team)) + { + GameServer()->SendChatTarget(ClientId, "Team can't be loaded while in team 0 mode"); + return; + } auto SaveResult = std::make_shared(ClientId); SaveResult->m_Status = CScoreSaveResult::LOAD_FAILED; pController->Teams().SetSaving(Team, SaveResult); diff --git a/src/game/server/teams.cpp b/src/game/server/teams.cpp index 604ef9995..8c4641e6a 100644 --- a/src/game/server/teams.cpp +++ b/src/game/server/teams.cpp @@ -32,6 +32,7 @@ void CGameTeams::Reset() { m_aTeamState[i] = TEAMSTATE_EMPTY; m_aTeamLocked[i] = false; + m_aTeamMode[i] = false; m_apSaveTeamResult[i] = nullptr; m_aTeamSentStartWarning[i] = false; ResetRoundState(i); @@ -75,11 +76,14 @@ void CGameTeams::OnCharacterStart(int ClientId) return; if(g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO && pStartingChar->m_DDRaceState == DDRACE_STARTED) return; - if((g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO || m_Core.Team(ClientId) != TEAM_FLOCK) && pStartingChar->m_DDRaceState == DDRACE_FINISHED) + if((g_Config.m_SvTeam == SV_TEAM_FORCED_SOLO || (m_Core.Team(ClientId) != TEAM_FLOCK && !m_aTeamMode[m_Core.Team(ClientId)])) && pStartingChar->m_DDRaceState == DDRACE_FINISHED) return; if(g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO && - (m_Core.Team(ClientId) == TEAM_FLOCK || m_Core.Team(ClientId) == TEAM_SUPER)) + (m_Core.Team(ClientId) == TEAM_FLOCK || TeamMode(m_Core.Team(ClientId)) || m_Core.Team(ClientId) == TEAM_SUPER)) { + if(TeamMode(m_Core.Team(ClientId)) && (m_aTeamState[m_Core.Team(ClientId)] < TEAMSTATE_STARTED)) + ChangeTeamState(m_Core.Team(ClientId), TEAMSTATE_STARTED); + m_aTeeStarted[ClientId] = true; pStartingChar->m_DDRaceState = DDRACE_STARTED; pStartingChar->m_StartTime = Tick; @@ -184,7 +188,7 @@ void CGameTeams::OnCharacterStart(int ClientId) void CGameTeams::OnCharacterFinish(int ClientId) { - if((m_Core.Team(ClientId) == TEAM_FLOCK && g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO) || m_Core.Team(ClientId) == TEAM_SUPER) + if(((m_Core.Team(ClientId) == TEAM_FLOCK || m_aTeamMode[m_Core.Team(ClientId)]) && g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO) || m_Core.Team(ClientId) == TEAM_SUPER) { CPlayer *pPlayer = GetPlayer(ClientId); if(pPlayer && pPlayer->IsPlaying()) @@ -250,7 +254,7 @@ void CGameTeams::Tick() { CCharacter *pChar = GameServer()->m_apPlayers[i] ? GameServer()->m_apPlayers[i]->GetCharacter() : nullptr; int Team = m_Core.Team(i); - if(!pChar || m_aTeamState[Team] != TEAMSTATE_STARTED || m_aTeeStarted[i] || m_aPractice[m_Core.Team(i)]) + if(!pChar || m_aTeamState[Team] != TEAMSTATE_STARTED || m_aTeamMode[Team] || m_aTeeStarted[i] || m_aPractice[m_Core.Team(i)]) { continue; } @@ -372,7 +376,7 @@ const char *CGameTeams::SetCharacterTeam(int ClientId, int Team) return "Invalid client ID"; if(Team < 0 || Team >= MAX_CLIENTS + 1) return "Invalid team number"; - if(Team != TEAM_SUPER && m_aTeamState[Team] > TEAMSTATE_OPEN && !m_aPractice[Team]) + if(Team != TEAM_SUPER && m_aTeamState[Team] > TEAMSTATE_OPEN && !m_aPractice[Team] && !m_aTeamMode[Team]) return "This team started already"; if(m_Core.Team(ClientId) == Team) return "You are in this team already"; @@ -412,6 +416,7 @@ void CGameTeams::SetForceCharacterTeam(int ClientId, int Team) // unlock team when last player leaves SetTeamLock(OldTeam, false); + SetTeamMode(OldTeam, false); ResetRoundState(OldTeam); // do not reset SaveTeamResult, because it should be logged into teehistorian even if the team leaves } @@ -433,7 +438,7 @@ void CGameTeams::SetForceCharacterTeam(int ClientId, int Team) m_pGameContext->m_World.RemoveEntitiesFromPlayer(ClientId); } - if(Team != TEAM_SUPER && (m_aTeamState[Team] == TEAMSTATE_EMPTY || m_aTeamLocked[Team])) + if(Team != TEAM_SUPER && (m_aTeamState[Team] == TEAMSTATE_EMPTY || (m_aTeamLocked[Team] && !m_aTeamMode[Team]))) { if(!m_aTeamLocked[Team]) ChangeTeamState(Team, TEAMSTATE_OPEN); @@ -931,7 +936,7 @@ void CGameTeams::SwapTeamCharacters(CPlayer *pPrimaryPlayer, CPlayer *pTargetPla PrimarySavedTee.Load(pTargetPlayer->GetCharacter(), Team, true); SecondarySavedTee.Load(pPrimaryPlayer->GetCharacter(), Team, true); - if(Team >= 1) + if(Team >= 1 && !m_aTeamMode[Team]) { for(const auto &pPlayer : GameServer()->m_apPlayers) { @@ -1047,7 +1052,8 @@ void CGameTeams::OnCharacterSpawn(int ClientId) SetForceCharacterTeam(ClientId, TEAM_FLOCK); else SetForceCharacterTeam(ClientId, ClientId); // initialize team - CheckTeamFinished(Team); + if(!m_aTeamMode[Team]) + CheckTeamFinished(Team); } } @@ -1083,7 +1089,7 @@ void CGameTeams::OnCharacterDeath(int ClientId, int Weapon) { SetForceCharacterTeam(ClientId, Team); - if(GetTeamState(Team) != TEAMSTATE_OPEN) + if(GetTeamState(Team) != TEAMSTATE_OPEN && !m_aTeamMode[m_Core.Team(ClientId)]) { ChangeTeamState(Team, CGameTeams::TEAMSTATE_OPEN); @@ -1102,7 +1108,7 @@ void CGameTeams::OnCharacterDeath(int ClientId, int Weapon) } else { - if(m_aTeamState[m_Core.Team(ClientId)] == CGameTeams::TEAMSTATE_STARTED && !m_aTeeStarted[ClientId] && !m_aPractice[Team]) + if(m_aTeamState[m_Core.Team(ClientId)] == CGameTeams::TEAMSTATE_STARTED && !m_aTeeStarted[ClientId] && !m_aTeamMode[m_Core.Team(ClientId)]) { char aBuf[128]; str_format(aBuf, sizeof(aBuf), "This team cannot finish anymore because '%s' left the team before hitting the start", Server()->ClientName(ClientId)); @@ -1113,7 +1119,8 @@ void CGameTeams::OnCharacterDeath(int ClientId, int Weapon) ChangeTeamState(Team, CGameTeams::TEAMSTATE_STARTED_UNFINISHABLE); } SetForceCharacterTeam(ClientId, TEAM_FLOCK); - CheckTeamFinished(Team); + if(!m_aTeamMode[m_Core.Team(ClientId)]) + CheckTeamFinished(Team); } } @@ -1123,6 +1130,12 @@ void CGameTeams::SetTeamLock(int Team, bool Lock) m_aTeamLocked[Team] = Lock; } +void CGameTeams::SetTeamMode(int Team, bool Mode) +{ + if(Team > TEAM_FLOCK && Team < TEAM_SUPER) + m_aTeamMode[Team] = Mode; +} + void CGameTeams::ResetInvited(int Team) { m_aInvited[Team].reset(); diff --git a/src/game/server/teams.h b/src/game/server/teams.h index db6e8b5fb..d808ec06b 100644 --- a/src/game/server/teams.h +++ b/src/game/server/teams.h @@ -25,6 +25,7 @@ class CGameTeams int m_aTeamState[NUM_TEAMS]; bool m_aTeamLocked[NUM_TEAMS]; + bool m_aTeamMode[NUM_TEAMS]; CClientMask m_aInvited[NUM_TEAMS]; bool m_aPractice[NUM_TEAMS]; std::shared_ptr m_apSaveTeamResult[NUM_TEAMS]; @@ -109,6 +110,7 @@ public: void SendTeamsState(int ClientId); void SetTeamLock(int Team, bool Lock); + void SetTeamMode(int Team, bool Mode); void ResetInvited(int Team); void SetClientInvited(int Team, int ClientId, bool Invited); @@ -149,6 +151,14 @@ public: return m_aTeamLocked[Team]; } + bool TeamMode(int Team) + { + if(Team <= TEAM_FLOCK || Team >= TEAM_SUPER) + return false; + + return m_aTeamMode[Team]; + } + bool IsInvited(int Team, int ClientId) { return m_aInvited[Team].test(ClientId);