From e1849ad1bb19b10c398d150f8789709dacfb95b2 Mon Sep 17 00:00:00 2001 From: def Date: Fri, 22 May 2020 23:59:47 +0200 Subject: [PATCH] Implement /practice for teams As discussed on Discord today, can be enabled inside of teams on the fly during each run on any server. Finishes don't count. I haven't tested save/load yet, would do that live on the server if this can be merged. --- src/game/server/ddracechat.cpp | 96 ++++++++++++++++++++++++-- src/game/server/ddracechat.h | 1 + src/game/server/entities/character.cpp | 4 +- src/game/server/gamecontext.h | 1 + src/game/server/player.cpp | 1 + src/game/server/player.h | 1 + src/game/server/save.cpp | 35 ++++++---- src/game/server/save.h | 1 + src/game/server/teams.cpp | 39 ++++++++++- src/game/server/teams.h | 17 +++++ src/game/variables.h | 2 +- 11 files changed, 173 insertions(+), 25 deletions(-) diff --git a/src/game/server/ddracechat.cpp b/src/game/server/ddracechat.cpp index c5ff590a6..5d4f2cec6 100644 --- a/src/game/server/ddracechat.cpp +++ b/src/game/server/ddracechat.cpp @@ -589,6 +589,82 @@ void CGameContext::ConTimeout(IConsole::IResult *pResult, void *pUserData) str_copy(pPlayer->m_TimeoutCode, pResult->GetString(0), sizeof(pPlayer->m_TimeoutCode)); } +void CGameContext::ConPractice(IConsole::IResult *pResult, void *pUserData) +{ + CGameContext *pSelf = (CGameContext *) pUserData; + if(!CheckClientID(pResult->m_ClientID)) + return; + + CPlayer *pPlayer = pSelf->m_apPlayers[pResult->m_ClientID]; + if(!pPlayer) + return; + + if(pSelf->ProcessSpamProtection(pResult->m_ClientID)) + return; + + CGameTeams &Teams = ((CGameControllerDDRace*) pSelf->m_pController)->m_Teams; + + int Team = Teams.m_Core.Team(pResult->m_ClientID); + + if(Team <= TEAM_FLOCK || Team >= TEAM_SUPER) + { + pSelf->Console()->Print( + IConsole::OUTPUT_LEVEL_STANDARD, + "print", + "Join a team to enable practice mode, which means you can use /r, but can't earn a rank."); + return; + } + + if(Teams.IsPractice(Team)) + { + pSelf->Console()->Print( + IConsole::OUTPUT_LEVEL_STANDARD, + "print", + "Team is already in practice mode"); + return; + } + + bool VotedForPractice = pResult->NumArguments() == 0 || pResult->GetInteger(0); + + if(VotedForPractice == pPlayer->m_VotedForPractice) + return; + + pPlayer->m_VotedForPractice = VotedForPractice; + + int NumCurrentVotes = 0; + int TeamSize = 0; + + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(Teams.m_Core.Team(i) == Team) + { + CPlayer *pPlayer2 = pSelf->m_apPlayers[i]; + if(pPlayer2 && pPlayer2->m_VotedForPractice) + NumCurrentVotes++; + TeamSize++; + } + } + + int NumRequiredVotes = TeamSize / 2 + 1; + + char aBuf[512]; + str_format(aBuf, sizeof(aBuf), "'%s' voted to %s /practice mode for your team, which means you can use /r, but you can't earn a rank. (%d/%d required votes)", pSelf->Server()->ClientName(pResult->m_ClientID), VotedForPractice ? "enable" : "disable", NumCurrentVotes, NumRequiredVotes); + + for(int i = 0; i < MAX_CLIENTS; i++) + if(Teams.m_Core.Team(i) == Team) + pSelf->SendChatTarget(i, aBuf); + + if(NumCurrentVotes >= NumRequiredVotes) + { + Teams.EnablePractice(Team); + + for(int i = 0; i < MAX_CLIENTS; i++) + if(Teams.m_Core.Team(i) == Team) + pSelf->SendChatTarget(i, "Practice mode enabled for your team, happy practicing!"); + } + +} + void CGameContext::ConSave(IConsole::IResult *pResult, void *pUserData) { CGameContext *pSelf = (CGameContext *) pUserData; @@ -944,6 +1020,8 @@ void CGameContext::ConJoinTeam(IConsole::IResult *pResult, void *pUserData) } else { + int Team = pResult->GetInteger(0); + if (pPlayer->m_Last_Team + pSelf->Server()->TickSpeed() * g_Config.m_SvTeamChangeDelay @@ -952,28 +1030,30 @@ void CGameContext::ConJoinTeam(IConsole::IResult *pResult, void *pUserData) pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "join", "You can\'t change teams that fast!"); } - else if(pResult->GetInteger(0) > 0 && pResult->GetInteger(0) < MAX_CLIENTS && pController->m_Teams.TeamLocked(pResult->GetInteger(0)) && !pController->m_Teams.IsInvited(pResult->GetInteger(0), pResult->m_ClientID)) + else if(Team > 0 && Team < MAX_CLIENTS && pController->m_Teams.TeamLocked(Team) && !pController->m_Teams.IsInvited(Team, pResult->m_ClientID)) { pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "join", g_Config.m_SvInvite ? "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(pResult->GetInteger(0) > 0 && pResult->GetInteger(0) < MAX_CLIENTS && pController->m_Teams.Count(pResult->GetInteger(0)) >= g_Config.m_SvTeamMaxSize) + else if(Team > 0 && Team < MAX_CLIENTS && pController->m_Teams.Count(Team) >= g_Config.m_SvTeamMaxSize) { char aBuf[512]; str_format(aBuf, sizeof(aBuf), "This team already has the maximum allowed size of %d players", g_Config.m_SvTeamMaxSize); pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "join", aBuf); } - else if (((CGameControllerDDRace*) pSelf->m_pController)->m_Teams.SetCharacterTeam( - pPlayer->GetCID(), pResult->GetInteger(0))) + else if(pController->m_Teams.SetCharacterTeam(pPlayer->GetCID(), Team)) { char aBuf[512]; str_format(aBuf, sizeof(aBuf), "%s joined team %d", pSelf->Server()->ClientName(pPlayer->GetCID()), - pResult->GetInteger(0)); + Team); pSelf->SendChat(-1, CGameContext::CHAT_ALL, aBuf); pPlayer->m_Last_Team = pSelf->Server()->Tick(); + + if(pController->m_Teams.IsPractice(Team)) + pSelf->SendChatTarget(pPlayer->GetCID(), "Practice mode enabled for your team, happy practicing!"); } else { @@ -1400,8 +1480,10 @@ void CGameContext::ConRescue(IConsole::IResult *pResult, void *pUserData) if (!pChr) return; - if (!g_Config.m_SvRescue) { - pSelf->SendChatTarget(pPlayer->GetCID(), "Rescue is not enabled on this server"); + CGameTeams &Teams = ((CGameControllerDDRace*) pSelf->m_pController)->m_Teams; + int Team = Teams.m_Core.Team(pResult->m_ClientID); + if (!g_Config.m_SvRescue && !Teams.IsPractice(Team)) { + pSelf->SendChatTarget(pPlayer->GetCID(), "Rescue is not enabled on this server and you're not in a team with /practice turned on. Note that you can't earn a rank with practice enabled."); return; } diff --git a/src/game/server/ddracechat.h b/src/game/server/ddracechat.h index cf24a4d4e..e0d3ec894 100644 --- a/src/game/server/ddracechat.h +++ b/src/game/server/ddracechat.h @@ -26,6 +26,7 @@ CHAT_COMMAND("specvoted", "", CFGFLAG_CHAT|CFGFLAG_SERVER, ConToggleSpecVoted, t CHAT_COMMAND("dnd", "", CFGFLAG_CHAT|CFGFLAG_SERVER|CFGFLAG_NONTEEHISTORIC, ConDND, this, "Toggle Do Not Disturb (no chat and server messages)") CHAT_COMMAND("mapinfo", "?r[map]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConMapInfo, this, "Show info about the map with name r gives (current map by default)") CHAT_COMMAND("timeout", "?s[code]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConTimeout, this, "Set timeout protection code s") +CHAT_COMMAND("practice", "?i['0'|'1']", CFGFLAG_CHAT|CFGFLAG_SERVER, ConPractice, this, "Enable cheats (currently only /rescue) for your current team's run, but you can't earn a rank") CHAT_COMMAND("save", "r[code]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConSave, this, "Save team with code r to current server. To save to another server, use '/save s r' where s = server (case-sensitive: GER, RUS, etc) and r = code.") CHAT_COMMAND("load", "?r[code]", CFGFLAG_CHAT|CFGFLAG_SERVER, ConLoad, this, "Load with code r. /load to check your existing saves") CHAT_COMMAND("map", "?r[map]", CFGFLAG_CHAT|CFGFLAG_SERVER|CFGFLAG_NONTEEHISTORIC, ConMap, this, "Vote a map by name") diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index cddb4d41e..42148e80f 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -795,7 +795,7 @@ void CCharacter::TickDefered() // advance the dummy { CWorldCore TempWorld; - m_ReckoningCore.Init(&TempWorld, GameServer()->Collision(), &((CGameControllerDDRace*)GameServer()->m_pController)->m_Teams.m_Core, &((CGameControllerDDRace*)GameServer()->m_pController)->m_TeleOuts); + m_ReckoningCore.Init(&TempWorld, GameServer()->Collision(), &Teams()->m_Core, &((CGameControllerDDRace*)GameServer()->m_pController)->m_TeleOuts); m_ReckoningCore.m_Id = m_pPlayer->GetCID(); m_ReckoningCore.Tick(false); m_ReckoningCore.Move(); @@ -2140,7 +2140,7 @@ void CCharacter::DDRaceTick() HandleTuneLayer(); // need this before coretick // look for save position for rescue feature - if(g_Config.m_SvRescue) { + if(g_Config.m_SvRescue || (Team() > TEAM_FLOCK && Team() < TEAM_SUPER)) { int index = GameServer()->Collision()->GetPureMapIndex(m_Pos); int tile = GameServer()->Collision()->GetTileIndex(index); int ftile = GameServer()->Collision()->GetFTileIndex(index); diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index d01e52d8e..4e3338732 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -326,6 +326,7 @@ private: static void ConDND(IConsole::IResult *pResult, void *pUserData); static void ConMapInfo(IConsole::IResult *pResult, void *pUserData); static void ConTimeout(IConsole::IResult *pResult, void *pUserData); + static void ConPractice(IConsole::IResult *pResult, void *pUserData); static void ConSave(IConsole::IResult *pResult, void *pUserData); static void ConLoad(IConsole::IResult *pResult, void *pUserData); static void ConMap(IConsole::IResult *pResult, void *pUserData); diff --git a/src/game/server/player.cpp b/src/game/server/player.cpp index 848514d2c..e59412703 100644 --- a/src/game/server/player.cpp +++ b/src/game/server/player.cpp @@ -136,6 +136,7 @@ void CPlayer::Reset() m_NotEligibleForFinish = false; m_EligibleForFinishCheck = 0; + m_VotedForPractice = false; } void CPlayer::Tick() diff --git a/src/game/server/player.h b/src/game/server/player.h index aee0a7b3a..c9a892260 100644 --- a/src/game/server/player.h +++ b/src/game/server/player.h @@ -198,6 +198,7 @@ public: #endif bool m_NotEligibleForFinish; int64 m_EligibleForFinishCheck; + bool m_VotedForPractice; }; #endif diff --git a/src/game/server/save.cpp b/src/game/server/save.cpp index 2840a36fe..fec42a0c6 100644 --- a/src/game/server/save.cpp +++ b/src/game/server/save.cpp @@ -318,7 +318,7 @@ int CSaveTee::LoadString(char* String) default: dbg_msg("load", "failed to load tee-string"); dbg_msg("load", "loaded %d vars", Num); - return Num+1; // never 0 here + return Num + 1; // never 0 here } } @@ -358,6 +358,7 @@ int CSaveTeam::save(int Team) m_NumSwitchers = m_pController->GameServer()->Collision()->m_NumSwitchers; m_TeamLocked = Teams->TeamLocked(Team); + m_Practice = Teams->IsPractice(Team); m_pSavedTees = new CSaveTee[m_MembersCount]; int j = 0; @@ -447,6 +448,8 @@ int CSaveTeam::load(int Team) pTeams->ChangeTeamState(Team, m_TeamState); pTeams->SetTeamLock(Team, m_TeamLocked); + if(m_Practice) + pTeams->EnablePractice(Team); for (int i = 0; i < m_MembersCount; i++) { @@ -496,25 +499,24 @@ CCharacter* CSaveTeam::MatchCharacter(char name[16], int SaveID) char* CSaveTeam::GetString() { - str_format(m_aString, sizeof(m_aString), "%d\t%d\t%d\t%d", m_TeamState, m_MembersCount, m_NumSwitchers, m_TeamLocked); + str_format(m_aString, sizeof(m_aString), "%d\t%d\t%d\t%d\t%d", m_TeamState, m_MembersCount, m_NumSwitchers, m_TeamLocked, m_Practice); - for (int i = 0; iTick() - GetStartTime(TeamPlayers[0])) / ((float)Server()->TickSpeed()); if (Time < 0.000001f) + { return; + } + + if(m_Practice[Team]) + { + ChangeTeamState(Team, TEAMSTATE_FINISHED); + + char aBuf[256]; + str_format(aBuf, sizeof(aBuf), + "Your team would've finished in: %d minute(s) %5.2f second(s). Since you had practice mode enabled your rank doesn't count.", + (int)Time / 60, Time - ((int)Time / 60 * 60)); + + for(int i = 0; i < MAX_CLIENTS; i++) + { + if(m_Core.Team(i) == Team && GameServer()->m_apPlayers[i]) + { + GameServer()->SendChatTarget(i, aBuf); + } + } + + for(unsigned int i = 0; i < PlayersCount; ++i) + { + SetDDRaceState(TeamPlayers[i], DDRACE_FINISHED); + } + + return; + } + char aTimestamp[TIMESTAMP_STR_LENGTH]; str_timestamp_format(aTimestamp, sizeof(aTimestamp), FORMAT_SPACE); // 2019-04-02 19:41:58 @@ -257,9 +286,14 @@ void CGameTeams::SetForceCharacterTeam(int ClientID, int Team) } if (OldTeam != Team) - for (int LoopClientID = 0; LoopClientID < MAX_CLIENTS; ++LoopClientID) - if (GetPlayer(LoopClientID)) + { + for(int LoopClientID = 0; LoopClientID < MAX_CLIENTS; ++LoopClientID) + if(GetPlayer(LoopClientID)) SendTeamsState(LoopClientID); + + if(GetPlayer(ClientID)) + GetPlayer(ClientID)->m_VotedForPractice = false; + } } void CGameTeams::ForceLeaveTeam(int ClientID) @@ -284,6 +318,7 @@ void CGameTeams::ForceLeaveTeam(int ClientID) // unlock team when last player leaves SetTeamLock(m_Core.Team(ClientID), false); ResetInvited(m_Core.Team(ClientID)); + m_Practice[m_Core.Team(ClientID)] = false; } } diff --git a/src/game/server/teams.h b/src/game/server/teams.h index 963bf9f53..de5e5a0e5 100644 --- a/src/game/server/teams.h +++ b/src/game/server/teams.h @@ -13,6 +13,7 @@ class CGameTeams bool m_TeamLocked[MAX_CLIENTS]; bool m_IsSaving[MAX_CLIENTS]; uint64_t m_Invited[MAX_CLIENTS]; + bool m_Practice[MAX_CLIENTS]; class CGameContext * m_pGameContext; @@ -122,6 +123,22 @@ public: { return m_IsSaving[TeamID]; } + + void EnablePractice(int Team) + { + if(Team <= TEAM_FLOCK || Team >= TEAM_SUPER) + return; + + m_Practice[Team] = true; + } + + bool IsPractice(int Team) + { + if(Team <= TEAM_FLOCK || Team >= TEAM_SUPER) + return false; + + return m_Practice[Team]; + } }; #endif diff --git a/src/game/variables.h b/src/game/variables.h index f1e5bd034..06f5a3c25 100644 --- a/src/game/variables.h +++ b/src/game/variables.h @@ -163,7 +163,7 @@ MACRO_CONFIG_STR(SvServerType, sv_server_type, 64, "none", CFGFLAG_SERVER, "Type MACRO_CONFIG_INT(SvSendVotesPerTick, sv_send_votes_per_tick, 5, 1, 15, CFGFLAG_SERVER, "Number of vote options being send per tick") MACRO_CONFIG_INT(SvRescue, sv_rescue, 0, 0, 1, CFGFLAG_SERVER, "Allow /rescue command so players can teleport themselves out of freeze") -MACRO_CONFIG_INT(SvRescueDelay, sv_rescue_delay, 5, 0, 1000, CFGFLAG_SERVER, "Number of seconds between two rescues") +MACRO_CONFIG_INT(SvRescueDelay, sv_rescue_delay, 1, 0, 1000, CFGFLAG_SERVER, "Number of seconds between two rescues") #if defined(CONF_VIDEORECORDER) MACRO_CONFIG_INT(ClVideoPauseWithDemo, cl_video_pausewithdemo, 1, 0, 1, CFGFLAG_CLIENT|CFGFLAG_SAVE, "Pause video rendering when demo playing pause")