mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 10:08:18 +00:00
Merge pull request #7521 from dobrykafe/pr-team-0-mode
Add `/mode` allowing teams to behave like team 0
This commit is contained in:
commit
ff991af6dd
|
@ -635,6 +635,15 @@ void CGameContext::ConPractice(IConsole::IResult *pResult, void *pUserData)
|
|||
return;
|
||||
}
|
||||
|
||||
if(Teams.TeamFlock(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.TeamFlock(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.TeamFlock(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().TeamFlock(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().TeamFlock(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().TeamFlock(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::ConFlock(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().TeamFlock(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().SetTeamFlock(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().SetTeamFlock(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;
|
||||
|
|
|
@ -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()->TeamFlock(Team()) || Teams()->Count(Team()) == 1 || Teams()->GetTeamState(Team()) == CGameTeams::TEAMSTATE_OPEN || !Teams()->TeamLocked(Team())))
|
||||
{
|
||||
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()->TeamFlock(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()->TeamFlock(Team))) && Team != TEAM_SUPER)
|
||||
{
|
||||
for(int i = 0; i < MAX_CLIENTS; i++)
|
||||
{
|
||||
|
|
|
@ -444,6 +444,8 @@ 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 ConFlock(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);
|
||||
|
|
|
@ -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().TeamFlock(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);
|
||||
|
|
|
@ -481,6 +481,11 @@ int CSaveTeam::Save(CGameContext *pGameServer, int Team, bool Dry)
|
|||
IGameController *pController = pGameServer->m_pController;
|
||||
CGameTeams *pTeams = &pController->Teams();
|
||||
|
||||
if(pTeams->TeamFlock(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;
|
||||
|
|
|
@ -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().TeamFlock(Team))
|
||||
{
|
||||
GameServer()->SendChatTarget(ClientId, "Team can't be loaded while in team 0 mode");
|
||||
return;
|
||||
}
|
||||
auto SaveResult = std::make_shared<CScoreSaveResult>(ClientId);
|
||||
SaveResult->m_Status = CScoreSaveResult::LOAD_FAILED;
|
||||
pController->Teams().SetSaving(Team, SaveResult);
|
||||
|
|
|
@ -32,6 +32,7 @@ void CGameTeams::Reset()
|
|||
{
|
||||
m_aTeamState[i] = TEAMSTATE_EMPTY;
|
||||
m_aTeamLocked[i] = false;
|
||||
m_aTeamFlock[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_aTeamFlock[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 || TeamFlock(m_Core.Team(ClientId)) || m_Core.Team(ClientId) == TEAM_SUPER))
|
||||
{
|
||||
if(TeamFlock(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_aTeamFlock[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_aTeamFlock[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_aTeamFlock[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);
|
||||
SetTeamFlock(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_aTeamFlock[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_aTeamFlock[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_aTeamFlock[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_aTeamFlock[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_aTeamFlock[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_aTeamFlock[m_Core.Team(ClientId)])
|
||||
CheckTeamFinished(Team);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1123,6 +1130,12 @@ void CGameTeams::SetTeamLock(int Team, bool Lock)
|
|||
m_aTeamLocked[Team] = Lock;
|
||||
}
|
||||
|
||||
void CGameTeams::SetTeamFlock(int Team, bool Mode)
|
||||
{
|
||||
if(Team > TEAM_FLOCK && Team < TEAM_SUPER)
|
||||
m_aTeamFlock[Team] = Mode;
|
||||
}
|
||||
|
||||
void CGameTeams::ResetInvited(int Team)
|
||||
{
|
||||
m_aInvited[Team].reset();
|
||||
|
|
|
@ -25,6 +25,7 @@ class CGameTeams
|
|||
|
||||
int m_aTeamState[NUM_TEAMS];
|
||||
bool m_aTeamLocked[NUM_TEAMS];
|
||||
bool m_aTeamFlock[NUM_TEAMS];
|
||||
CClientMask m_aInvited[NUM_TEAMS];
|
||||
bool m_aPractice[NUM_TEAMS];
|
||||
std::shared_ptr<CScoreSaveResult> m_apSaveTeamResult[NUM_TEAMS];
|
||||
|
@ -109,6 +110,7 @@ public:
|
|||
|
||||
void SendTeamsState(int ClientId);
|
||||
void SetTeamLock(int Team, bool Lock);
|
||||
void SetTeamFlock(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 TeamFlock(int Team)
|
||||
{
|
||||
if(Team <= TEAM_FLOCK || Team >= TEAM_SUPER)
|
||||
return false;
|
||||
|
||||
return m_aTeamFlock[Team];
|
||||
}
|
||||
|
||||
bool IsInvited(int Team, int ClientId)
|
||||
{
|
||||
return m_aInvited[Team].test(ClientId);
|
||||
|
|
Loading…
Reference in a new issue