Merge pull request #8813 from furo321/hot-reload-maps

Add `hot_reload` to reload map while preserving state
This commit is contained in:
Dennis Felsing 2024-08-27 07:03:05 +00:00 committed by GitHub
commit 29f3323735
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 110 additions and 20 deletions

View file

@ -254,6 +254,7 @@ public:
virtual void Ban(int ClientId, int Seconds, const char *pReason, bool VerbatimReason) = 0; virtual void Ban(int ClientId, int Seconds, const char *pReason, bool VerbatimReason) = 0;
virtual void RedirectClient(int ClientId, int Port, bool Verbose = false) = 0; virtual void RedirectClient(int ClientId, int Port, bool Verbose = false) = 0;
virtual void ChangeMap(const char *pMap) = 0; virtual void ChangeMap(const char *pMap) = 0;
virtual void ReloadMap() = 0;
virtual void DemoRecorder_HandleAutoStart() = 0; virtual void DemoRecorder_HandleAutoStart() = 0;

View file

@ -2551,6 +2551,11 @@ void CServer::ChangeMap(const char *pMap)
m_MapReload = str_comp(Config()->m_SvMap, m_aCurrentMap) != 0; m_MapReload = str_comp(Config()->m_SvMap, m_aCurrentMap) != 0;
} }
void CServer::ReloadMap()
{
m_MapReload = true;
}
int CServer::LoadMap(const char *pMapName) int CServer::LoadMap(const char *pMapName)
{ {
m_MapReload = false; m_MapReload = false;

View file

@ -379,6 +379,7 @@ public:
void ChangeMap(const char *pMap) override; void ChangeMap(const char *pMap) override;
const char *GetMapName() const override; const char *GetMapName() const override;
void ReloadMap() override;
int LoadMap(const char *pMapName); int LoadMap(const char *pMapName);
void SaveDemo(int ClientId, float Time) override; void SaveDemo(int ClientId, float Time) override;

View file

@ -8616,7 +8616,7 @@ void CEditor::HandleWriterFinishJobs()
char aMapName[128]; char aMapName[128];
IStorage::StripPathAndExtension(pJob->GetRealFileName(), aMapName, sizeof(aMapName)); IStorage::StripPathAndExtension(pJob->GetRealFileName(), aMapName, sizeof(aMapName));
if(!str_comp(aMapName, CurrentServerInfo.m_aMap)) if(!str_comp(aMapName, CurrentServerInfo.m_aMap))
Client()->Rcon("reload"); Client()->Rcon("hot_reload");
} }
} }
} }

View file

@ -103,6 +103,28 @@ bool CCharacter::Spawn(CPlayer *pPlayer, vec2 Pos)
TrySetRescue(RESCUEMODE_MANUAL); TrySetRescue(RESCUEMODE_MANUAL);
Server()->StartRecord(m_pPlayer->GetCid()); Server()->StartRecord(m_pPlayer->GetCid());
int Team = GameServer()->m_aTeamMapping[m_pPlayer->GetCid()];
if(Team != -1)
{
GameServer()->m_pController->Teams().SetForceCharacterTeam(m_pPlayer->GetCid(), Team);
GameServer()->m_aTeamMapping[m_pPlayer->GetCid()] = -1;
if(GameServer()->m_apSavedTeams[Team])
{
GameServer()->m_apSavedTeams[Team]->Load(GameServer(), Team, true, true);
delete GameServer()->m_apSavedTeams[Team];
GameServer()->m_apSavedTeams[Team] = nullptr;
}
if(GameServer()->m_apSavedTees[m_pPlayer->GetCid()])
{
GameServer()->m_apSavedTees[m_pPlayer->GetCid()]->Load(m_pPlayer->GetCharacter(), Team);
delete GameServer()->m_apSavedTees[Team];
GameServer()->m_apSavedTees[m_pPlayer->GetCid()] = nullptr;
}
}
return true; return true;
} }

View file

