From 8b27a6e852e0374ce660e0a48357aed04448f6e9 Mon Sep 17 00:00:00 2001 From: furo Date: Tue, 27 Aug 2024 01:19:40 +0200 Subject: [PATCH] Add `hot_reload` to reload map while preserving state --- src/engine/server.h | 1 + src/engine/server/server.cpp | 5 +++ src/engine/server/server.h | 1 + src/game/editor/editor.cpp | 2 +- src/game/server/entities/character.cpp | 22 ++++++++++++ src/game/server/gamecontext.cpp | 49 ++++++++++++++++++++++++++ src/game/server/gamecontext.h | 4 +++ src/game/server/save.cpp | 40 ++++++++++++--------- src/game/server/save.h | 6 ++-- 9 files changed, 110 insertions(+), 20 deletions(-) diff --git a/src/engine/server.h b/src/engine/server.h index 62f9504b8..462afbb97 100644 --- a/src/engine/server.h +++ b/src/engine/server.h @@ -254,6 +254,7 @@ public: 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 ChangeMap(const char *pMap) = 0; + virtual void ReloadMap() = 0; virtual void DemoRecorder_HandleAutoStart() = 0; diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 791953890..7191a4abe 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -2551,6 +2551,11 @@ void CServer::ChangeMap(const char *pMap) m_MapReload = str_comp(Config()->m_SvMap, m_aCurrentMap) != 0; } +void CServer::ReloadMap() +{ + m_MapReload = true; +} + int CServer::LoadMap(const char *pMapName) { m_MapReload = false; diff --git a/src/engine/server/server.h b/src/engine/server/server.h index 66ee206e8..57e249821 100644 --- a/src/engine/server/server.h +++ b/src/engine/server/server.h @@ -379,6 +379,7 @@ public: void ChangeMap(const char *pMap) override; const char *GetMapName() const override; + void ReloadMap() override; int LoadMap(const char *pMapName); void SaveDemo(int ClientId, float Time) override; diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 4f2591914..5f1d102dc 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -8587,7 +8587,7 @@ void CEditor::HandleWriterFinishJobs() char aMapName[128]; IStorage::StripPathAndExtension(pJob->GetRealFileName(), aMapName, sizeof(aMapName)); if(!str_comp(aMapName, CurrentServerInfo.m_aMap)) - Client()->Rcon("reload"); + Client()->Rcon("hot_reload"); } } } diff --git a/src/game/server/entities/character.cpp b/src/game/server/entities/character.cpp index 2c1f33e6a..1594cf173 100644 --- a/src/game/server/entities/character.cpp +++ b/src/game/server/entities/character.cpp @@ -103,6 +103,28 @@ bool CCharacter::Spawn(CPlayer *pPlayer, vec2 Pos) TrySetRescue(RESCUEMODE_MANUAL); 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; } diff --git a/src/game/server/gamecontext.cpp b/src/game/server/gamecontext.cpp index e47f63475..ed3e1a2ad 100644 --- a/src/game/server/gamecontext.cpp +++ b/src/game/server/gamecontext.cpp @@ -105,6 +105,14 @@ void CGameContext::Construct(int Resetting) 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_pVoteOptionHeap = new CHeap(); } @@ -119,7 +127,15 @@ void CGameContext::Destruct(int Resetting) delete pPlayer; if(Resetting == NO_RESET) + { + for(auto &pSavedTee : m_apSavedTees) + delete pSavedTee; + + for(auto &pSavedTeam : m_apSavedTeams) + delete pSavedTeam; + delete m_pVoteOptionHeap; + } if(m_pScore) { @@ -1710,6 +1726,14 @@ void CGameContext::OnClientDrop(int ClientId, const char *pReason) delete m_apPlayers[ClientId]; 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; // update spectator modes @@ -3194,6 +3218,30 @@ void CGameContext::ConSetTeamAll(IConsole::IResult *pResult, void *pUserData) 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) { 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("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("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("remove_vote", "r[name]", CFGFLAG_SERVER, ConRemoveVote, this, "remove a voting option"); diff --git a/src/game/server/gamecontext.h b/src/game/server/gamecontext.h index 66db3dc51..dd5868d07 100644 --- a/src/game/server/gamecontext.h +++ b/src/game/server/gamecontext.h @@ -124,6 +124,7 @@ class CGameContext : public IGameServer static void ConSay(IConsole::IResult *pResult, void *pUserData); static void ConSetTeam(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 ConRemoveVote(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 CNetObj_PlayerInput m_aLastPlayerInput[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 // ClientId has to be valid diff --git a/src/game/server/save.cpp b/src/game/server/save.cpp index cec79c946..a5ae20e27 100644 --- a/src/game/server/save.cpp +++ b/src/game/server/save.cpp @@ -11,7 +11,7 @@ CSaveTee::CSaveTee() = default; -void CSaveTee::Save(CCharacter *pChr) +void CSaveTee::Save(CCharacter *pChr, bool AddPenalty) { m_ClientId = pChr->m_pPlayer->GetCid(); 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; 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 m_Time = 0; + if(AddPenalty && pChr->m_StartTime) + m_Time += g_Config.m_SvSaveSwapGamesPenalty * pChr->Server()->TickSpeed(); + m_Pos = pChr->m_Pos; m_PrevPos = pChr->m_PrevPos; m_TeleCheckpoint = pChr->m_TeleCheckpoint; @@ -490,28 +493,28 @@ CSaveTeam::~CSaveTeam() 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; IGameController *pController = pGameServer->m_pController; CGameTeams *pTeams = &pController->Teams(); - if(pTeams->TeamFlock(Team)) + if(pTeams->TeamFlock(Team) && !Force) { return ESaveResult::TEAM_0_MODE; } m_MembersCount = pTeams->Count(Team); - if(m_MembersCount <= 0) + if(m_MembersCount <= 0 && !Force) { return ESaveResult::TEAM_NOT_FOUND; } m_TeamState = pTeams->GetTeamState(Team); - if(m_TeamState != CGameTeams::TEAMSTATE_STARTED) + if(m_TeamState != CGameTeams::TEAMSTATE_STARTED && !Force) { return ESaveResult::NOT_STARTED; } @@ -537,7 +540,7 @@ ESaveResult CSaveTeam::Save(CGameContext *pGameServer, int Team, bool Dry) aPlayerCids[j] = p->GetPlayer()->GetCid(); j++; } - if(m_MembersCount != j) + if(m_MembersCount != j && !Force) return ESaveResult::CHAR_NOT_FOUND; if(pGameServer->Collision()->m_HighestSwitchNumber) @@ -589,7 +592,7 @@ bool CSaveTeam::HandleSaveError(ESaveResult Result, int ClientId, CGameContext * 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; CGameTeams *pTeams = &pController->Teams(); @@ -600,14 +603,18 @@ bool CSaveTeam::Load(CGameContext *pGameServer, int Team, bool KeepCurrentWeakSt bool ContainsInvalidPlayer = false; int aPlayerCids[MAX_CLIENTS]; - for(int i = m_MembersCount; i-- > 0;) + + if(!IgnorePlayers) { - int ClientId = m_pSavedTees[i].GetClientId(); - aPlayerCids[i] = ClientId; - if(pGameServer->m_apPlayers[ClientId] && pTeams->m_Core.Team(ClientId) == Team) + for(int i = m_MembersCount; i-- > 0;) { - CCharacter *pChr = MatchCharacter(pGameServer, m_pSavedTees[i].GetClientId(), i, KeepCurrentWeakStrong); - ContainsInvalidPlayer |= !m_pSavedTees[i].Load(pChr, Team); + int ClientId = m_pSavedTees[i].GetClientId(); + 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 - pGameServer->m_World.RemoveEntitiesFromPlayers(aPlayerCids, m_MembersCount); + if(!IgnorePlayers) + pGameServer->m_World.RemoveEntitiesFromPlayers(aPlayerCids, m_MembersCount); return !ContainsInvalidPlayer; } diff --git a/src/game/server/save.h b/src/game/server/save.h index c60a78072..cf35e807e 100644 --- a/src/game/server/save.h +++ b/src/game/server/save.h @@ -35,7 +35,7 @@ class CSaveTee public: CSaveTee(); ~CSaveTee() = default; - void Save(CCharacter *pchr); + void Save(CCharacter *pchr, bool AddPenalty = true); bool Load(CCharacter *pchr, int Team, bool IsSwap = false); char *GetString(const CSaveTeam *pTeam); int FromString(const char *pString); @@ -159,8 +159,8 @@ public: int FromString(const char *pString); // 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; - ESaveResult Save(CGameContext *pGameServer, int Team, bool Dry = false); - bool Load(CGameContext *pGameServer, int Team, bool KeepCurrentWeakStrong); + ESaveResult Save(CGameContext *pGameServer, int Team, bool Dry = false, bool Force = false); + bool Load(CGameContext *pGameServer, int Team, bool KeepCurrentWeakStrong, bool IgnorePlayers = false); CSaveTee *m_pSavedTees = nullptr;