/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 1998-2000, Matthes Bender * Copyright (c) 2004-2009, RedWolf Design GmbH, http://www.clonk.de/ * Copyright (c) 2009-2016, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ // permanent player information management // see header for some additional information #include "C4Include.h" #include "control/C4PlayerInfo.h" #include "game/C4Game.h" #include "config/C4Config.h" #include "lib/C4Log.h" #include "player/C4Player.h" #include "game/C4FullScreen.h" #include "player/C4PlayerList.h" #include "control/C4GameControl.h" #include "c4group/C4Components.h" // *** C4PlayerInfo void C4PlayerInfo::Clear() { // del temp file DeleteTempFile(); // clear fields sName.Clear(); szFilename.Clear(); pRes = NULL; ResCore.Clear(); // default fields dwColor = dwOriginalColor = 0xffffffff; dwAlternateColor = 0; dwFlags = 0; iID = idSavegamePlayer = idTeam = 0; iInGameNumber = iInGameJoinFrame = iInGamePartFrame = -1; sLeagueAccount = ""; iLeagueScore=iLeagueRank=0; iLeagueProjectedGain = -1; eType = C4PT_User; idExtraData = C4ID::None; iLeaguePerformance = 0; sLeagueProgressData.Clear(); } void C4PlayerInfo::DeleteTempFile() { // is temp file? if (!! szFilename && (dwFlags & PIF_TempFile)) { // erase it EraseItem(szFilename.getData()); // reset flag and filename to prevent double deletion dwFlags &= ~PIF_TempFile; szFilename.Clear(); } } bool C4PlayerInfo::LoadFromLocalFile(const char *szFilename) { // players should not be added in replay mode assert(!Game.C4S.Head.Replay); // clear previous Clear(); // open player file group C4Group Grp; if (!Reloc.Open(Grp, szFilename)) return false; // read core C4PlayerInfoCore C4P; if (!C4P.Load(Grp)) return false; // set values eType = C4PT_User; sName = C4P.PrefName; this->szFilename = szFilename; dwColor = dwOriginalColor = 0xff000000 | (C4P.PrefColorDw & 0xffffff); // ignore alpha dwAlternateColor = 0xff000000 | (C4P.PrefColor2Dw & 0xffffff); // ignore alpha // network: resource (not for replays, because everyone has the player files there...) if (::Network.isEnabled() && !Game.C4S.Head.Replay) { // add resource // 2do: rejoining players need to update their resource version when saving the player // otherwise, player file versions may differ pRes = ::Network.ResList.getRefRes(szFilename, true); // not found? add if (!pRes) pRes = ::Network.ResList.AddByGroup(&Grp, false, NRT_Player, -1, szFilename); if (!pRes) return false; // set core and flag ResCore = pRes->getCore(); dwFlags |= PIF_HasRes; // filename is no longer needed in network mode, because it's stored in the res-core } // done, success return true; } bool C4PlayerInfo::SetAsScriptPlayer(const char *szName, uint32_t dwColor, uint32_t dwFlags, C4ID idExtra) { // clear previous Clear(); // set parameters eType = C4PT_Script; this->dwColor = dwOriginalColor = 0xff000000 | (dwColor & 0xffffff); // ignore alpha dwAlternateColor = 0; this->sName.CopyValidated(szName); idExtraData = idExtra; this->dwFlags |= dwFlags; // done, success return true; } const char *C4PlayerInfo::GetLocalJoinFilename() const { // preferred: by resource if (pRes) return pRes->getFile(); // if no resource is known (replay or non-net), return filename return szFilename.getData(); } uint32_t C4PlayerInfo::GetLobbyColor() const { // special case if random teams and team colors are enabled in lobby: // Unjoined players do not show their team! Instead, they just display their original color if (Game.Teams.GetTeamDist() == C4TeamList::TEAMDIST_RandomInv) if (Game.Teams.IsTeamColors()) if (Game.Teams.GetTeamByID(GetTeam())) if (!HasJoined() && !GetAssociatedSavegamePlayerID()) return GetOriginalColor(); // otherwise, just show the normal player color return GetColor(); } StdStrBuf C4PlayerInfo::GetLobbyName() const { // return player name including colored clan/team tag if known StdStrBuf sResult; if (sLeagueAccount.getLength()) { if (sClanTag.getLength()) { // gray team tag color used in lobby and game evaluation dialog! sResult.Format("%s %s", sClanTag.getData(), sLeagueAccount.getData()); } else sResult.Ref(sLeagueAccount); } else { // fallback to regular player name sResult.Ref(sForcedName.getLength() ? static_cast(sForcedName) : static_cast(sName)); } return sResult; } bool C4PlayerInfo::HasTeamWon() const { // team win/solo win C4Team *pTeam; if (idTeam && (pTeam = Game.Teams.GetTeamByID(idTeam))) return pTeam->HasWon(); else return HasWon(); } void C4PlayerInfo::CompileFunc(StdCompiler *pComp) { // Names pComp->Value(mkNamingAdapt(sName, "Name", "")); pComp->Value(mkNamingAdapt(sForcedName, "ForcedName", "")); pComp->Value(mkNamingAdapt(szFilename, "Filename", "")); // Flags const StdBitfieldEntry Entries[] = { { "Joined", PIF_Joined }, { "Removed", PIF_Removed }, { "HasResource", PIF_HasRes }, { "JoinIssued", PIF_JoinIssued }, { "SavegameJoin", PIF_JoinedForSavegameOnly }, { "Disconnected", PIF_Disconnected }, { "VotedOut", PIF_VotedOut }, { "Won", PIF_Won }, { "AttributesFixed", PIF_AttributesFixed }, { "NoScenarioInit", PIF_NoScenarioInit }, { "NoEliminationCheck", PIF_NoEliminationCheck }, { "Invisible", PIF_Invisible}, { NULL, 0 }, }; uint16_t dwSyncFlags = dwFlags & PIF_SyncFlags; // do not store local flags! pComp->Value(mkNamingAdapt(mkBitfieldAdapt(dwSyncFlags, Entries), "Flags", 0u)); if (pComp->isCompiler()) dwFlags = dwSyncFlags; pComp->Value(mkNamingAdapt(iID, "ID", 0)); // type StdEnumEntry PlayerTypes[] = { { "User", C4PT_User }, { "Script", C4PT_Script }, { NULL, C4PT_User }, }; pComp->Value(mkNamingAdapt(mkEnumAdaptT(eType, PlayerTypes), "Type", C4PT_User)); // safety: Do not allow invisible regular players if (pComp->isCompiler()) { if (eType != C4PT_Script) dwFlags &= ~PIF_Invisible; } // load colors pComp->Value(mkNamingAdapt(dwColor, "Color", 0u)); pComp->Value(mkNamingAdapt(dwOriginalColor, "OriginalColor", dwColor)); // load savegame ID pComp->Value(mkNamingAdapt(mkIntPackAdapt(idSavegamePlayer), "SavgamePlayer", 0)); // load team ID pComp->Value(mkNamingAdapt(mkIntPackAdapt(idTeam), "Team", 0)); // load authentication ID pComp->Value(mkNamingAdapt(szAuthID, "AUID", "")); // InGame info if (dwFlags & PIF_Joined) { pComp->Value(mkNamingAdapt(iInGameNumber, "GameNumber", -1)); pComp->Value(mkNamingAdapt(iInGameJoinFrame, "GameJoinFrame", -1)); } else iInGameNumber = iInGameJoinFrame = -1; if (dwFlags & PIF_Removed) pComp->Value(mkNamingAdapt(iInGamePartFrame, "GamePartFrame", -1)); else iInGamePartFrame = -1; // script player extra data pComp->Value(mkNamingAdapt(idExtraData, "ExtraData", C4ID::None)); // load league info pComp->Value(mkNamingAdapt(sLeagueAccount, "LeagueAccount", "")); pComp->Value(mkNamingAdapt(mkIntPackAdapt(iLeagueScore), "LeagueScore", 0)); pComp->Value(mkNamingAdapt(mkIntPackAdapt(iLeagueRank), "LeagueRank", 0)); pComp->Value(mkNamingAdapt(mkIntPackAdapt(iLeagueRankSymbol), "LeagueRankSymbol", 0)); pComp->Value(mkNamingAdapt(mkIntPackAdapt(iLeagueProjectedGain), "ProjectedGain", -1)); pComp->Value(mkNamingAdapt(mkParAdapt(sClanTag, StdCompiler::RCT_All), "ClanTag", "")); pComp->Value(mkNamingAdapt(mkIntPackAdapt(iLeaguePerformance), "LeaguePerformance", 0)); pComp->Value(mkNamingAdapt(sLeagueProgressData, "LeagueProgressData", "")); // file resource if (pComp->isCompiler() && Game.C4S.Head.Replay) { // Replays don't have player resources, drop the flag dwFlags &= ~PIF_HasRes; } if (dwFlags & PIF_HasRes) { // ResCore if (pComp->isDecompiler() && pRes) { // ensure ResCore is up-to-date ResCore = pRes->getCore(); } pComp->Value(mkNamingAdapt(ResCore, "ResCore")); } } void C4PlayerInfo::SetFilename(const char *szToFilename) { szFilename = szToFilename; } void C4PlayerInfo::SetToScenarioFilename(const char *szScenFilename) { // kill res DiscardResource(); // set new filename SetFilename(szScenFilename); // flag scenario filename dwFlags |= PIF_InScenarioFile; } void C4PlayerInfo::LoadResource() { // only if any resource present and not yet assigned if (IsRemoved() || !(dwFlags & PIF_HasRes) || pRes) return; // Ignore res if a local file is to be used // the PIF_InScenarioFile is not set for startup players in initial replays, // because resources are used for player joins but emulated in playback control // if there will ever be resources in replay mode, this special case can be removed if (Game.C4S.Head.Replay || (dwFlags & PIF_InScenarioFile)) dwFlags &= ~PIF_HasRes; else // create resource (will check if resource already exists) if (!(pRes = ::Network.ResList.AddByCore(ResCore))) { dwFlags &= ~PIF_HasRes; // add failed? invalid resource??! -- TODO: may be too large to load LogF("Error: Could not add resource %d for player %s! Player file too large to load?", (int) ResCore.getID(), (const char *) GetFilename()); } } void C4PlayerInfo::DiscardResource() { // del any file resource if (pRes) { assert(dwFlags & PIF_HasRes); pRes = NULL; dwFlags &= ~PIF_HasRes; } else assert(~dwFlags & PIF_HasRes); ResCore.Clear(); } bool C4PlayerInfo::SetSavegameResume(C4PlayerInfo *pSavegameInfo) { // copy some data fields; but not the file fields, because the join method is determined by this player if (!pSavegameInfo) return false; iID = pSavegameInfo->GetID(); dwFlags = (dwFlags & ~PIF_SavegameTakeoverFlags) | (pSavegameInfo->GetFlags() & PIF_SavegameTakeoverFlags); dwColor = pSavegameInfo->GetColor(); // redundant; should be done by host already idTeam = pSavegameInfo->GetTeam(); return true; } void C4PlayerInfo::SetJoined(int32_t iNumber) { // mark as joined in current frame iInGameNumber = iNumber; iInGameJoinFrame = Game.FrameCounter; dwFlags |= PIF_Joined; } void C4PlayerInfo::SetRemoved() { // mark as removed - always marks as previously joined, too dwFlags |= PIF_Joined | PIF_Removed; // remember removal frame iInGamePartFrame = Game.FrameCounter; } bool C4PlayerInfo::LoadBigIcon(C4FacetSurface &fctTarget) { bool fSuccess = false; // load BigIcon.png of player into target facet; return false if no bigicon present or player file not yet loaded C4Group Plr; C4Network2Res *pRes = NULL; bool fIncompleteRes = false; if ((pRes = GetRes())) if (!pRes->isComplete()) fIncompleteRes = true; size_t iBigIconSize=0; if (!fIncompleteRes) if (Plr.Open(pRes ? pRes->getFile() : GetFilename())) if (Plr.AccessEntry(C4CFN_BigIcon, &iBigIconSize)) if (iBigIconSize<=C4NetResMaxBigicon*1024) if (fctTarget.Load(Plr, C4CFN_BigIcon, C4FCT_Full, C4FCT_Full, false, 0)) fSuccess = true; return fSuccess; } // *** C4ClientPlayerInfos C4ClientPlayerInfos::C4ClientPlayerInfos(const char *szJoinFilenames, bool fAdd, C4PlayerInfo *pAddInfo) : iPlayerCount(0), iPlayerCapacity(0), ppPlayers(NULL), iClientID(-1), dwFlags(0) { // init for local client? if (szJoinFilenames || pAddInfo) { // set developer flag for developer hosts if (SSearch(Config.GetRegistrationData("Type"), "Developer")) dwFlags |= CIF_Developer; // set local ID iClientID = ::Control.ClientID(); // maybe control is not preinitialized if (!::Control.isNetwork() && iClientID < 0) iClientID = 0; // join packet or initial packet? if (fAdd) // packet is to be added to other players dwFlags |= CIF_AddPlayers; else // set initial flag for first-time join packet dwFlags |= CIF_Initial; // join all players in list if ((iPlayerCapacity = (szJoinFilenames ? SModuleCount(szJoinFilenames) : 0) + !!pAddInfo)) { ppPlayers = new C4PlayerInfo *[iPlayerCapacity]; if (szJoinFilenames) { char szPlrFile[_MAX_PATH+1]; for (int32_t i=0; iLoadFromLocalFile(szPlrFile)) { // player def loaded; register and count it ppPlayers[iPlayerCount++] = pNewInfo; } else { // loading failure; clear info class delete pNewInfo; // Log(FormatString(LoadResStr("IDS_ERR_LOAD_PLAYER"), szPlrFile).getData()); } } } if (pAddInfo) ppPlayers[iPlayerCount++] = pAddInfo; } } } C4ClientPlayerInfos::C4ClientPlayerInfos(const C4ClientPlayerInfos &rCopy) { // copy fields iClientID = rCopy.iClientID; if ((iPlayerCount = rCopy.iPlayerCount)) { // copy player infos ppPlayers = new C4PlayerInfo *[iPlayerCapacity = rCopy.iPlayerCapacity]; int32_t i = iPlayerCount; C4PlayerInfo **ppCurrPlrInfo = ppPlayers, **ppSrcPlrInfo = rCopy.ppPlayers; while (i--) *ppCurrPlrInfo++ = new C4PlayerInfo(**ppSrcPlrInfo++); } // no players else { ppPlayers = NULL; iPlayerCapacity = 0; } // misc fields dwFlags = rCopy.dwFlags; } C4ClientPlayerInfos &C4ClientPlayerInfos::operator = (const C4ClientPlayerInfos &rCopy) { Clear(); // copy fields iClientID = rCopy.iClientID; if ((iPlayerCount = rCopy.iPlayerCount)) { // copy player infos ppPlayers = new C4PlayerInfo *[iPlayerCapacity = rCopy.iPlayerCapacity]; int32_t i = iPlayerCount; C4PlayerInfo **ppCurrPlrInfo = ppPlayers, **ppSrcPlrInfo = rCopy.ppPlayers; while (i--) *ppCurrPlrInfo++ = new C4PlayerInfo(**ppSrcPlrInfo++); } // no players else { ppPlayers = NULL; iPlayerCapacity = 0; } // misc fields dwFlags = rCopy.dwFlags; return *this; } void C4ClientPlayerInfos::Clear() { // del player infos int32_t i = iPlayerCount; C4PlayerInfo **ppCurrPlrInfo = ppPlayers; while (i--) delete *ppCurrPlrInfo++; // del player info vector delete [] ppPlayers; ppPlayers = NULL; // reset other fields iPlayerCount = iPlayerCapacity = 0; iClientID=-1; dwFlags = 0; } void C4ClientPlayerInfos::GrabMergeFrom(C4ClientPlayerInfos &rFrom) { // anything to grab? if (!rFrom.iPlayerCount) return; // any previous players to copy? if (iPlayerCount) { // buffer sufficient? if (iPlayerCount + rFrom.iPlayerCount > iPlayerCapacity) GrowList(rFrom.iPlayerCount); // merge into new buffer memcpy(ppPlayers + iPlayerCount, rFrom.ppPlayers, rFrom.iPlayerCount * sizeof(C4PlayerInfo *)); iPlayerCount += rFrom.iPlayerCount; rFrom.iPlayerCount = rFrom.iPlayerCapacity = 0; delete [] rFrom.ppPlayers; rFrom.ppPlayers = NULL; } else { // no own players: take over buffer of pFrom if (ppPlayers) delete [] ppPlayers; ppPlayers = rFrom.ppPlayers; rFrom.ppPlayers = NULL; iPlayerCount = rFrom.iPlayerCount; rFrom.iPlayerCount = 0; iPlayerCapacity = rFrom.iPlayerCapacity; rFrom.iPlayerCapacity = 0; } } void C4ClientPlayerInfos::AddInfo(C4PlayerInfo *pAddInfo) { // grow list if necessary if (iPlayerCount == iPlayerCapacity) GrowList(4); // add info ppPlayers[iPlayerCount++] = pAddInfo; } void C4ClientPlayerInfos::RemoveIndexedInfo(int32_t iAtIndex) { // bounds check if (iAtIndex<0 || iAtIndex>=iPlayerCount) return; // del player info at index delete ppPlayers[iAtIndex]; // move down last index (may self-assign a ptr) ppPlayers[iAtIndex] = ppPlayers[--iPlayerCount]; } void C4ClientPlayerInfos::RemoveInfo(int32_t idPlr) { // check all infos; remove the one that matches int32_t i = 0; C4PlayerInfo **ppCurrPlrInfo = ppPlayers; while (i < iPlayerCount) { if ((*ppCurrPlrInfo)->GetID() == idPlr) { RemoveIndexedInfo(i); return; } ++ppCurrPlrInfo; ++i; } // none matched return; } void C4ClientPlayerInfos::GrowList(size_t iByVal) { // create new list (out of mem: simply returns here; info list remains in a valid state) C4PlayerInfo **ppNewInfo = new C4PlayerInfo *[iPlayerCapacity += iByVal]; // move existing if (ppPlayers) { memcpy(ppNewInfo, ppPlayers, iPlayerCount * sizeof(C4PlayerInfo *)); delete [] ppPlayers; } // assign new ppPlayers = ppNewInfo; } int32_t C4ClientPlayerInfos::GetFlaggedPlayerCount(DWORD dwFlag) const { // check all players int32_t iCount = 0; int32_t i = iPlayerCount; C4PlayerInfo **ppCurrPlrInfo = ppPlayers; while (i--) if ((*ppCurrPlrInfo++)->GetFlags() | dwFlag) ++iCount; // return number of matching infos return iCount; } C4PlayerInfo *C4ClientPlayerInfos::GetPlayerInfo(int32_t iIndex) const { // check range if (iIndex<0 || iIndex>=iPlayerCount) return NULL; // return indexed info return ppPlayers[iIndex]; } C4PlayerInfo *C4ClientPlayerInfos::GetPlayerInfo(int32_t iIndex, C4PlayerType eType) const { // get indexed matching info for (int32_t iCheck=0; iCheckGetType() == eType) if (!iIndex--) return pNfo; } // nothing found return NULL; } C4PlayerInfo *C4ClientPlayerInfos::GetPlayerInfoByID(int32_t id) const { // check all infos int32_t i = iPlayerCount; C4PlayerInfo **ppCurrPlrInfo = ppPlayers; while (i--) { if ((*ppCurrPlrInfo)->GetID() == id) return *ppCurrPlrInfo; ++ppCurrPlrInfo; } // none matched return NULL; } C4PlayerInfo *C4ClientPlayerInfos::GetPlayerInfoByRes(int32_t idResID) const { int32_t i = iPlayerCount; C4PlayerInfo **ppCurrPlrInfo = ppPlayers; C4Network2Res *pRes; while (i--) { if ((pRes = (*ppCurrPlrInfo)->GetRes())) if (pRes->getResID() == idResID) // only if the player is actually using the resource if ((*ppCurrPlrInfo)->IsUsingPlayerFile()) return *ppCurrPlrInfo; ++ppCurrPlrInfo; } return NULL; } bool C4ClientPlayerInfos::HasUnjoinedPlayers() const { // check all players int32_t i = iPlayerCount; C4PlayerInfo **ppCurrPlrInfo = ppPlayers; while (i--) if (!(*ppCurrPlrInfo++)->HasJoined()) return true; // all joined return false; } int32_t C4ClientPlayerInfos::GetJoinedPlayerCount() const { // count all players with IsJoined() int32_t i = iPlayerCount; int32_t cnt=0; C4PlayerInfo **ppCurrPlrInfo = ppPlayers; while (i--) if ((*ppCurrPlrInfo++)->IsJoined()) ++cnt; return cnt; } void C4ClientPlayerInfos::CompileFunc(StdCompiler *pComp) { bool fCompiler = pComp->isCompiler(); if (fCompiler) Clear(); pComp->Value(mkNamingAdapt(iClientID, "ID", C4ClientIDUnknown)); // Flags StdBitfieldEntry Entries[] = { { "AddPlayers", CIF_AddPlayers }, { "Updated", CIF_Updated }, { "Initial", CIF_Initial }, { "Developer", CIF_Developer }, { NULL, 0 } }; pComp->Value(mkNamingAdapt(mkBitfieldAdapt(dwFlags, Entries), "Flags", 0u)); pComp->Value(mkNamingCountAdapt(iPlayerCount, "Player")); if (iPlayerCount < 0 || iPlayerCount > C4MaxPlayer) { pComp->excCorrupt("player count out of range"); return; } // Grow list, if necessary if (fCompiler && iPlayerCount > iPlayerCapacity) { GrowList(iPlayerCount - iPlayerCapacity); ZeroMem(ppPlayers, sizeof(*ppPlayers) * iPlayerCount); } // Compile pComp->Value(mkNamingAdapt(mkArrayAdaptMap(ppPlayers, iPlayerCount, mkPtrAdaptNoNull), "Player")); // Force specialization mkPtrAdaptNoNull(*ppPlayers); } void C4ClientPlayerInfos::LoadResources() { // load for all players int32_t i = iPlayerCount; C4PlayerInfo **ppCurrPlrInfo = ppPlayers; while (i--) (*ppCurrPlrInfo++)->LoadResource(); } // *** C4PlayerInfoList C4PlayerInfoList::C4PlayerInfoList() : iClientCount(0), iClientCapacity(0), ppClients(NULL), iLastPlayerID(0) { // ctor: no need to alloc mem yet } C4PlayerInfoList::C4PlayerInfoList(const C4PlayerInfoList &rCpy) : iClientCount(rCpy.iClientCount), iClientCapacity(rCpy.iClientCapacity), ppClients(NULL), iLastPlayerID(rCpy.iLastPlayerID) { // copy client info vector if (rCpy.ppClients) { ppClients = new C4ClientPlayerInfos*[iClientCapacity]; C4ClientPlayerInfos **ppInfo = ppClients, **ppCpy = rCpy.ppClients; int32_t i = iClientCount; while (i--) *ppInfo++ = new C4ClientPlayerInfos(**ppCpy++); } } C4PlayerInfoList &C4PlayerInfoList::operator = (const C4PlayerInfoList &rCpy) { Clear(); iClientCount = rCpy.iClientCount; iClientCapacity = rCpy.iClientCapacity; iLastPlayerID = rCpy.iLastPlayerID; if (rCpy.ppClients) { ppClients = new C4ClientPlayerInfos*[iClientCapacity]; C4ClientPlayerInfos **ppInfo = ppClients, **ppCpy = rCpy.ppClients; int32_t i = iClientCount; while (i--) *ppInfo++ = new C4ClientPlayerInfos(**ppCpy++); } else ppClients = NULL; return *this; } void C4PlayerInfoList::Clear() { // delete client infos C4ClientPlayerInfos **ppInfo = ppClients; int32_t i=iClientCount; while (i--) delete *ppInfo++; // clear client infos delete [] ppClients; ppClients = NULL; iClientCount = iClientCapacity = 0; // reset player ID counter iLastPlayerID = 0; } void C4PlayerInfoList::GrowList(size_t iByVal) { // create new list (out of mem: simply returns here; info list remains in a valid state) C4ClientPlayerInfos **ppNewInfo = new C4ClientPlayerInfos *[iClientCapacity += iByVal]; // move existing if (ppClients) { memcpy(ppNewInfo, ppClients, iClientCount * sizeof(C4ClientPlayerInfos *)); delete [] ppClients; } // assign new ppClients = ppNewInfo; } bool C4PlayerInfoList::DoLocalNonNetworkPlayerJoin(const char *szPlayerFile) { // construct joining information C4ClientPlayerInfos *pNewJoin = new C4ClientPlayerInfos(szPlayerFile, true); // handle it bool fSuccess = DoLocalNonNetworkPlayerInfoUpdate(pNewJoin); // done delete pNewJoin; return fSuccess; } bool C4PlayerInfoList::DoPlayerInfoUpdate(C4ClientPlayerInfos *pUpdate) { // never done by clients or in replay - update will be handled via queue if (!::Control.isCtrlHost()) return false; // in network game, process by host. In offline game, just create control bool fSucc = true; if (::Control.isNetwork()) ::Network.Players.RequestPlayerInfoUpdate(*pUpdate); else fSucc = DoLocalNonNetworkPlayerInfoUpdate(pUpdate); return fSucc; } bool C4PlayerInfoList::DoLocalNonNetworkPlayerInfoUpdate(C4ClientPlayerInfos *pUpdate) { // assign IDs first: Must be done early, so AssignTeams works if (!AssignPlayerIDs(pUpdate)) { return false; } // set standard teams AssignTeams(pUpdate, true); // color/name change by team or savegame assignment UpdatePlayerAttributes(pUpdate, true); // add through queue: This will add directly, do the record and put player joins into the queue // in running mode, this call will also put the actual player joins into the queue ::Control.DoInput(CID_PlrInfo, new C4ControlPlayerInfo(*pUpdate), Game.IsRunning ? CDT_Queue : CDT_Direct); // done, success return true; } void C4PlayerInfoList::UpdatePlayerAttributes(C4ClientPlayerInfos *pForInfo, bool fResolveConflicts) { assert(pForInfo); // update colors of all players of this packet C4PlayerInfo *pInfo, *pInfo2; int32_t i=0; while ((pInfo = pForInfo->GetPlayerInfo(i++))) if (!pInfo->HasJoined()) { // assign savegame colors int32_t idSavegameID; bool fHasForcedColor = false; DWORD dwForceClr; if ((idSavegameID = pInfo->GetAssociatedSavegamePlayerID())) if ((pInfo2 = Game.RestorePlayerInfos.GetPlayerInfoByID(idSavegameID))) { dwForceClr = pInfo2->GetColor(); fHasForcedColor = true; } // assign team colors if (!fHasForcedColor && Game.Teams.IsTeamColors()) { C4Team *pPlrTeam = Game.Teams.GetTeamByID(pInfo->GetTeam()); if (pPlrTeam) { dwForceClr = pPlrTeam->GetColor(); fHasForcedColor = true; } } // do color change if (fHasForcedColor && (dwForceClr != pInfo->GetColor())) { pInfo->SetColor(dwForceClr); pForInfo->SetUpdated(); } // make sure colors have correct alpha (modified engines might send malformed packages of transparent colors) if ((pInfo->GetColor() & 0xff000000u) != 0xff000000u) { pInfo->SetColor(pInfo->GetColor() | 0xff000000u); pForInfo->SetUpdated(); } } if (fResolveConflicts) ResolvePlayerAttributeConflicts(pForInfo); } void C4PlayerInfoList::UpdatePlayerAttributes() { // update attributes of all packets int32_t iIdx=0; C4ClientPlayerInfos *pForInfo; while ((pForInfo = GetIndexedInfo(iIdx++))) UpdatePlayerAttributes(pForInfo, false); // now resole all conflicts ResolvePlayerAttributeConflicts(NULL); } bool C4PlayerInfoList::AssignPlayerIDs(C4ClientPlayerInfos *pNewClientInfo) { // assign player IDs to those player infos without C4PlayerInfo *pPlrInfo; int32_t i=0; while ((pPlrInfo = pNewClientInfo->GetPlayerInfo(i++))) if (!pPlrInfo->GetID()) { // are there still any player slots free? if (GetFreePlayerSlotCount()<1) { // nope - then deny this join! Log(FormatString(LoadResStr("IDS_MSG_TOOMANYPLAYERS"), (int)Game.Parameters.MaxPlayers).getData()); pNewClientInfo->RemoveIndexedInfo(--i); continue; } // Join OK; grant an ID pPlrInfo->SetID(++iLastPlayerID); } // return whether any join remains return !!pNewClientInfo->GetPlayerCount(); } int32_t C4PlayerInfoList::GetFreePlayerSlotCount() { // number of free slots depends on max player setting return std::max(Game.Parameters.MaxPlayers - GetStartupCount(), 0); } void C4PlayerInfoList::AssignTeams(C4ClientPlayerInfos *pNewClientInfo, bool fByHost) { if (!Game.Teams.IsMultiTeams()) return; // assign any unset teams (host/standalone only - fByHost determines whether the packet came from the host) C4PlayerInfo *pPlrInfo; int32_t i=0; while ((pPlrInfo = pNewClientInfo->GetPlayerInfo(i++))) Game.Teams.RecheckPlayerInfoTeams(*pPlrInfo, fByHost); } void C4PlayerInfoList::RecheckAutoGeneratedTeams() { // ensure all teams specified in the list exist C4ClientPlayerInfos *pPlrInfos; int32_t j=0; while ((pPlrInfos = GetIndexedInfo(j++))) { C4PlayerInfo *pPlrInfo; int32_t i=0; while ((pPlrInfo = pPlrInfos->GetPlayerInfo(i++))) { int32_t idTeam = pPlrInfo->GetTeam(); if (idTeam) Game.Teams.GetGenerateTeamByID(idTeam); } } } C4ClientPlayerInfos *C4PlayerInfoList::AddInfo(C4ClientPlayerInfos *pNewClientInfo) { assert(pNewClientInfo); // caution: also called for RestorePlayerInfos-list // host: reserve new IDs for all players // client: all IDs should be assigned already by host if (::Network.isHost() || !::Network.isEnabled()) { if (!AssignPlayerIDs(pNewClientInfo) && pNewClientInfo->IsAddPacket()) { // no players could be added (probably MaxPlayer) delete pNewClientInfo; return NULL; } } // ensure all teams specified in the list exist (this should be done for savegame teams as well) C4PlayerInfo *pInfo; int32_t i=0; while ((pInfo = pNewClientInfo->GetPlayerInfo(i++))) { int32_t idTeam = pInfo->GetTeam(); if (idTeam) Game.Teams.GetGenerateTeamByID(idTeam); } // add info for client; overwriting or appending to existing info if necessary // try to find existing data of same client C4ClientPlayerInfos **ppExistingInfo = GetInfoPtrByClientID(pNewClientInfo->GetClientID()); if (ppExistingInfo) { // info exists: append to it? if (pNewClientInfo->IsAddPacket()) { (*ppExistingInfo)->GrabMergeFrom(*pNewClientInfo); // info added: remove unused class delete pNewClientInfo; // assign existing info for further usage in this fn return pNewClientInfo = *ppExistingInfo; } // no add packet: overwrite current info delete *ppExistingInfo; return *ppExistingInfo = pNewClientInfo; } // no existing info: add it directly pNewClientInfo->ResetAdd(); // may need to grow list (vector) for that if (iClientCount >= iClientCapacity) GrowList(4); ppClients[iClientCount++] = pNewClientInfo; // done; return actual info return pNewClientInfo; } C4ClientPlayerInfos **C4PlayerInfoList::GetInfoPtrByClientID(int32_t iClientID) const { // search list for (int32_t i=0; iGetClientID() == iClientID) return ppClients+i; // nothing found return NULL; } int32_t C4PlayerInfoList::GetPlayerCount() const { // count players of all clients int32_t iCount=0; for (int32_t i=0; iGetPlayerCount(); // return it return iCount; } int32_t C4PlayerInfoList::GetJoinIssuedPlayerCount() const { // count players of all clients int32_t iCount=0; for (int32_t i=0; iGetPlayerCount(); ++j) if (pClient->GetPlayerInfo(j)->HasJoinIssued()) ++iCount; } // return it return iCount; } int32_t C4PlayerInfoList::GetActivePlayerCount(bool fCountInvisible) const { // count players of all clients int32_t iCount=0; for (int32_t i=0; iGetPlayerCount(); ++j) { C4PlayerInfo *pInfo = pClient->GetPlayerInfo(j); if (!pInfo->IsRemoved()) if (fCountInvisible || !pInfo->IsInvisible()) ++iCount; } } // return it return iCount; } int32_t C4PlayerInfoList::GetActiveScriptPlayerCount(bool fCountSavegameResumes, bool fCountInvisible) const { // count players of all clients int32_t iCount=0; for (int32_t i=0; iGetPlayerCount(); ++j) { C4PlayerInfo *pNfo = pClient->GetPlayerInfo(j); if (!pNfo->IsRemoved()) if (pNfo->GetType() == C4PT_Script) if (fCountSavegameResumes || !pNfo->GetAssociatedSavegamePlayerID()) if (fCountInvisible || !pNfo->IsInvisible()) ++iCount; } } // return it return iCount; } StdStrBuf C4PlayerInfoList::GetActivePlayerNames(bool fCountInvisible, int32_t iAtClientID) const { // add up players of all clients StdStrBuf sPlr; int32_t iCount=0; for (int32_t i=0; iGetClientID() != iAtClientID) continue; for (int32_t j=0; jGetPlayerCount(); ++j) { C4PlayerInfo *pInfo = pClient->GetPlayerInfo(j); if (!pInfo->IsRemoved()) if (fCountInvisible || !pInfo->IsInvisible()) { if (iCount++) { // not first name: Add separator sPlr.Append(", "); } sPlr.Append(pInfo->GetName()); } } } // return it return sPlr; } C4PlayerInfo *C4PlayerInfoList::GetPlayerInfoByIndex(int32_t index) const { // check all packets for a player for (int32_t i=0; iGetPlayerInfo(j++))) if (index-- <= 0) return pInfo; } // nothing found return NULL; } C4PlayerInfo *C4PlayerInfoList::GetPlayerInfoByID(int32_t id) const { // must be a valid ID assert(id); // check all packets for a player for (int32_t i=0; iGetPlayerInfo(j++))) if (pInfo->GetID() == id) return pInfo; } // nothing found return NULL; } C4ClientPlayerInfos *C4PlayerInfoList::GetClientInfoByPlayerID(int32_t id) const { // get client info that contains a specific player assert(id); for (int32_t i=0; iGetPlayerInfo(j++))) if (pInfo->GetID() == id) return ppClients[i]; } // nothing found return NULL; } C4PlayerInfo *C4PlayerInfoList::GetPlayerInfoByID(int32_t id, int32_t *pidClient) const { // must be a valid ID assert(id); assert(pidClient); // check all packets for a player for (int32_t i=0; iGetPlayerInfo(j++))) if (pInfo->GetID() == id) { *pidClient = ppClients[i]->GetClientID(); return pInfo; } } // nothing found return NULL; } C4PlayerInfo *C4PlayerInfoList::GetPlayerInfoBySavegameID(int32_t id) const { // must be a valid ID assert(id); // check all packets for a player for (int32_t i=0; iGetPlayerInfo(j++))) if (pInfo->GetAssociatedSavegamePlayerID() == id) return pInfo; } // nothing found return NULL; } C4PlayerInfo *C4PlayerInfoList::GetNextPlayerInfoByID(int32_t id) const { // check all packets for players C4PlayerInfo *pSmallest=NULL; for (int32_t i=0; iGetPlayerInfo(j++))) if (pInfo->GetID() > id) if (!pSmallest || pSmallest->GetID()>pInfo->GetID()) pSmallest = pInfo; } // return best found return pSmallest; } C4PlayerInfo *C4PlayerInfoList::GetActivePlayerInfoByName(const char *szName) { // check all packets for matching players for (int32_t i=0; iGetPlayerInfo(j++))) if (!pInfo->IsRemoved()) if (SEqualNoCase(szName, pInfo->GetName())) return pInfo; } // nothing found return NULL; } C4PlayerInfo *C4PlayerInfoList::FindSavegameResumePlayerInfo(const C4PlayerInfo *pMatchInfo, MatchingLevel mlMatchStart, MatchingLevel mlMatchEnd) const { assert(pMatchInfo); // try different matching levels using the infamous for-case-paradigm for (int iMatchLvl = mlMatchStart; iMatchLvl <= mlMatchEnd; ++iMatchLvl) { for (int32_t i=0; iGetPlayerInfo(j++))) if (!Game.PlayerInfos.GetPlayerInfoByID(pInfo->GetID()) && !Game.PlayerInfos.GetPlayerInfoBySavegameID(pInfo->GetID())) // only unassigned player infos switch (iMatchLvl) { case PML_PlrFileName: // file name and player name must match if (!pMatchInfo->GetFilename() || !pInfo->GetFilename()) break; if (!SEqualNoCase(GetFilename(pMatchInfo->GetFilename()), GetFilename(pInfo->GetFilename()))) break; // nobreak: Check player name as well case PML_PlrName: // match player name if (SEqualNoCase(pMatchInfo->GetName(), pInfo->GetName())) return pInfo; break; case PML_PrefColor: // match player color if (pMatchInfo->GetOriginalColor() == pInfo->GetOriginalColor()) return pInfo; break; case PML_Any: // match anything return pInfo; } } } // no match return NULL; } C4PlayerInfo *C4PlayerInfoList::FindUnassociatedRestoreInfo(const C4PlayerInfoList &rRestoreInfoList) { // search given list for a player that's not associated locally C4ClientPlayerInfos *pRestoreClient; int32_t iClient=0; while ((pRestoreClient = rRestoreInfoList.GetIndexedInfo(iClient++))) { C4PlayerInfo *pRestoreInfo; int32_t iInfo=0; while ((pRestoreInfo = pRestoreClient->GetPlayerInfo(iInfo++))) if (pRestoreInfo->IsJoined()) // match association either by savegame ID (before C4Game::InitPlayers) or real ID (after C4Game::InitPlayers) if (!GetPlayerInfoBySavegameID(pRestoreInfo->GetID()) && !GetPlayerInfoByID(pRestoreInfo->GetID())) return pRestoreInfo; } // no unassociated info found return NULL; } bool C4PlayerInfoList::HasSameTeamPlayers(int32_t iClient1, int32_t iClient2) const { // compare all player teams of clients const C4ClientPlayerInfos *pCnfo1 = GetInfoByClientID(iClient1); const C4ClientPlayerInfos *pCnfo2 = GetInfoByClientID(iClient2); if (!pCnfo1 || !pCnfo2) return false; int32_t i=0,j; const C4PlayerInfo *pNfo1, *pNfo2; while ((pNfo1 = pCnfo1->GetPlayerInfo(i++))) { if (!pNfo1->IsUsingTeam()) continue; j=0; while ((pNfo2 = pCnfo2->GetPlayerInfo(j++))) { if (!pNfo2->IsUsingTeam()) continue; if (pNfo2->GetTeam() == pNfo1->GetTeam()) // match found! return true; } } // no match return false; } bool C4PlayerInfoList::Load(C4Group &hGroup, const char *szFromFile, C4LangStringTable *pLang) { // clear previous Clear(); // load file contents StdStrBuf Buf; if (!hGroup.LoadEntryString(szFromFile, &Buf)) // no file is OK; means no player infos return true; // replace strings if (pLang) pLang->ReplaceStrings(Buf); // (try to) compile if (!CompileFromBuf_LogWarn( mkNamingAdapt(*this, "PlayerInfoList"), Buf, szFromFile)) return false; // done, success return true; } bool C4PlayerInfoList::Save(C4Group &hGroup, const char *szToFile) { // remove previous entry from group hGroup.DeleteEntry(szToFile); // anything to save? if (!iClientCount) return true; // save it try { // decompile StdStrBuf Buf = DecompileToBuf( mkNamingAdapt(*this, "PlayerInfoList")); // save buffer to group hGroup.Add(szToFile, Buf, false, true); } catch (StdCompiler::Exception *) { return false; } // done, success return true; } bool C4PlayerInfoList::InitLocal() { // not in replay if (Game.C4S.Head.Replay) return true; // no double init assert(!GetInfoCount()); // no network assert(!::Network.isEnabled()); // create player info for local player joins C4ClientPlayerInfos *pLocalInfo = new C4ClientPlayerInfos(Game.PlayerFilenames); // register local info immediately pLocalInfo = AddInfo(pLocalInfo); // Script players in restore infos need to be associated with matching script players in main info list CreateRestoreInfosForJoinedScriptPlayers(Game.RestorePlayerInfos); // and assign teams if (Game.Teams.IsMultiTeams() && pLocalInfo) AssignTeams(pLocalInfo, true); // done, success return true; } bool C4PlayerInfoList::LocalJoinUnjoinedPlayersInQueue() { // local call only - in network, C4Network2Players joins players! assert(!::Network.isEnabled()); // get local players C4ClientPlayerInfos **ppkLocal = GetInfoPtrByClientID(::Control.ClientID()), *pkLocal; if (!ppkLocal) return false; pkLocal = *ppkLocal; // check all players int32_t i=0; C4PlayerInfo *pInfo; while ((pInfo = pkLocal->GetPlayerInfo(i++))) // not yet joined? if (!pInfo->HasJoinIssued()) { // join will be marked when queue is executed (C4Player::Join) // but better mark join now already to prevent permanent sending overkill pInfo->SetJoinIssued(); // join by filename if possible. Script players may not have a filename assigned though const char *szFilename = pInfo->GetFilename(); if (!szFilename && (pInfo->GetType() != C4PT_Script)) { // failure for user players const char *szPlrName = pInfo->GetName(); if (!szPlrName) szPlrName="???"; LogF(LoadResStr("IDS_ERR_JOINQUEUEPLRS"), szPlrName); continue; } Game.Input.Add(CID_JoinPlr, new C4ControlJoinPlayer(szFilename, ::Control.ClientID(), pInfo->GetID())); } // done, success return true; } void C4PlayerInfoList::CreateRestoreInfosForJoinedScriptPlayers(C4PlayerInfoList &rSavegamePlayers) { // create matching script player joins for all script playeers in restore info // Just copy their infos to the first client int32_t i; C4ClientPlayerInfos *pHostInfo = GetIndexedInfo(0); for (i=0; iGetPlayerInfo(j++))) if (pInfo->GetType() == C4PT_Script) { // safety C4PlayerInfo *pRejoinInfo; if ((pRejoinInfo = GetPlayerInfoBySavegameID(pInfo->GetID()))) { LogF("Warning: User player %s takes over script player %s!", pRejoinInfo->GetName(), pInfo->GetName()); continue; } if (!pHostInfo) { LogF("Error restoring savegame script players: No host player infos to add to!"); continue; } // generate takeover info pRejoinInfo = new C4PlayerInfo(*pInfo); pRejoinInfo->SetAssociatedSavegamePlayer(pInfo->GetID()); pHostInfo->AddInfo(pRejoinInfo); } } // teams must recognize the change Game.Teams.RecheckPlayers(); } bool C4PlayerInfoList::RestoreSavegameInfos(C4PlayerInfoList &rSavegamePlayers) { // any un-associated players? if (rSavegamePlayers.GetPlayerCount()) { // for runtime network joins, this should never happen! assert(!Game.C4S.Head.NetworkRuntimeJoin); // do savegame player association of real players // for non-lobby games do automatic association first int32_t iNumGrabbed = 0, i; if (!::Network.isEnabled() && Game.C4S.Head.SaveGame) { // do several passes: First passes using regular player matching; following passes matching anything but with a warning message for (int eMatchingLevel = 0; eMatchingLevel <= PML_Any; ++eMatchingLevel) for (int32_t i=0; iGetPlayerInfo(j++))) if (!(id = pInfo->GetAssociatedSavegamePlayerID())) if ((pSavegameInfo = rSavegamePlayers.FindSavegameResumePlayerInfo(pInfo, (MatchingLevel)eMatchingLevel, (MatchingLevel)eMatchingLevel))) { pInfo->SetAssociatedSavegamePlayer(pSavegameInfo->GetID()); if (eMatchingLevel > PML_PlrName) { // this is a "wild" match: Warn the player (but not in replays) StdStrBuf sMsg; sMsg.Format(LoadResStr("IDS_MSG_PLAYERASSIGNMENT"), pInfo->GetName(), pSavegameInfo->GetName()); Log(sMsg.getData()); if (::pGUI && FullScreen.Active && !Game.C4S.Head.Replay) ::pGUI->ShowMessageModal(sMsg.getData(), LoadResStr("IDS_MSG_FREESAVEGAMEPLRS"), C4GUI::MessageDialog::btnOK, C4GUI::Ico_Notify, &Config.Startup.HideMsgPlrTakeOver); } } } } // association complete: evaluate it for (i=0; iGetPlayerInfo(j++))) if ((id = pInfo->GetAssociatedSavegamePlayerID())) { if ((pSavegameInfo = rSavegamePlayers.GetPlayerInfoByID(id))) { // pInfo continues for pSavegameInfo pInfo->SetSavegameResume(pSavegameInfo); ++iNumGrabbed; } else { // shouldn't happen assert(!"Invalid savegame association"); } } else { // no association for this info: Joins as new player // in savegames, this is unusual. For regular script player restore, it's not if (Game.C4S.Head.SaveGame) LogF(LoadResStr("IDS_PRC_RESUMENOPLRASSOCIATION"), (const char *)pInfo->GetName()); } } // otherwise any remaining players int32_t iCountRemaining = rSavegamePlayers.GetPlayerCount() - iNumGrabbed; if (iCountRemaining) { // in replay mode, if there are no regular player joins, it must have been a runtime record // i.e., a record that was started during the game // in this case, the savegame player infos equal the real player infos to be used if (::Control.isReplay() && !GetInfoCount()) { *this = rSavegamePlayers; } else { // in regular mode, these players must be removed LogF(LoadResStr("IDS_PRC_RESUMEREMOVEPLRS"), iCountRemaining); // remove them directly from the game RemoveUnassociatedPlayers(rSavegamePlayers); } } } // now that players are restored, restore teams Game.Teams.RecheckPlayers(); // done, success return true; } bool C4PlayerInfoList::RecreatePlayerFiles() { // Note that this method will be called on the main list for savegame resumes (even in network) or regular games with RecreateInfos, // and on RestorePlayerInfos for runtime network joins // check all player files that need to be recreated for (int32_t i=0; iGetPlayerInfo(j++))) if (pInfo->IsJoined()) { // all players in replays and runtime joins; script players even in savegames need to be restored from the scenario goup if (Game.C4S.Head.Replay || Game.C4S.Head.NetworkRuntimeJoin || pInfo->GetType() == C4PT_Script) { // in this case, a filename must have been assigned while saving // and mark a file inside the scenario file // get filename of joined player - this should always be valid! const char *szCurrPlrFile; StdStrBuf sFilenameInRecord; if (Game.C4S.Head.Replay) { // replay of resumed savegame: RecreatePlayers saves used player files into the record group in this manner sFilenameInRecord.Format("Recreate-%d.ocp", pInfo->GetID()); szCurrPlrFile = sFilenameInRecord.getData(); } else szCurrPlrFile = pInfo->GetFilename(); const char *szPlrName = pInfo->GetName(); if (!szPlrName) szPlrName = "???"; if (!szCurrPlrFile || !*szCurrPlrFile) { // that's okay for script players, because those may join w/o recreation files if (pInfo->GetType() != C4PT_Script) { LogF(LoadResStr("IDS_ERR_LOAD_RECR_NOFILE"), szPlrName); } continue; } // join from temp file StdCopyStrBuf szJoinPath; szJoinPath = Config.AtTempPath(GetFilename(szCurrPlrFile)); // extract player there if (!Game.ScenarioFile.FindEntry(GetFilename(szCurrPlrFile)) || !Game.ScenarioFile.Extract(GetFilename(szCurrPlrFile), szJoinPath.getData())) { // that's okay for script players, because those may join w/o recreation files if (pInfo->GetType() != C4PT_Script) { LogF(LoadResStr("IDS_ERR_LOAD_RECR_NOEXTRACT"), szPlrName, GetFilename(szCurrPlrFile)); } continue; } // set join source pInfo->SetFilename(szJoinPath.getData()); pInfo->DiscardResource(); // setting a temp file here will cause the player file to be deleted directly after recreation // if recreation fails (e.g. the game gets aborted due to invalid files), the info dtor will delete the file pInfo->SetTempFile(); } else { // regular player in savegame being resumed in network or normal mode: // the filenames and/or resources should have been assigned // a) either in lobby mode during player re-acquisition // b) or when players from rSavegamePlayers were taken over } } else if (!pInfo->HasJoinIssued()) { // new players to be joined into the game: // regular control queue join can be done; no special handling needed } } // done, success return true; } bool C4PlayerInfoList::RecreatePlayers(C4ValueNumbers * numbers) { // check all player infos for (int32_t i=0; iGetJoinedPlayerCount()) continue; // determine client ID and name // client IDs must be set correctly even in replays, // so client-removal packets are executed correctly int32_t idAtClient = pkInfo->GetClientID(); const char *szAtClientName; if (Game.C4S.Head.Replay) // the client name can currently not really be retrieved in replays // but it's not used anyway szAtClientName = "Replay"; else // local non-network non-replay games set local name if (!::Network.isEnabled()) { assert(idAtClient == ::Control.ClientID()); szAtClientName = "Local"; } else { // network non-replay games: find client and set name by it const C4Client *pClient = Game.Clients.getClientByID(idAtClient); if (pClient) szAtClientName = pClient->getName(); else { // this shouldn't happen - remove the player info LogF(LoadResStr("IDS_PRC_RESUMENOCLIENT"), idAtClient, pkInfo->GetPlayerCount()); continue; } } // rejoin all joined players of that client int32_t j=0; C4PlayerInfo *pInfo; while ((pInfo = pkInfo->GetPlayerInfo(j++))) if (pInfo->IsJoined()) { // get filename to join from const char *szFilename = pInfo->GetLocalJoinFilename(); // ensure resource is loaded, if joining from resource // this may display a waiting dialog and block the thread for a while C4Network2Res *pJoinRes = pInfo->GetRes(); if (szFilename && pJoinRes && pJoinRes->isLoading()) { const char *szName = pInfo->GetName(); if (!::Network.RetrieveRes(pJoinRes->getCore(), C4NetResRetrieveTimeout, FormatString(LoadResStr("IDS_NET_RES_PLRFILE"), szName).getData())) szFilename=NULL; } // file present? if (!szFilename || !*szFilename) { if (pInfo->GetType() == C4PT_User) { // for user players, this could happen only if the user cancelled the resource const char *szPlrName = pInfo->GetName(); if (!szPlrName) szPlrName = "???"; LogF(LoadResStr("IDS_ERR_LOAD_RECR_NOFILEFROMNET"), szPlrName); continue; } else { // for script players: Recreation without filename OK szFilename = NULL; } } // record file handling: Save to the record file in the manner it's expected by C4PlayerInfoList::RecreatePlayers if (::Control.isRecord() && szFilename) { StdStrBuf sFilenameInRecord; sFilenameInRecord.Format("Recreate-%d.ocp", pInfo->GetID()); ::Control.RecAddFile(szFilename, sFilenameInRecord.getData()); } // recreate join directly ::Players.Join(szFilename, false, idAtClient, szAtClientName, pInfo, numbers); // delete temporary files immediately if (pInfo->IsTempFile()) pInfo->DeleteTempFile(); } } // done! return true; } void C4PlayerInfoList::RemoveUnassociatedPlayers(C4PlayerInfoList &rSavegamePlayers) { // check all joined infos C4ClientPlayerInfos *pClient; int iClient=0; while ((pClient = rSavegamePlayers.GetIndexedInfo(iClient++))) { C4PlayerInfo *pInfo; int iInfo = 0; while ((pInfo = pClient->GetPlayerInfo(iInfo++))) { // remove players that were in the game but are not associated if (pInfo->IsJoined() && !GetPlayerInfoBySavegameID(pInfo->GetID())) { if (::Players.RemoveUnjoined(pInfo->GetInGameNumber())) { LogF(LoadResStr("IDS_PRC_REMOVEPLR"), pInfo->GetName()); } } pInfo->SetRemoved(); } } } bool C4PlayerInfoList::SetAsRestoreInfos(C4PlayerInfoList &rFromPlayers, bool fSaveUserPlrs, bool fSaveScriptPlrs, bool fSetUserPlrRefToLocalGroup, bool fSetScriptPlrRefToLocalGroup) { // copy everything *this = rFromPlayers; // then remove everything that's no longer joined and update the rest C4ClientPlayerInfos *pClient; int iClient=0; while ((pClient = GetIndexedInfo(iClient++))) { // update all players for this client C4PlayerInfo *pInfo; int iInfo = 0; while ((pInfo = pClient->GetPlayerInfo(iInfo++))) { bool fKeepInfo = false; // remove players that are not in the game if (pInfo->IsJoined()) { // pre-reset filename pInfo->SetFilename(NULL); if (pInfo->GetType() == C4PT_User) { fKeepInfo = fSaveUserPlrs; if (fSetUserPlrRefToLocalGroup) { // in the game: Set filename for inside savegame file StdStrBuf sNewName; if (::Network.isEnabled()) { C4Client *pGameClient = Game.Clients.getClientByID(pClient->GetClientID()); const char *szName = pGameClient ? pGameClient->getName() : "Unknown"; sNewName.Format("%s-%s", szName, (const char *) GetFilename(pInfo->GetLocalJoinFilename())); } else sNewName.Copy(GetFilename(pInfo->GetFilename())); // O(n) is fast. // If not, blame whoever wrote Replace! ;) sNewName.Replace("%", "%25", 0); for (int ch = 128; ch < 256; ++ch) { const char* hexChars = "0123456789abcdef"; char old[] = { char(ch), 0 }; char safe[] = { '%', 'x', 'x', 0 }; safe[1] = hexChars[ch / 16]; safe[2] = hexChars[ch % 16]; sNewName.Replace(old, safe, 0); } pInfo->SetFilename(sNewName.getData()); } } else if (pInfo->GetType() == C4PT_Script) { fKeepInfo = fSaveScriptPlrs; if (fSetScriptPlrRefToLocalGroup) { // just compose a unique filename for script player pInfo->SetFilename(FormatString("ScriptPlr-%d.ocp", (int)pInfo->GetID()).getData()); } } } if (!fKeepInfo) { pClient->RemoveIndexedInfo(--iInfo); } else { pInfo->DiscardResource(); } } // remove empty clients if (!pClient->GetPlayerCount()) { RemoveInfo(GetInfoPtrByClientID(pClient->GetClientID())); delete pClient; --iClient; } } // done return true; } void C4PlayerInfoList::ResetLeagueProjectedGain(bool fSetUpdated) { C4ClientPlayerInfos *pClient; int iClient=0; while ((pClient = GetIndexedInfo(iClient++))) { C4PlayerInfo *pInfo; int iInfo = 0; while ((pInfo = pClient->GetPlayerInfo(iInfo++))) if (pInfo->IsLeagueProjectedGainValid()) { pInfo->ResetLeagueProjectedGain(); if (fSetUpdated) pClient->SetUpdated(); } } } void C4PlayerInfoList::CompileFunc(StdCompiler *pComp) { bool fCompiler = pComp->isCompiler(); if (fCompiler) Clear(); // skip compiling if there is nothing to compile (cosmentics) if (!fCompiler && pComp->hasNaming() && iLastPlayerID == 0 && iClientCount == 0) return; // header pComp->Value(mkNamingAdapt(iLastPlayerID, "LastPlayerID", 0)); // client count int32_t iTemp = iClientCount; pComp->Value(mkNamingCountAdapt(iTemp, "Client")); if (iTemp < 0 || iTemp > C4MaxClient) { pComp->excCorrupt("client count out of range"); return; } // grow list if (fCompiler) { if (iTemp > iClientCapacity) GrowList(iTemp - iClientCapacity); iClientCount = iTemp; ZeroMem(ppClients, sizeof(*ppClients) * iClientCount); } // client packets pComp->Value( mkNamingAdapt( mkArrayAdaptMap(ppClients, iClientCount, mkPtrAdaptNoNull), "Client")); // force compiler to specialize mkPtrAdaptNoNull(*ppClients); } int32_t C4PlayerInfoList::GetStartupCount() { // count all joined and to-be-joined int32_t iCnt=0; for (int32_t i=0; iGetPlayerInfo(j++))) if (!pInfo->IsRemoved()) ++iCnt; } return iCnt; } void C4PlayerInfoList::LoadResources() { // load for all players int32_t i = iClientCount; C4ClientPlayerInfos **ppClient=ppClients; while (i--) (*ppClient++)->LoadResources(); } void C4PlayerInfoList::FixIDCounter() { // make sure ID counter is same as largest info for (int32_t i=0; iGetPlayerInfo(j++))) { iLastPlayerID = std::max(pInfo->GetID(), iLastPlayerID); } } } /* -- Player info packets -- */ void C4PacketPlayerInfoUpdRequest::CompileFunc(StdCompiler *pComp) { pComp->Value(Info); } void C4PacketPlayerInfo::CompileFunc(StdCompiler *pComp) { pComp->Value(mkNamingAdapt(fIsRecreationInfo, "Recreation", false)); pComp->Value(mkNamingAdapt(Info, "Info")); }