@ -105,6 +105,14 @@ void CGameContext::Construct(int Resetting)
if(Resetting == NO_RESET) if(Resetting == NO_RESET)
{ {
for(auto &pSavedTee : m_apSavedTees)
pSavedTee = nullptr;
for(auto &pSavedTeam : m_apSavedTeams)
pSavedTeam = nullptr;
std::fill(std::begin(m_aTeamMapping), std::end(m_aTeamMapping), -1);
m_NonEmptySince = 0; m_NonEmptySince = 0;
m_pVoteOptionHeap = new CHeap(); m_pVoteOptionHeap = new CHeap();
} }
@ -119,7 +127,15 @@ void CGameContext::Destruct(int Resetting)
delete pPlayer; delete pPlayer;
if(Resetting == NO_RESET) if(Resetting == NO_RESET)
{
for(auto &pSavedTee : m_apSavedTees)
delete pSavedTee;
for(auto &pSavedTeam : m_apSavedTeams)
delete pSavedTeam;
delete m_pVoteOptionHeap; delete m_pVoteOptionHeap;
}
if(m_pScore) if(m_pScore)
{ {
@ -1710,6 +1726,14 @@ void CGameContext::OnClientDrop(int ClientId, const char *pReason)
delete m_apPlayers[ClientId]; delete m_apPlayers[ClientId];
m_apPlayers[ClientId] = 0; m_apPlayers[ClientId] = 0;
delete m_apSavedTeams[ClientId];
m_apSavedTeams[ClientId] = nullptr;
delete m_apSavedTees[ClientId];
m_apSavedTees[ClientId] = nullptr;
m_aTeamMapping[ClientId] = -1;
m_VoteUpdate = true; m_VoteUpdate = true;
// update spectator modes // update spectator modes
@ -3194,6 +3218,30 @@ void CGameContext::ConSetTeamAll(IConsole::IResult *pResult, void *pUserData)
pSelf->m_pController->DoTeamChange(pPlayer, Team, false); pSelf->m_pController->DoTeamChange(pPlayer, Team, false);
} }
void CGameContext::ConHotReload(IConsole::IResult *pResult, void *pUserData)
{
CGameContext *pSelf = (CGameContext *)pUserData;
for(int i = 0; i < MAX_CLIENTS; i++)
{
if(!pSelf->GetPlayerChar(i))
continue;
// Save the tee individually
pSelf->m_apSavedTees[i] = new CSaveTee();
pSelf->m_apSavedTees[i]->Save(pSelf->GetPlayerChar(i), false);
// Save the team state
pSelf->m_aTeamMapping[i] = pSelf->GetDDRaceTeam(i);
if(pSelf->m_apSavedTeams[pSelf->m_aTeamMapping[i]])
continue;
pSelf->m_apSavedTeams[pSelf->m_aTeamMapping[i]] = new CSaveTeam();
pSelf->m_apSavedTeams[pSelf->m_aTeamMapping[i]]->Save(pSelf, pSelf->m_aTeamMapping[i], true, true);
}
pSelf->Server()->ReloadMap();
}
void CGameContext::ConAddVote(IConsole::IResult *pResult, void *pUserData) void CGameContext::ConAddVote(IConsole::IResult *pResult, void *pUserData)
{ {
CGameContext *pSelf = (CGameContext *)pUserData; CGameContext *pSelf = (CGameContext *)pUserData;
@ -3552,6 +3600,7 @@ void CGameContext::OnConsoleInit()
Console()->Register("say", "r[message]", CFGFLAG_SERVER, ConSay, this, "Say in chat"); Console()->Register("say", "r[message]", CFGFLAG_SERVER, ConSay, this, "Say in chat");
Console()->Register("set_team", "i[id] i[team-id] ?i[delay in minutes]", CFGFLAG_SERVER, ConSetTeam, this, "Set team of player to team"); Console()->Register("set_team", "i[id] i[team-id] ?i[delay in minutes]", CFGFLAG_SERVER, ConSetTeam, this, "Set team of player to team");
Console()->Register("set_team_all", "i[team-id]", CFGFLAG_SERVER, ConSetTeamAll, this, "Set team of all players to team"); Console()->Register("set_team_all", "i[team-id]", CFGFLAG_SERVER, ConSetTeamAll, this, "Set team of all players to team");
Console()->Register("hot_reload", "", CFGFLAG_SERVER | CMDFLAG_TEST, ConHotReload, this, "Reload the map while preserving the state of tees and teams");
Console()->Register("add_vote", "s[name] r[command]", CFGFLAG_SERVER, ConAddVote, this, "Add a voting option"); Console()->Register("add_vote", "s[name] r[command]", CFGFLAG_SERVER, ConAddVote, this, "Add a voting option");
Console()->Register("remove_vote", "r[name]", CFGFLAG_SERVER, ConRemoveVote, this, "remove a voting option"); Console()->Register("remove_vote", "r[name]", CFGFLAG_SERVER, ConRemoveVote, this, "remove a voting option");

View file

@ -124,6 +124,7 @@ class CGameContext : public IGameServer
static void ConSay(IConsole::IResult *pResult, void *pUserData); static void ConSay(IConsole::IResult *pResult, void *pUserData);
static void ConSetTeam(IConsole::IResult *pResult, void *pUserData); static void ConSetTeam(IConsole::IResult *pResult, void *pUserData);
static void ConSetTeamAll(IConsole::IResult *pResult, void *pUserData); static void ConSetTeamAll(IConsole::IResult *pResult, void *pUserData);
static void ConHotReload(IConsole::IResult *pResult, void *pUserData);
static void ConAddVote(IConsole::IResult *pResult, void *pUserData); static void ConAddVote(IConsole::IResult *pResult, void *pUserData);
static void ConRemoveVote(IConsole::IResult *pResult, void *pUserData); static void ConRemoveVote(IConsole::IResult *pResult, void *pUserData);
static void ConForceVote(IConsole::IResult *pResult, void *pUserData); static void ConForceVote(IConsole::IResult *pResult, void *pUserData);
@ -180,6 +181,9 @@ public:
// keep last input to always apply when none is sent // keep last input to always apply when none is sent
CNetObj_PlayerInput m_aLastPlayerInput[MAX_CLIENTS]; CNetObj_PlayerInput m_aLastPlayerInput[MAX_CLIENTS];
bool m_aPlayerHasInput[MAX_CLIENTS]; bool m_aPlayerHasInput[MAX_CLIENTS];
CSaveTeam *m_apSavedTeams[MAX_CLIENTS];
CSaveTee *m_apSavedTees[MAX_CLIENTS];
int m_aTeamMapping[MAX_CLIENTS];
// returns last input if available otherwise nulled PlayerInput object // returns last input if available otherwise nulled PlayerInput object
// ClientId has to be valid // ClientId has to be valid

View file

@ -11,7 +11,7 @@
CSaveTee::CSaveTee() = default; CSaveTee::CSaveTee() = default;
void CSaveTee::Save(CCharacter *pChr) void CSaveTee::Save(CCharacter *pChr, bool AddPenalty)
{ {
m_ClientId = pChr->m_pPlayer->GetCid(); m_ClientId = pChr->m_pPlayer->GetCid();
str_copy(m_aName, pChr->Server()->ClientName(m_ClientId), sizeof(m_aName)); str_copy(m_aName, pChr->Server()->ClientName(m_ClientId), sizeof(m_aName));
@ -75,10 +75,13 @@ void CSaveTee::Save(CCharacter *pChr)
m_TuneZoneOld = pChr->m_TuneZoneOld; m_TuneZoneOld = pChr->m_TuneZoneOld;
if(pChr->m_StartTime) if(pChr->m_StartTime)
m_Time = pChr->Server()->Tick() - pChr->m_StartTime + g_Config.m_SvSaveSwapGamesPenalty * pChr->Server()->TickSpeed(); m_Time = pChr->Server()->Tick() - pChr->m_StartTime;
else else
m_Time = 0; m_Time = 0;
if(AddPenalty && pChr->m_StartTime)
m_Time += g_Config.m_SvSaveSwapGamesPenalty * pChr->Server()->TickSpeed();
m_Pos = pChr->m_Pos; m_Pos = pChr->m_Pos;
m_PrevPos = pChr->m_PrevPos; m_PrevPos = pChr->m_PrevPos;
m_TeleCheckpoint = pChr->m_TeleCheckpoint; m_TeleCheckpoint = pChr->m_TeleCheckpoint;
@ -490,28 +493,28 @@ CSaveTeam::~CSaveTeam()
delete[] m_pSavedTees; delete[] m_pSavedTees;
} }
ESaveResult CSaveTeam::Save(CGameContext *pGameServer, int Team, bool Dry) ESaveResult CSaveTeam::Save(CGameContext *pGameServer, int Team, bool Dry, bool Force)
{ {
if(g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO && (Team <= 0 || MAX_CLIENTS <= Team)) if(g_Config.m_SvTeam != SV_TEAM_FORCED_SOLO && (Team <= 0 || MAX_CLIENTS <= Team) && !Force)
return ESaveResult::TEAM_FLOCK; return ESaveResult::TEAM_FLOCK;
IGameController *pController = pGameServer->m_pController; IGameController *pController = pGameServer->m_pController;
CGameTeams *pTeams = &pController->Teams(); CGameTeams *pTeams = &pController->Teams();
if(pTeams->TeamFlock(Team)) if(pTeams->TeamFlock(Team) && !Force)
{ {
return ESaveResult::TEAM_0_MODE; return ESaveResult::TEAM_0_MODE;
} }
m_MembersCount = pTeams->Count(Team); m_MembersCount = pTeams->Count(Team);
if(m_MembersCount <= 0) if(m_MembersCount <= 0 && !Force)
{ {
return ESaveResult::TEAM_NOT_FOUND; return ESaveResult::TEAM_NOT_FOUND;
} }
m_TeamState = pTeams->GetTeamState(Team); m_TeamState = pTeams->GetTeamState(Team);
if(m_TeamState != CGameTeams::TEAMSTATE_STARTED) if(m_TeamState != CGameTeams::TEAMSTATE_STARTED && !Force)
{ {
return ESaveResult::NOT_STARTED; return ESaveResult::NOT_STARTED;
} }
@ -537,7 +540,7 @@ ESaveResult CSaveTeam::Save(CGameContext *pGameServer, int Team, bool Dry)
aPlayerCids[j] = p->GetPlayer()->GetCid(); aPlayerCids[j] = p->GetPlayer()->GetCid();
j++; j++;
} }
if(m_MembersCount != j) if(m_MembersCount != j && !Force)
return ESaveResult::CHAR_NOT_FOUND; return ESaveResult::CHAR_NOT_FOUND;
if(pGameServer->Collision()->m_HighestSwitchNumber) if(pGameServer->Collision()->m_HighestSwitchNumber)
@ -589,7 +592,7 @@ bool CSaveTeam::HandleSaveError(ESaveResult Result, int ClientId, CGameContext *
return true; return true;
} }
bool CSaveTeam::Load(CGameContext *pGameServer, int Team, bool KeepCurrentWeakStrong) bool CSaveTeam::Load(CGameContext *pGameServer, int Team, bool KeepCurrentWeakStrong, bool IgnorePlayers)
{ {
IGameController *pController = pGameServer->m_pController; IGameController *pController = pGameServer->m_pController;
CGameTeams *pTeams = &pController->Teams(); CGameTeams *pTeams = &pController->Teams();
@ -600,14 +603,18 @@ bool CSaveTeam::Load(CGameContext *pGameServer, int Team, bool KeepCurrentWeakSt
bool ContainsInvalidPlayer = false; bool ContainsInvalidPlayer = false;
int aPlayerCids[MAX_CLIENTS]; int aPlayerCids[MAX_CLIENTS];
for(int i = m_MembersCount; i-- > 0;)
if(!IgnorePlayers)
{ {
int ClientId = m_pSavedTees[i].GetClientId(); for(int i = m_MembersCount; i-- > 0;)
aPlayerCids[i] = ClientId;
if(pGameServer->m_apPlayers[ClientId] && pTeams->m_Core.Team(ClientId) == Team)
{ {
CCharacter *pChr = MatchCharacter(pGameServer, m_pSavedTees[i].GetClientId(), i, KeepCurrentWeakStrong); int ClientId = m_pSavedTees[i].GetClientId();
ContainsInvalidPlayer |= !m_pSavedTees[i].Load(pChr, Team); aPlayerCids[i] = ClientId;
if(pGameServer->m_apPlayers[ClientId] && pTeams->m_Core.Team(ClientId) == Team)
{
CCharacter *pChr = MatchCharacter(pGameServer, m_pSavedTees[i].GetClientId(), i, KeepCurrentWeakStrong);
ContainsInvalidPlayer |= !m_pSavedTees[i].Load(pChr, Team);
}
} }
} }
@ -622,7 +629,8 @@ bool CSaveTeam::Load(CGameContext *pGameServer, int Team, bool KeepCurrentWeakSt
} }
} }
// remove projectiles and laser // remove projectiles and laser
pGameServer->m_World.RemoveEntitiesFromPlayers(aPlayerCids, m_MembersCount); if(!IgnorePlayers)
pGameServer->m_World.RemoveEntitiesFromPlayers(aPlayerCids, m_MembersCount);
return !ContainsInvalidPlayer; return !ContainsInvalidPlayer;
} }

