mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 10:08:18 +00:00
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.
This commit is contained in:
parent
f670ced3bf
commit
e1849ad1bb
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -136,6 +136,7 @@ void CPlayer::Reset()
|
|||
|
||||
m_NotEligibleForFinish = false;
|
||||
m_EligibleForFinishCheck = 0;
|
||||
m_VotedForPractice = false;
|
||||
}
|
||||
|
||||
void CPlayer::Tick()
|
||||
|
|
|
@ -198,6 +198,7 @@ public:
|
|||
#endif
|
||||
bool m_NotEligibleForFinish;
|
||||
int64 m_EligibleForFinishCheck;
|
||||
bool m_VotedForPractice;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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; i<m_MembersCount; i++)
|
||||
for(int i = 0; i < m_MembersCount; i++)
|
||||
{
|
||||
char aBuf[1024];
|
||||
str_format(aBuf, sizeof(aBuf), "\n%s", m_pSavedTees[i].GetString());
|
||||
str_append(m_aString, aBuf, sizeof(m_aString));
|
||||
}
|
||||
|
||||
if(m_NumSwitchers)
|
||||
if(m_pSwitchers && m_NumSwitchers)
|
||||
{
|
||||
for(int i=1; i < m_NumSwitchers+1; i++)
|
||||
{
|
||||
char aBuf[64];
|
||||
if (m_pSwitchers)
|
||||
{
|
||||
str_format(aBuf, sizeof(aBuf), "\n%d\t%d\t%d", m_pSwitchers[i].m_Status, m_pSwitchers[i].m_EndTime, m_pSwitchers[i].m_Type);
|
||||
str_append(m_aString, aBuf, sizeof(m_aString));
|
||||
}
|
||||
str_format(aBuf, sizeof(aBuf), "\n%d\t%d\t%d", m_pSwitchers[i].m_Status, m_pSwitchers[i].m_EndTime, m_pSwitchers[i].m_Type);
|
||||
str_append(m_aString, aBuf, sizeof(m_aString));
|
||||
}
|
||||
}
|
||||
|
||||
return m_aString;
|
||||
}
|
||||
|
@ -552,11 +554,18 @@ int CSaveTeam::LoadString(const char* String)
|
|||
if(StrSize < sizeof(TeamStats))
|
||||
{
|
||||
str_copy(TeamStats, CopyPos, StrSize);
|
||||
int Num = sscanf(TeamStats, "%d\t%d\t%d\t%d", &m_TeamState, &m_MembersCount, &m_NumSwitchers, &m_TeamLocked);
|
||||
if(Num != 4)
|
||||
int Num = sscanf(TeamStats, "%d\t%d\t%d\t%d\t%d", &m_TeamState, &m_MembersCount, &m_NumSwitchers, &m_TeamLocked, &m_Practice);
|
||||
switch(Num) // Don't forget to update this when you save / load more / less.
|
||||
{
|
||||
dbg_msg("load", "failed to load teamstats");
|
||||
dbg_msg("load", "loaded %d vars", Num);
|
||||
case 4:
|
||||
m_Practice = false;
|
||||
// fallthrough
|
||||
case 5:
|
||||
break;
|
||||
default:
|
||||
dbg_msg("load", "failed to load teamstats");
|
||||
dbg_msg("load", "loaded %d vars", Num);
|
||||
return Num + 1; // never 0 here
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
@ -121,6 +121,7 @@ private:
|
|||
int m_MembersCount;
|
||||
int m_NumSwitchers;
|
||||
int m_TeamLocked;
|
||||
int m_Practice;
|
||||
};
|
||||
|
||||
#endif // GAME_SERVER_SAVE_H
|
||||
|
|
|
@ -21,6 +21,7 @@ void CGameTeams::Reset()
|
|||
m_TeamLocked[i] = false;
|
||||
m_IsSaving[i] = false;
|
||||
m_Invited[i] = 0;
|
||||
m_Practice[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -184,7 +185,35 @@ void CGameTeams::CheckTeamFinished(int Team)
|
|||
float Time = (float)(Server()->Tick() - 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Reference in a new issue