4148: Kill unfinishable teams if they don't enter /practice within a minute r=Learath2 a=heinrich5991

This is to avoid players playing in unfinishable teams for a long time
if they don't understand the system messages.

Fixes #4088.

## Checklist

- [x] Tested the change ingame
- [ ] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test if it works standalone, system.c especially
- [ ] Considered possible null pointers and out of bounds array indexing
- [ ] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: heinrich5991 <heinrich5991@gmail.com>
This commit is contained in:
bors[bot] 2021-09-13 11:04:03 +00:00 committed by GitHub
commit 84ea62d769
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 65 additions and 72 deletions

View file

@ -659,18 +659,12 @@ void CGameContext::ConPractice(IConsole::IResult *pResult, void *pUserData)
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. Type /practice to vote (%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);
pSelf->SendChatTeam(Team, 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!");
pSelf->SendChatTeam(Team, "Practice mode enabled for your team, happy practicing!");
}
}
@ -879,10 +873,7 @@ void CGameContext::ConLockTeam(IConsole::IResult *pResult, void *pUserData)
((CGameControllerDDRace *)pSelf->m_pController)->m_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));
for(int i = 0; i < MAX_CLIENTS; i++)
if(((CGameControllerDDRace *)pSelf->m_pController)->m_Teams.m_Core.Team(i) == Team)
pSelf->SendChatTarget(i, aBuf);
pSelf->SendChatTeam(Team, aBuf);
}
}
@ -916,10 +907,7 @@ void CGameContext::UnlockTeam(int ClientID, int Team)
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "'%s' unlocked your team.", Server()->ClientName(ClientID));
for(int i = 0; i < MAX_CLIENTS; i++)
if(((CGameControllerDDRace *)m_pController)->m_Teams.m_Core.Team(i) == Team)
SendChatTarget(i, aBuf);
SendChatTeam(Team, aBuf);
}
void CGameContext::ConInviteTeam(IConsole::IResult *pResult, void *pUserData)
@ -980,10 +968,7 @@ void CGameContext::ConInviteTeam(IConsole::IResult *pResult, void *pUserData)
pSelf->SendChatTarget(Target, aBuf);
str_format(aBuf, sizeof aBuf, "'%s' invited '%s' to your team.", pSelf->Server()->ClientName(pResult->m_ClientID), pSelf->Server()->ClientName(Target));
;
for(int i = 0; i < MAX_CLIENTS; i++)
if(((CGameControllerDDRace *)pSelf->m_pController)->m_Teams.m_Core.Team(i) == Team)
pSelf->SendChatTarget(i, aBuf);
pSelf->SendChatTeam(Team, aBuf);
}
else
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "invite", "Can't invite players to this team");

View file

@ -103,10 +103,7 @@ void CGameControllerDDRace::HandleCharacterTiles(CCharacter *pChr, int MapIndex)
else if(((m_TileIndex == TILE_UNLOCK_TEAM) || (m_TileFIndex == TILE_UNLOCK_TEAM)) && m_Teams.TeamLocked(GetPlayerTeam(ClientID)))
{
m_Teams.SetTeamLock(GetPlayerTeam(ClientID), false);
for(int i = 0; i < MAX_CLIENTS; i++)
if(GetPlayerTeam(i) == GetPlayerTeam(ClientID))
GameServer()->SendChatTarget(i, "Your team was unlocked by an unlock team tile");
GameServer()->SendChatTeam(GetPlayerTeam(ClientID), "Your team was unlocked by an unlock team tile");
}
// solo part

View file