View file

@ -35,7 +35,7 @@ class CSaveTee
public: public:
CSaveTee(); CSaveTee();
~CSaveTee() = default; ~CSaveTee() = default;
void Save(CCharacter *pchr); void Save(CCharacter *pchr, bool AddPenalty = true);
bool Load(CCharacter *pchr, int Team, bool IsSwap = false); bool Load(CCharacter *pchr, int Team, bool IsSwap = false);
char *GetString(const CSaveTeam *pTeam); char *GetString(const CSaveTeam *pTeam);
int FromString(const char *pString); int FromString(const char *pString);
@ -159,8 +159,8 @@ public:
int FromString(const char *pString); int FromString(const char *pString);
// returns true if a team can load, otherwise writes a nice error Message in pMessage // returns true if a team can load, otherwise writes a nice error Message in pMessage
bool MatchPlayers(const char (*paNames)[MAX_NAME_LENGTH], const int *pClientId, int NumPlayer, char *pMessage, int MessageLen) const; bool MatchPlayers(const char (*paNames)[MAX_NAME_LENGTH], const int *pClientId, int NumPlayer, char *pMessage, int MessageLen) const;
ESaveResult Save(CGameContext *pGameServer, int Team, bool Dry = false); ESaveResult Save(CGameContext *pGameServer, int Team, bool Dry = false, bool Force = false);
bool Load(CGameContext *pGameServer, int Team, bool KeepCurrentWeakStrong); bool Load(CGameContext *pGameServer, int Team, bool KeepCurrentWeakStrong, bool IgnorePlayers = false);
CSaveTee *m_pSavedTees = nullptr; CSaveTee *m_pSavedTees = nullptr;