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:
def 2020-05-22 23:59:47 +02:00
parent f670ced3bf
commit e1849ad1bb
11 changed files with 173 additions and 25 deletions

View file

@ -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;
}

View file

@ -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")

View file

@ -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);

View file

@ -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);

View file

@ -136,6 +136,7 @@ void CPlayer::Reset()
m_NotEligibleForFinish = false;
m_EligibleForFinishCheck = 0;
m_VotedForPractice = false;
}
void CPlayer::Tick()

View file

@ -198,6 +198,7 @@ public:
#endif
bool m_NotEligibleForFinish;
int64 m_EligibleForFinishCheck;
bool m_VotedForPractice;
};
#endif

View file

@ -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

View file

@ -121,6 +121,7 @@ private:
int m_MembersCount;
int m_NumSwitchers;
int m_TeamLocked;
int m_Practice;
};
#endif // GAME_SERVER_SAVE_H

View file

@ -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;
}
}

View file

@ -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

View file

@ -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")