@ -29,6 +29,7 @@ void CGameTeams::Reset()
m_Practice[i] = false;
m_LastSwap[i] = 0;
m_TeamSentStartWarning[i] = false;
m_TeamUnfinishableKillTick[i] = -1;
}
}
@ -38,7 +39,8 @@ void CGameTeams::ResetRoundState(int Team)
ResetSwitchers(Team);
m_LastSwap[Team] = 0;
m_Practice[Team] = 0;
m_Practice[Team] = false;
m_TeamUnfinishableKillTick[Team] = -1;
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(m_Core.Team(i) == Team && GameServer()->m_apPlayers[i])
@ -127,6 +129,7 @@ void CGameTeams::OnCharacterStart(int ClientID)
{
ChangeTeamState(m_Core.Team(ClientID), TEAMSTATE_STARTED);
m_TeamSentStartWarning[m_Core.Team(ClientID)] = false;
m_TeamUnfinishableKillTick[m_Core.Team(ClientID)] = -1;
int NumPlayers = Count(m_Core.Team(ClientID));
@ -205,6 +208,25 @@ void CGameTeams::OnCharacterFinish(int ClientID)
void CGameTeams::Tick()
{
int Now = Server()->Tick();
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(m_TeamUnfinishableKillTick[i] == -1 || m_TeamState[i] != TEAMSTATE_STARTED_UNFINISHABLE)
{
continue;
}
if(Now >= m_TeamUnfinishableKillTick[i])
{
if(m_Practice[i])
{
m_TeamUnfinishableKillTick[i] = -1;
continue;
}
KillTeam(i, -1);
GameServer()->SendChatTeam(i, "Your team was killed because it couldn't finish anymore and hasn't entered /practice mode");
}
}
int Frequency = Server()->TickSpeed() * 60;
int Remainder = Server()->TickSpeed() * 30;
uint64_t TeamHasWantedStartTime = 0;
@ -263,14 +285,7 @@ void CGameTeams::Tick()
NumPlayersNotStarted,
NumPlayersNotStarted == 1 ? "player that has" : "players that have",
aPlayerNames);
for(int j = 0; j < MAX_CLIENTS; j++)
{
if(m_Core.Team(j) == i)
{
GameServer()->SendChatTarget(j, aBuf);
}
}
GameServer()->SendChatTeam(i, aBuf);
}
}
@ -312,14 +327,7 @@ void CGameTeams::CheckTeamFinished(int Team)
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);
}
}
GameServer()->SendChatTeam(Team, aBuf);
for(unsigned int i = 0; i < PlayersCount; ++i)
{
@ -434,6 +442,22 @@ void CGameTeams::ChangeTeamState(int Team, int State)
m_TeamState[Team] = State;
}
void CGameTeams::KillTeam(int Team, int NewStrongID)
{
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(m_Core.Team(i) == Team && GameServer()->m_apPlayers[i])
{
GameServer()->m_apPlayers[i]->m_VotedForPractice = false;
GameServer()->m_apPlayers[i]->KillCharacter(WEAPON_SELF);
if(NewStrongID != -1 && i != NewStrongID)
{
GameServer()->m_apPlayers[i]->Respawn(true); // spawn the rest of team with weak hook on the killer
}
}
}
}
bool CGameTeams::TeamFinished(int Team)
{
if(m_TeamState[Team] != TEAMSTATE_STARTED)
@ -982,20 +1006,11 @@ void CGameTeams::OnCharacterDeath(int ClientID, int Weapon)
m_Practice[Team] = false;
for(int i = 0; i < MAX_CLIENTS; i++)
if(m_Core.Team(i) == Team && GameServer()->m_apPlayers[i])
{
GameServer()->m_apPlayers[i]->m_VotedForPractice = false;
if(i != ClientID)
{
GameServer()->m_apPlayers[i]->KillCharacter(WEAPON_SELF);
if(Weapon == WEAPON_SELF)
GameServer()->m_apPlayers[i]->Respawn(true); // spawn the rest of team with weak hook on the killer
}
if(Count(Team) > 1)
GameServer()->SendChatTarget(i, aBuf);
}
KillTeam(Team, Weapon == WEAPON_SELF ? ClientID : -1);
if(Count(Team) > 1)
{
GameServer()->SendChatTeam(Team, aBuf);
}
}
}
else
@ -1003,16 +1018,10 @@ void CGameTeams::OnCharacterDeath(int ClientID, int Weapon)
if(m_TeamState[m_Core.Team(ClientID)] == CGameTeams::TEAMSTATE_STARTED && !m_TeeStarted[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));
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(m_Core.Team(i) != Team || i == ClientID)
{
continue;
}
GameServer()->SendChatTarget(i, aBuf);
}
str_format(aBuf, sizeof(aBuf), "This team cannot finish anymore because '%s' left the team before hitting the start, enter /practice mode to avoid being killed in 60 seconds", Server()->ClientName(ClientID));
GameServer()->SendChatTeam(Team, aBuf);
m_TeamUnfinishableKillTick[Team] = Server()->Tick() + 60 * Server()->TickSpeed();
ChangeTeamState(Team, CGameTeams::TEAMSTATE_STARTED_UNFINISHABLE);
}
SetForceCharacterTeam(ClientID, TEAM_FLOCK);
@ -1044,14 +1053,7 @@ void CGameTeams::SetClientInvited(int Team, int ClientID, bool Invited)
void CGameTeams::KillSavedTeam(int ClientID, int Team)
{
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(m_Core.Team(i) == Team && GameServer()->m_apPlayers[i])
{
GameServer()->m_apPlayers[i]->m_VotedForPractice = false;
GameServer()->m_apPlayers[i]->KillCharacter(WEAPON_SELF);
}
}
KillTeam(Team, -1);
}
void CGameTeams::ResetSavedTeam(int ClientID, int Team)

View file

@ -27,9 +27,18 @@ class CGameTeams
std::shared_ptr<CScoreSaveResult> m_pSaveTeamResult[MAX_CLIENTS];
uint64_t m_LastSwap[MAX_CLIENTS];
bool m_TeamSentStartWarning[MAX_CLIENTS];
// `m_TeamUnfinishableKillTick` is -1 by default and gets set when a
// team becomes unfinishable. If the team hasn't entered practice mode
// by that time, it'll get killed to prevent people not understanding
// the message from playing for a long time in an unfinishable team.
int m_TeamUnfinishableKillTick[MAX_CLIENTS];
class CGameContext *m_pGameContext;
// Kill the whole team, making the player `NewStrongID` have strong
// hook on everyone else. `NewStrongID` can be -1 to get the normal
// spawning order.
void KillTeam(int Team, int NewStrongID);
bool TeamFinished(int Team);
void OnTeamFinish(CPlayer **Players, unsigned int Size, float Time, const char *pTimestamp);
void OnFinish(CPlayer *Player, float Time, const char *pTimestamp);