1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 1998-2000, Matthes Bender
5  * Copyright (c) 2004-2009, RedWolf Design GmbH, http://www.clonk.de/
6  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
7  *
8  * Distributed under the terms of the ISC license; see accompanying file
9  * "COPYING" for details.
10  *
11  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12  * See accompanying file "TRADEMARK" for details.
13  *
14  * To redistribute this file separately, substitute the full license texts
15  * for the above references.
16  */
17 // permanent player information management
18 // see header for some additional information
19 
20 #include "C4Include.h"
21 #include "control/C4PlayerInfo.h"
22 
23 #include "c4group/C4Components.h"
24 #include "control/C4GameControl.h"
25 #include "game/C4FullScreen.h"
26 #include "player/C4Player.h"
27 #include "player/C4PlayerList.h"
28 
29 // *** C4PlayerInfo
30 
Clear()31 void C4PlayerInfo::Clear()
32 {
33 	// del temp file
34 	DeleteTempFile();
35 	// clear fields
36 	sName.Clear(); szFilename.Clear();
37 	pRes = nullptr;
38 	ResCore.Clear();
39 	// default fields
40 	dwColor = dwOriginalColor = 0xffffffff;
41 	dwAlternateColor = 0;
42 	dwFlags = 0;
43 	iID = idSavegamePlayer = idTeam = 0;
44 	iInGameNumber = iInGameJoinFrame = iInGamePartFrame = -1;
45 	sLeagueAccount = ""; iLeagueScore=iLeagueRank=0;
46 	iLeagueProjectedGain = -1;
47 	eType = C4PT_User;
48 	idExtraData = C4ID::None;
49 	iLeaguePerformance = 0;
50 	sLeagueProgressData.Clear();
51 }
52 
DeleteTempFile()53 void C4PlayerInfo::DeleteTempFile()
54 {
55 	// is temp file?
56 	if (!! szFilename && (dwFlags & PIF_TempFile))
57 	{
58 		// erase it
59 		EraseItem(szFilename.getData());
60 		// reset flag and filename to prevent double deletion
61 		dwFlags &= ~PIF_TempFile;
62 		szFilename.Clear();
63 	}
64 }
65 
LoadFromLocalFile(const char * szFilename)66 bool C4PlayerInfo::LoadFromLocalFile(const char *szFilename)
67 {
68 	// players should not be added in replay mode
69 	assert(!Game.C4S.Head.Replay);
70 	// clear previous
71 	Clear();
72 	// open player file group
73 	C4Group Grp;
74 	if (!Reloc.Open(Grp, szFilename)) return false;
75 
76 	// read core
77 	C4PlayerInfoCore C4P;
78 	if (!C4P.Load(Grp)) return false;
79 	// set values
80 	eType = C4PT_User;
81 	sName = C4P.PrefName;
82 	this->szFilename = szFilename;
83 	dwColor = dwOriginalColor = 0xff000000 | (C4P.PrefColorDw & 0xffffff); // ignore alpha
84 	dwAlternateColor = 0xff000000 | (C4P.PrefColor2Dw & 0xffffff); // ignore alpha
85 	// network: resource (not for replays, because everyone has the player files there...)
86 	if (::Network.isEnabled() && !Game.C4S.Head.Replay)
87 	{
88 		// add resource
89 		// 2do: rejoining players need to update their resource version when saving the player
90 		// otherwise, player file versions may differ
91 		pRes = ::Network.ResList.getRefRes(szFilename, true);
92 		// not found? add
93 		if (!pRes) pRes = ::Network.ResList.AddByGroup(&Grp, false, NRT_Player, -1, szFilename);
94 		if (!pRes) return false;
95 		// set core and flag
96 		ResCore = pRes->getCore();
97 		dwFlags |= PIF_HasRes;
98 		// filename is no longer needed in network mode, because it's stored in the res-core
99 	}
100 	// done, success
101 	return true;
102 }
103 
SetAsScriptPlayer(const char * szName,uint32_t dwColor,uint32_t dwFlags,C4ID idExtra)104 bool C4PlayerInfo::SetAsScriptPlayer(const char *szName, uint32_t dwColor, uint32_t dwFlags, C4ID idExtra)
105 {
106 	// clear previous
107 	Clear();
108 	// set parameters
109 	eType = C4PT_Script;
110 	this->dwColor = dwOriginalColor = 0xff000000 | (dwColor & 0xffffff); // ignore alpha
111 	dwAlternateColor = 0;
112 	this->sName.CopyValidated(szName);
113 	idExtraData = idExtra;
114 	this->dwFlags |= dwFlags;
115 	// done, success
116 	return true;
117 }
118 
GetLocalJoinFilename() const119 const char *C4PlayerInfo::GetLocalJoinFilename() const
120 {
121 	// preferred: by resource
122 	if (pRes) return pRes->getFile();
123 	// if no resource is known (replay or non-net), return filename
124 	return szFilename.getData();
125 }
126 
GetLobbyColor() const127 uint32_t C4PlayerInfo::GetLobbyColor() const
128 {
129 	// special case if random teams and team colors are enabled in lobby:
130 	// Unjoined players do not show their team! Instead, they just display their original color
131 	if (Game.Teams.GetTeamDist() == C4TeamList::TEAMDIST_RandomInv)
132 		if (Game.Teams.IsTeamColors())
133 			if (Game.Teams.GetTeamByID(GetTeam()))
134 				if (!HasJoined() && !GetAssociatedSavegamePlayerID())
135 					return GetOriginalColor();
136 	// otherwise, just show the normal player color
137 	return GetColor();
138 }
139 
GetLobbyName() const140 StdStrBuf C4PlayerInfo::GetLobbyName() const
141 {
142 	// return player name including colored clan/team tag if known
143 	StdStrBuf sResult;
144 	if (sLeagueAccount.getLength())
145 	{
146 		if (sClanTag.getLength())
147 		{
148 			// gray team tag color used in lobby and game evaluation dialog!
149 			sResult.Format("<c afafaf>%s</c> %s", sClanTag.getData(), sLeagueAccount.getData());
150 		}
151 		else
152 			sResult.Ref(sLeagueAccount);
153 	}
154 	else
155 	{
156 		// fallback to regular player name
157 		sResult.Ref(sForcedName.getLength() ? static_cast<const StdStrBuf &>(sForcedName) : static_cast<const StdStrBuf &>(sName));
158 	}
159 	return sResult;
160 }
161 
HasTeamWon() const162 bool C4PlayerInfo::HasTeamWon() const
163 {
164 	// team win/solo win
165 	C4Team *pTeam;
166 	if (idTeam && (pTeam = Game.Teams.GetTeamByID(idTeam)))
167 		return pTeam->HasWon();
168 	else
169 		return HasWon();
170 }
171 
CompileFunc(StdCompiler * pComp)172 void C4PlayerInfo::CompileFunc(StdCompiler *pComp)
173 {
174 	// Names
175 	pComp->Value(mkNamingAdapt(sName, "Name", ""));
176 	pComp->Value(mkNamingAdapt(sForcedName, "ForcedName", ""));
177 	pComp->Value(mkNamingAdapt(szFilename, "Filename", ""));
178 
179 	// Flags
180 	const StdBitfieldEntry<uint16_t> Entries[] =
181 	{
182 		{ "Joined", PIF_Joined },
183 		{ "Removed", PIF_Removed },
184 		{ "HasResource", PIF_HasRes },
185 		{ "JoinIssued", PIF_JoinIssued },
186 		{ "SavegameJoin", PIF_JoinedForSavegameOnly },
187 		{ "Disconnected", PIF_Disconnected },
188 		{ "VotedOut", PIF_VotedOut },
189 		{ "Won", PIF_Won },
190 		{ "AttributesFixed", PIF_AttributesFixed },
191 		{ "NoScenarioInit", PIF_NoScenarioInit },
192 		{ "NoScenarioSave", PIF_NoScenarioSave },
193 		{ "NoEliminationCheck", PIF_NoEliminationCheck },
194 		{ "Invisible", PIF_Invisible},
195 		{ nullptr, 0 },
196 	};
197 	uint16_t dwSyncFlags = dwFlags & PIF_SyncFlags; // do not store local flags!
198 	pComp->Value(mkNamingAdapt(mkBitfieldAdapt(dwSyncFlags, Entries), "Flags", 0u));
199 	if (pComp->isDeserializer()) dwFlags = dwSyncFlags;
200 	pComp->Value(mkNamingAdapt(iID, "ID", 0));
201 
202 	// type
203 	StdEnumEntry<C4PlayerType> PlayerTypes[] =
204 	{
205 		{ "User",   C4PT_User },
206 		{ "Script", C4PT_Script },
207 
208 		{ nullptr,  C4PT_User },
209 	};
210 	pComp->Value(mkNamingAdapt(mkEnumAdaptT<uint8_t>(eType, PlayerTypes), "Type", C4PT_User));
211 
212 	// safety: Do not allow invisible regular players
213 	if (pComp->isDeserializer())
214 	{
215 		if (eType != C4PT_Script) dwFlags &= ~PIF_Invisible;
216 	}
217 
218 	// load colors
219 	pComp->Value(mkNamingAdapt(dwColor, "Color", 0u));
220 	pComp->Value(mkNamingAdapt(dwOriginalColor, "OriginalColor", dwColor));
221 	// load savegame ID
222 	pComp->Value(mkNamingAdapt(mkIntPackAdapt(idSavegamePlayer), "SavgamePlayer", 0));
223 	// load team ID
224 	pComp->Value(mkNamingAdapt(mkIntPackAdapt(idTeam), "Team", 0));
225 	// load authentication ID
226 	pComp->Value(mkNamingAdapt(szAuthID, "AUID", ""));
227 
228 	// InGame info
229 	if (dwFlags & PIF_Joined)
230 	{
231 		pComp->Value(mkNamingAdapt(iInGameNumber,    "GameNumber", -1));
232 		pComp->Value(mkNamingAdapt(iInGameJoinFrame, "GameJoinFrame", -1));
233 	}
234 	else
235 		iInGameNumber = iInGameJoinFrame = -1;
236 
237 	if (dwFlags & PIF_Removed)
238 		pComp->Value(mkNamingAdapt(iInGamePartFrame, "GamePartFrame", -1));
239 	else
240 		iInGamePartFrame = -1;
241 
242 	// script player extra data
243 	pComp->Value(mkNamingAdapt(idExtraData, "ExtraData", C4ID::None));
244 
245 	// load league info
246 	pComp->Value(mkNamingAdapt(sLeagueAccount, "LeagueAccount", ""));
247 	pComp->Value(mkNamingAdapt(mkIntPackAdapt(iLeagueScore), "LeagueScore", 0));
248 	pComp->Value(mkNamingAdapt(mkIntPackAdapt(iLeagueRank), "LeagueRank", 0));
249 	pComp->Value(mkNamingAdapt(mkIntPackAdapt(iLeagueRankSymbol), "LeagueRankSymbol", 0));
250 	pComp->Value(mkNamingAdapt(mkIntPackAdapt(iLeagueProjectedGain), "ProjectedGain", -1));
251 	pComp->Value(mkNamingAdapt(mkParAdapt(sClanTag, StdCompiler::RCT_All), "ClanTag", ""));
252 	pComp->Value(mkNamingAdapt(mkIntPackAdapt(iLeaguePerformance), "LeaguePerformance", 0));
253 	pComp->Value(mkNamingAdapt(sLeagueProgressData, "LeagueProgressData", ""));
254 
255 	// file resource
256 	if (pComp->isDeserializer() && Game.C4S.Head.Replay)
257 	{
258 		// Replays don't have player resources, drop the flag
259 		dwFlags &= ~PIF_HasRes;
260 	}
261 	if (dwFlags & PIF_HasRes)
262 	{
263 		// ResCore
264 		if (pComp->isSerializer() && pRes)
265 		{
266 			// ensure ResCore is up-to-date
267 			ResCore = pRes->getCore();
268 		}
269 		pComp->Value(mkNamingAdapt(ResCore, "ResCore"));
270 	}
271 
272 }
273 
SetFilename(const char * szToFilename)274 void C4PlayerInfo::SetFilename(const char *szToFilename)
275 {
276 	szFilename = szToFilename;
277 }
278 
SetToScenarioFilename(const char * szScenFilename)279 void C4PlayerInfo::SetToScenarioFilename(const char *szScenFilename)
280 {
281 	// kill res
282 	DiscardResource();
283 	// set new filename
284 	SetFilename(szScenFilename);
285 	// flag scenario filename
286 	dwFlags |= PIF_InScenarioFile;
287 }
288 
LoadResource()289 void C4PlayerInfo::LoadResource()
290 {
291 	// only if any resource present and not yet assigned
292 	if (IsRemoved() || !(dwFlags & PIF_HasRes) || pRes) return;
293 	// Ignore res if a local file is to be used
294 	// the PIF_InScenarioFile is not set for startup players in initial replays,
295 	// because resources are used for player joins but emulated in playback control
296 	// if there will ever be resources in replay mode, this special case can be removed
297 	if (Game.C4S.Head.Replay || (dwFlags & PIF_InScenarioFile))
298 		dwFlags &= ~PIF_HasRes;
299 	else
300 		// create resource (will check if resource already exists)
301 		if (!(pRes = ::Network.ResList.AddByCore(ResCore)))
302 		{
303 			dwFlags &= ~PIF_HasRes;
304 			// add failed? invalid resource??! -- TODO: may be too large to load
305 			LogF("Error: Could not add resource %d for player %s! Player file too large to load?", (int) ResCore.getID(), (const char *) GetFilename());
306 		}
307 }
308 
DiscardResource()309 void C4PlayerInfo::DiscardResource()
310 {
311 	// del any file resource
312 	if (pRes)
313 	{
314 		assert(dwFlags & PIF_HasRes);
315 		pRes = nullptr;
316 		dwFlags &= ~PIF_HasRes;
317 	}
318 	else assert(~dwFlags & PIF_HasRes);
319 	ResCore.Clear();
320 }
321 
SetSavegameResume(C4PlayerInfo * pSavegameInfo)322 bool C4PlayerInfo::SetSavegameResume(C4PlayerInfo *pSavegameInfo)
323 {
324 	// copy some data fields; but not the file fields, because the join method is determined by this player
325 	if (!pSavegameInfo) return false;
326 	iID = pSavegameInfo->GetID();
327 	dwFlags = (dwFlags & ~PIF_SavegameTakeoverFlags) | (pSavegameInfo->GetFlags() & PIF_SavegameTakeoverFlags);
328 	dwColor = pSavegameInfo->GetColor(); // redundant; should be done by host already
329 	idTeam = pSavegameInfo->GetTeam();
330 	return true;
331 }
332 
SetJoined(int32_t iNumber)333 void C4PlayerInfo::SetJoined(int32_t iNumber)
334 {
335 	// mark as joined in current frame
336 	iInGameNumber = iNumber;
337 	iInGameJoinFrame = Game.FrameCounter;
338 	dwFlags |= PIF_Joined;
339 }
340 
SetRemoved()341 void C4PlayerInfo::SetRemoved()
342 {
343 	// mark as removed - always marks as previously joined, too
344 	dwFlags |= PIF_Joined | PIF_Removed;
345 	// remember removal frame
346 	iInGamePartFrame = Game.FrameCounter;
347 }
348 
LoadBigIcon(C4FacetSurface & fctTarget)349 bool C4PlayerInfo::LoadBigIcon(C4FacetSurface &fctTarget)
350 {
351 	bool fSuccess = false;
352 	// load BigIcon.png of player into target facet; return false if no bigicon present or player file not yet loaded
353 	C4Group Plr;
354 	C4Network2Res *pRes = nullptr;
355 	bool fIncompleteRes = false;
356 	if ((pRes = GetRes()))
357 		if (!pRes->isComplete())
358 			fIncompleteRes = true;
359 	size_t iBigIconSize=0;
360 	if (!fIncompleteRes)
361 		if (Plr.Open(pRes ? pRes->getFile() : GetFilename()))
362 			if (Plr.AccessEntry(C4CFN_BigIcon, &iBigIconSize))
363 				if (iBigIconSize<=C4NetResMaxBigicon*1024)
364 					if (fctTarget.Load(Plr, C4CFN_BigIcon, C4FCT_Full, C4FCT_Full, false, 0))
365 						fSuccess = true;
366 	return fSuccess;
367 }
368 
369 
370 // *** C4ClientPlayerInfos
371 
C4ClientPlayerInfos(const char * szJoinFilenames,bool fAdd,C4PlayerInfo * pAddInfo)372 C4ClientPlayerInfos::C4ClientPlayerInfos(const char *szJoinFilenames, bool fAdd, C4PlayerInfo *pAddInfo)
373 {
374 	// init for local client?
375 	if (szJoinFilenames || pAddInfo)
376 	{
377 		// set developer flag for developer hosts
378 		if (SSearch(Config.GetRegistrationData("Type"), "Developer"))
379 			dwFlags |= CIF_Developer;
380 		// set local ID
381 		iClientID = ::Control.ClientID();
382 		// maybe control is not preinitialized
383 		if (!::Control.isNetwork() && iClientID < 0) iClientID = 0;
384 		// join packet or initial packet?
385 		if (fAdd)
386 			// packet is to be added to other players
387 			dwFlags |= CIF_AddPlayers;
388 		else
389 			// set initial flag for first-time join packet
390 			dwFlags |= CIF_Initial;
391 		// join all players in list
392 		if ((iPlayerCapacity = (szJoinFilenames ? SModuleCount(szJoinFilenames) : 0) + !!pAddInfo))
393 		{
394 			ppPlayers = new C4PlayerInfo *[iPlayerCapacity];
395 			if (szJoinFilenames)
396 			{
397 				char szPlrFile[_MAX_PATH+1];
398 				for (int32_t i=0; i<iPlayerCapacity; ++i)
399 					if (SGetModule(szJoinFilenames, i, szPlrFile, _MAX_PATH))
400 					{
401 						C4PlayerInfo *pNewInfo = new C4PlayerInfo();
402 						if (pNewInfo->LoadFromLocalFile(szPlrFile))
403 						{
404 							// player def loaded; register and count it
405 							ppPlayers[iPlayerCount++] = pNewInfo;
406 						}
407 						else
408 						{
409 							// loading failure; clear info class
410 							delete pNewInfo;
411 							//
412 							Log(FormatString(LoadResStr("IDS_ERR_LOAD_PLAYER"), szPlrFile).getData());
413 						}
414 					}
415 			}
416 			if (pAddInfo)
417 				ppPlayers[iPlayerCount++] = pAddInfo;
418 		}
419 	}
420 }
421 
C4ClientPlayerInfos(const C4ClientPlayerInfos & rCopy)422 C4ClientPlayerInfos::C4ClientPlayerInfos(const C4ClientPlayerInfos &rCopy)
423 {
424 	// copy fields
425 	iClientID = rCopy.iClientID;
426 	if ((iPlayerCount = rCopy.iPlayerCount))
427 	{
428 		// copy player infos
429 		ppPlayers = new C4PlayerInfo *[iPlayerCapacity = rCopy.iPlayerCapacity];
430 		int32_t i = iPlayerCount;
431 		C4PlayerInfo **ppCurrPlrInfo = ppPlayers, **ppSrcPlrInfo = rCopy.ppPlayers;
432 		while (i--) *ppCurrPlrInfo++ = new C4PlayerInfo(**ppSrcPlrInfo++);
433 	}
434 	// no players
435 	else
436 	{
437 		ppPlayers = nullptr;
438 		iPlayerCapacity = 0;
439 	}
440 	// misc fields
441 	dwFlags = rCopy.dwFlags;
442 }
443 
operator =(const C4ClientPlayerInfos & rCopy)444 C4ClientPlayerInfos &C4ClientPlayerInfos::operator = (const C4ClientPlayerInfos &rCopy)
445 {
446 	Clear();
447 	// copy fields
448 	iClientID = rCopy.iClientID;
449 	if ((iPlayerCount = rCopy.iPlayerCount))
450 	{
451 		// copy player infos
452 		ppPlayers = new C4PlayerInfo *[iPlayerCapacity = rCopy.iPlayerCapacity];
453 		int32_t i = iPlayerCount;
454 		C4PlayerInfo **ppCurrPlrInfo = ppPlayers, **ppSrcPlrInfo = rCopy.ppPlayers;
455 		while (i--) *ppCurrPlrInfo++ = new C4PlayerInfo(**ppSrcPlrInfo++);
456 	}
457 	// no players
458 	else
459 	{
460 		ppPlayers = nullptr;
461 		iPlayerCapacity = 0;
462 	}
463 	// misc fields
464 	dwFlags = rCopy.dwFlags;
465 	return *this;
466 }
467 
468 
Clear()469 void C4ClientPlayerInfos::Clear()
470 {
471 	// del player infos
472 	int32_t i = iPlayerCount; C4PlayerInfo **ppCurrPlrInfo = ppPlayers;
473 	while (i--) delete *ppCurrPlrInfo++;
474 	// del player info vector
475 	delete [] ppPlayers; ppPlayers = nullptr;
476 	// reset other fields
477 	iPlayerCount = iPlayerCapacity = 0;
478 	iClientID=-1;
479 	dwFlags = 0;
480 }
481 
GrabMergeFrom(C4ClientPlayerInfos & rFrom)482 void C4ClientPlayerInfos::GrabMergeFrom(C4ClientPlayerInfos &rFrom)
483 {
484 	// anything to grab?
485 	if (!rFrom.iPlayerCount) return;
486 	// any previous players to copy?
487 	if (iPlayerCount)
488 	{
489 		// buffer sufficient?
490 		if (iPlayerCount + rFrom.iPlayerCount > iPlayerCapacity)
491 			GrowList(rFrom.iPlayerCount);
492 		// merge into new buffer
493 		memcpy(ppPlayers + iPlayerCount, rFrom.ppPlayers, rFrom.iPlayerCount * sizeof(C4PlayerInfo *));
494 		iPlayerCount += rFrom.iPlayerCount;
495 		rFrom.iPlayerCount = rFrom.iPlayerCapacity = 0;
496 		delete [] rFrom.ppPlayers; rFrom.ppPlayers = nullptr;
497 	}
498 	else
499 	{
500 		// no own players: take over buffer of pFrom
501 		if (ppPlayers) delete [] ppPlayers;
502 		ppPlayers = rFrom.ppPlayers; rFrom.ppPlayers = nullptr;
503 		iPlayerCount = rFrom.iPlayerCount; rFrom.iPlayerCount = 0;
504 		iPlayerCapacity = rFrom.iPlayerCapacity; rFrom.iPlayerCapacity = 0;
505 	}
506 }
507 
AddInfo(C4PlayerInfo * pAddInfo)508 void C4ClientPlayerInfos::AddInfo(C4PlayerInfo *pAddInfo)
509 {
510 	// grow list if necessary
511 	if (iPlayerCount == iPlayerCapacity) GrowList(4);
512 	// add info
513 	ppPlayers[iPlayerCount++] = pAddInfo;
514 }
515 
RemoveIndexedInfo(int32_t iAtIndex)516 void C4ClientPlayerInfos::RemoveIndexedInfo(int32_t iAtIndex)
517 {
518 	// bounds check
519 	if (iAtIndex<0 || iAtIndex>=iPlayerCount) return;
520 	// del player info at index
521 	delete ppPlayers[iAtIndex];
522 	// move down last index (may self-assign a ptr)
523 	ppPlayers[iAtIndex] = ppPlayers[--iPlayerCount];
524 }
525 
RemoveInfo(int32_t idPlr)526 void C4ClientPlayerInfos::RemoveInfo(int32_t idPlr)
527 {
528 	// check all infos; remove the one that matches
529 	int32_t i = 0; C4PlayerInfo **ppCurrPlrInfo = ppPlayers;
530 	while (i < iPlayerCount)
531 	{
532 		if ((*ppCurrPlrInfo)->GetID() == idPlr)
533 		{
534 			RemoveIndexedInfo(i);
535 			return;
536 		}
537 		++ppCurrPlrInfo; ++i;
538 	}
539 	// none matched
540 	return;
541 }
542 
GrowList(size_t iByVal)543 void C4ClientPlayerInfos::GrowList(size_t iByVal)
544 {
545 	// create new list (out of mem: simply returns here; info list remains in a valid state)
546 	C4PlayerInfo **ppNewInfo = new C4PlayerInfo *[iPlayerCapacity += iByVal];
547 	// move existing
548 	if (ppPlayers)
549 	{
550 		memcpy(ppNewInfo, ppPlayers, iPlayerCount * sizeof(C4PlayerInfo *));
551 		delete [] ppPlayers;
552 	}
553 	// assign new
554 	ppPlayers = ppNewInfo;
555 }
556 
GetFlaggedPlayerCount(DWORD dwFlag) const557 int32_t C4ClientPlayerInfos::GetFlaggedPlayerCount(DWORD dwFlag) const
558 {
559 	// check all players
560 	int32_t iCount = 0;
561 	int32_t i = iPlayerCount; C4PlayerInfo **ppCurrPlrInfo = ppPlayers;
562 	while (i--) if ((*ppCurrPlrInfo++)->GetFlags() | dwFlag)
563 			++iCount;
564 	// return number of matching infos
565 	return iCount;
566 }
567 
GetPlayerInfo(int32_t iIndex) const568 C4PlayerInfo *C4ClientPlayerInfos::GetPlayerInfo(int32_t iIndex) const
569 {
570 	// check range
571 	if (iIndex<0 || iIndex>=iPlayerCount) return nullptr;
572 	// return indexed info
573 	return ppPlayers[iIndex];
574 }
575 
GetPlayerInfo(int32_t iIndex,C4PlayerType eType) const576 C4PlayerInfo *C4ClientPlayerInfos::GetPlayerInfo(int32_t iIndex, C4PlayerType eType) const
577 {
578 	// get indexed matching info
579 	for (int32_t iCheck=0; iCheck<iPlayerCount; ++iCheck)
580 	{
581 		C4PlayerInfo *pNfo = ppPlayers[iCheck];
582 		if (pNfo->GetType() == eType)
583 			if (!iIndex--)
584 				return pNfo;
585 	}
586 	// nothing found
587 	return nullptr;
588 }
589 
GetPlayerInfoByID(int32_t id) const590 C4PlayerInfo *C4ClientPlayerInfos::GetPlayerInfoByID(int32_t id) const
591 {
592 	// check all infos
593 	int32_t i = iPlayerCount; C4PlayerInfo **ppCurrPlrInfo = ppPlayers;
594 	while (i--)
595 	{
596 		if ((*ppCurrPlrInfo)->GetID() == id) return *ppCurrPlrInfo;
597 		++ppCurrPlrInfo;
598 	}
599 	// none matched
600 	return nullptr;
601 }
602 
GetPlayerInfoByRes(int32_t idResID) const603 C4PlayerInfo *C4ClientPlayerInfos::GetPlayerInfoByRes(int32_t idResID) const
604 {
605 	int32_t i = iPlayerCount; C4PlayerInfo **ppCurrPlrInfo = ppPlayers;
606 	C4Network2Res *pRes;
607 	while (i--)
608 	{
609 		if ((pRes = (*ppCurrPlrInfo)->GetRes()))
610 			if (pRes->getResID() == idResID)
611 				// only if the player is actually using the resource
612 				if ((*ppCurrPlrInfo)->IsUsingPlayerFile())
613 					return *ppCurrPlrInfo;
614 		++ppCurrPlrInfo;
615 	}
616 	return nullptr;
617 }
618 
HasUnjoinedPlayers() const619 bool C4ClientPlayerInfos::HasUnjoinedPlayers() const
620 {
621 	// check all players
622 	int32_t i = iPlayerCount; C4PlayerInfo **ppCurrPlrInfo = ppPlayers;
623 	while (i--) if (!(*ppCurrPlrInfo++)->HasJoined()) return true;
624 	// all joined
625 	return false;
626 }
627 
GetJoinedPlayerCount() const628 int32_t C4ClientPlayerInfos::GetJoinedPlayerCount() const
629 {
630 	// count all players with IsJoined()
631 	int32_t i = iPlayerCount; int32_t cnt=0; C4PlayerInfo **ppCurrPlrInfo = ppPlayers;
632 	while (i--) if ((*ppCurrPlrInfo++)->IsJoined()) ++cnt;
633 	return cnt;
634 }
635 
CompileFunc(StdCompiler * pComp)636 void C4ClientPlayerInfos::CompileFunc(StdCompiler *pComp)
637 {
638 	bool deserializing = pComp->isDeserializer();
639 	if (deserializing) Clear();
640 	pComp->Value(mkNamingAdapt(iClientID, "ID", C4ClientIDUnknown));
641 
642 	// Flags
643 	StdBitfieldEntry<uint32_t> Entries[] =
644 	{
645 		{ "AddPlayers", CIF_AddPlayers },
646 		{ "Updated", CIF_Updated },
647 		{ "Initial", CIF_Initial },
648 		{ "Developer", CIF_Developer },
649 
650 		{ nullptr, 0 }
651 	};
652 	pComp->Value(mkNamingAdapt(mkBitfieldAdapt(dwFlags, Entries), "Flags", 0u));
653 
654 	pComp->Value(mkNamingCountAdapt<int32_t>(iPlayerCount, "Player"));
655 	if (iPlayerCount < 0 || iPlayerCount > C4MaxPlayer)
656 		{ pComp->excCorrupt("player count out of range"); return; }
657 	// Grow list, if necessary
658 	if (deserializing && iPlayerCount > iPlayerCapacity)
659 	{
660 		GrowList(iPlayerCount - iPlayerCapacity);
661 		ZeroMem(ppPlayers, sizeof(*ppPlayers) * iPlayerCount);
662 	}
663 	// Compile
664 	pComp->Value(mkNamingAdapt(mkArrayAdaptMap(ppPlayers, iPlayerCount, mkPtrAdaptNoNull<C4PlayerInfo>), "Player"));
665 	// Force specialization
666 	mkPtrAdaptNoNull<C4PlayerInfo>(*ppPlayers);
667 }
668 
LoadResources()669 void C4ClientPlayerInfos::LoadResources()
670 {
671 	// load for all players
672 	int32_t i = iPlayerCount; C4PlayerInfo **ppCurrPlrInfo = ppPlayers;
673 	while (i--) (*ppCurrPlrInfo++)->LoadResource();
674 }
675 
676 // *** C4PlayerInfoList
677 
678 C4PlayerInfoList::C4PlayerInfoList() = default;
679 	// ctor: no need to alloc mem yet
680 
C4PlayerInfoList(const C4PlayerInfoList & rCpy)681 C4PlayerInfoList::C4PlayerInfoList(const C4PlayerInfoList &rCpy) : iClientCount(rCpy.iClientCount), iClientCapacity(rCpy.iClientCapacity),
682 		ppClients(nullptr), iLastPlayerID(rCpy.iLastPlayerID)
683 {
684 	// copy client info vector
685 	if (rCpy.ppClients)
686 	{
687 		ppClients = new C4ClientPlayerInfos*[iClientCapacity];
688 		C4ClientPlayerInfos **ppInfo = ppClients, **ppCpy = rCpy.ppClients;
689 		int32_t i = iClientCount;
690 		while (i--) *ppInfo++ = new C4ClientPlayerInfos(**ppCpy++);
691 	}
692 }
693 
operator =(const C4PlayerInfoList & rCpy)694 C4PlayerInfoList &C4PlayerInfoList::operator = (const C4PlayerInfoList &rCpy)
695 {
696 	Clear();
697 	iClientCount = rCpy.iClientCount;
698 	iClientCapacity = rCpy.iClientCapacity;
699 	iLastPlayerID = rCpy.iLastPlayerID;
700 	if (rCpy.ppClients)
701 	{
702 		ppClients = new C4ClientPlayerInfos*[iClientCapacity];
703 		C4ClientPlayerInfos **ppInfo = ppClients, **ppCpy = rCpy.ppClients;
704 		int32_t i = iClientCount;
705 		while (i--) *ppInfo++ = new C4ClientPlayerInfos(**ppCpy++);
706 	}
707 	else
708 		ppClients = nullptr;
709 	return *this;
710 }
711 
Clear()712 void C4PlayerInfoList::Clear()
713 {
714 	// delete client infos
715 	C4ClientPlayerInfos **ppInfo = ppClients; int32_t i=iClientCount;
716 	while (i--) delete *ppInfo++;
717 	// clear client infos
718 	delete [] ppClients; ppClients = nullptr;
719 	iClientCount = iClientCapacity = 0;
720 	// reset player ID counter
721 	iLastPlayerID = 0;
722 }
723 
GrowList(size_t iByVal)724 void C4PlayerInfoList::GrowList(size_t iByVal)
725 {
726 	// create new list (out of mem: simply returns here; info list remains in a valid state)
727 	C4ClientPlayerInfos **ppNewInfo = new C4ClientPlayerInfos *[iClientCapacity += iByVal];
728 	// move existing
729 	if (ppClients)
730 	{
731 		memcpy(ppNewInfo, ppClients, iClientCount * sizeof(C4ClientPlayerInfos *));
732 		delete [] ppClients;
733 	}
734 	// assign new
735 	ppClients = ppNewInfo;
736 }
737 
DoLocalNonNetworkPlayerJoin(const char * szPlayerFile)738 bool C4PlayerInfoList::DoLocalNonNetworkPlayerJoin(const char *szPlayerFile)
739 {
740 	// construct joining information
741 	C4ClientPlayerInfos *pNewJoin = new C4ClientPlayerInfos(szPlayerFile, true);
742 	// handle it
743 	bool fSuccess = DoLocalNonNetworkPlayerInfoUpdate(pNewJoin);
744 	// done
745 	delete pNewJoin;
746 	return fSuccess;
747 }
748 
DoPlayerInfoUpdate(C4ClientPlayerInfos * pUpdate)749 bool C4PlayerInfoList::DoPlayerInfoUpdate(C4ClientPlayerInfos *pUpdate)
750 {
751 	// never done by clients or in replay - update will be handled via queue
752 	if (!::Control.isCtrlHost()) return false;
753 	// in network game, process by host. In offline game, just create control
754 	bool fSucc = true;
755 	if (::Control.isNetwork())
756 		::Network.Players.RequestPlayerInfoUpdate(*pUpdate);
757 	else
758 		fSucc = DoLocalNonNetworkPlayerInfoUpdate(pUpdate);
759 	return fSucc;
760 }
761 
DoLocalNonNetworkPlayerInfoUpdate(C4ClientPlayerInfos * pUpdate)762 bool C4PlayerInfoList::DoLocalNonNetworkPlayerInfoUpdate(C4ClientPlayerInfos *pUpdate)
763 {
764 	// assign IDs first: Must be done early, so AssignTeams works
765 	if (!AssignPlayerIDs(pUpdate))
766 	{
767 		return false;
768 	}
769 	// set standard teams
770 	AssignTeams(pUpdate, true);
771 	// color/name change by team or savegame assignment
772 	UpdatePlayerAttributes(pUpdate, true);
773 	// add through queue: This will add directly, do the record and put player joins into the queue
774 	// in running mode, this call will also put the actual player joins into the queue
775 	::Control.DoInput(CID_PlrInfo, new C4ControlPlayerInfo(*pUpdate), Game.IsRunning ? CDT_Queue : CDT_Direct);
776 	// done, success
777 	return true;
778 }
779 
UpdatePlayerAttributes(C4ClientPlayerInfos * pForInfo,bool fResolveConflicts)780 void C4PlayerInfoList::UpdatePlayerAttributes(C4ClientPlayerInfos *pForInfo, bool fResolveConflicts)
781 {
782 	assert(pForInfo);
783 	// update colors of all players of this packet
784 	C4PlayerInfo *pInfo, *pInfo2; int32_t i=0;
785 	while ((pInfo = pForInfo->GetPlayerInfo(i++)))
786 		if (!pInfo->HasJoined())
787 		{
788 			// assign savegame colors
789 			int32_t idSavegameID; bool fHasForcedColor = false; DWORD dwForceClr;
790 			if ((idSavegameID = pInfo->GetAssociatedSavegamePlayerID()))
791 				if ((pInfo2 = Game.RestorePlayerInfos.GetPlayerInfoByID(idSavegameID)))
792 				{
793 					dwForceClr = pInfo2->GetColor();
794 					fHasForcedColor = true;
795 				}
796 			// assign team colors
797 			if (!fHasForcedColor && Game.Teams.IsTeamColors())
798 			{
799 				C4Team *pPlrTeam = Game.Teams.GetTeamByID(pInfo->GetTeam());
800 				if (pPlrTeam)
801 				{
802 					dwForceClr = pPlrTeam->GetColor();
803 					fHasForcedColor = true;
804 				}
805 			}
806 			// do color change
807 			if (fHasForcedColor && (dwForceClr != pInfo->GetColor()))
808 			{
809 				pInfo->SetColor(dwForceClr);
810 				pForInfo->SetUpdated();
811 			}
812 			// make sure colors have correct alpha (modified engines might send malformed packages of transparent colors)
813 			if ((pInfo->GetColor() & 0xff000000u) != 0xff000000u)
814 			{
815 				pInfo->SetColor(pInfo->GetColor() | 0xff000000u);
816 				pForInfo->SetUpdated();
817 			}
818 		}
819 	if (fResolveConflicts) ResolvePlayerAttributeConflicts(pForInfo);
820 }
821 
UpdatePlayerAttributes()822 void C4PlayerInfoList::UpdatePlayerAttributes()
823 {
824 	// update attributes of all packets
825 	int32_t iIdx=0;
826 	C4ClientPlayerInfos *pForInfo;
827 	while ((pForInfo = GetIndexedInfo(iIdx++))) UpdatePlayerAttributes(pForInfo, false);
828 	// now resole all conflicts
829 	ResolvePlayerAttributeConflicts(nullptr);
830 }
831 
AssignPlayerIDs(C4ClientPlayerInfos * pNewClientInfo)832 bool C4PlayerInfoList::AssignPlayerIDs(C4ClientPlayerInfos *pNewClientInfo)
833 {
834 	// assign player IDs to those player infos without
835 	C4PlayerInfo *pPlrInfo; int32_t i=0;
836 	while ((pPlrInfo = pNewClientInfo->GetPlayerInfo(i++)))
837 		if (!pPlrInfo->GetID())
838 		{
839 			// are there still any player slots free?
840 			if (GetFreePlayerSlotCount()<1)
841 			{
842 				// nope - then deny this join!
843 				Log(FormatString(LoadResStr("IDS_MSG_TOOMANYPLAYERS"), (int)Game.Parameters.MaxPlayers).getData());
844 				pNewClientInfo->RemoveIndexedInfo(--i);
845 				continue;
846 			}
847 			// Join OK; grant an ID
848 			pPlrInfo->SetID(++iLastPlayerID);
849 		}
850 	// return whether any join remains
851 	return !!pNewClientInfo->GetPlayerCount();
852 }
853 
GetFreePlayerSlotCount()854 int32_t C4PlayerInfoList::GetFreePlayerSlotCount()
855 {
856 	// number of free slots depends on max player setting
857 	return std::max<int32_t>(Game.Parameters.MaxPlayers - GetStartupCount(), 0);
858 }
859 
AssignTeams(C4ClientPlayerInfos * pNewClientInfo,bool fByHost)860 void C4PlayerInfoList::AssignTeams(C4ClientPlayerInfos *pNewClientInfo, bool fByHost)
861 {
862 	if (!Game.Teams.IsMultiTeams()) return;
863 	// assign any unset teams (host/standalone only - fByHost determines whether the packet came from the host)
864 	C4PlayerInfo *pPlrInfo; int32_t i=0;
865 	while ((pPlrInfo = pNewClientInfo->GetPlayerInfo(i++)))
866 		Game.Teams.RecheckPlayerInfoTeams(*pPlrInfo, fByHost);
867 }
868 
RecheckAutoGeneratedTeams()869 void C4PlayerInfoList::RecheckAutoGeneratedTeams()
870 {
871 	// ensure all teams specified in the list exist
872 	C4ClientPlayerInfos *pPlrInfos; int32_t j=0;
873 	while ((pPlrInfos = GetIndexedInfo(j++)))
874 	{
875 		C4PlayerInfo *pPlrInfo; int32_t i=0;
876 		while ((pPlrInfo = pPlrInfos->GetPlayerInfo(i++)))
877 		{
878 			int32_t idTeam = pPlrInfo->GetTeam();
879 			if (idTeam) Game.Teams.GetGenerateTeamByID(idTeam);
880 		}
881 	}
882 }
883 
AddInfo(C4ClientPlayerInfos * pNewClientInfo)884 C4ClientPlayerInfos *C4PlayerInfoList::AddInfo(C4ClientPlayerInfos *pNewClientInfo)
885 {
886 	assert(pNewClientInfo);
887 	// caution: also called for RestorePlayerInfos-list
888 	// host: reserve new IDs for all players
889 	// client: all IDs should be assigned already by host
890 	if (::Network.isHost() || !::Network.isEnabled())
891 	{
892 		if (!AssignPlayerIDs(pNewClientInfo) && pNewClientInfo->IsAddPacket())
893 		{
894 			// no players could be added (probably MaxPlayer)
895 			delete pNewClientInfo;
896 			return nullptr;
897 		}
898 	}
899 	// ensure all teams specified in the list exist (this should be done for savegame teams as well)
900 	C4PlayerInfo *pInfo; int32_t i=0;
901 	while ((pInfo = pNewClientInfo->GetPlayerInfo(i++)))
902 	{
903 		int32_t idTeam = pInfo->GetTeam();
904 		if (idTeam) Game.Teams.GetGenerateTeamByID(idTeam);
905 	}
906 	// add info for client; overwriting or appending to existing info if necessary
907 	// try to find existing data of same client
908 	C4ClientPlayerInfos **ppExistingInfo = GetInfoPtrByClientID(pNewClientInfo->GetClientID());
909 	if (ppExistingInfo)
910 	{
911 		// info exists: append to it?
912 		if (pNewClientInfo->IsAddPacket())
913 		{
914 			(*ppExistingInfo)->GrabMergeFrom(*pNewClientInfo);
915 			// info added: remove unused class
916 			delete pNewClientInfo;
917 			// assign existing info for further usage in this fn
918 			return pNewClientInfo = *ppExistingInfo;
919 		}
920 		// no add packet: overwrite current info
921 		delete *ppExistingInfo;
922 		return *ppExistingInfo = pNewClientInfo;
923 	}
924 	// no existing info: add it directly
925 	pNewClientInfo->ResetAdd();
926 	// may need to grow list (vector) for that
927 	if (iClientCount >= iClientCapacity) GrowList(4);
928 	ppClients[iClientCount++] = pNewClientInfo;
929 	// done; return actual info
930 	return pNewClientInfo;
931 }
932 
GetInfoPtrByClientID(int32_t iClientID) const933 C4ClientPlayerInfos **C4PlayerInfoList::GetInfoPtrByClientID(int32_t iClientID) const
934 {
935 	// search list
936 	for (int32_t i=0; i<iClientCount; ++i) if (ppClients[i]->GetClientID() == iClientID) return ppClients+i;
937 	// nothing found
938 	return nullptr;
939 }
940 
GetPlayerCount() const941 int32_t C4PlayerInfoList::GetPlayerCount() const
942 {
943 	// count players of all clients
944 	int32_t iCount=0;
945 	for (int32_t i=0; i<iClientCount; ++i)
946 		iCount += ppClients[i]->GetPlayerCount();
947 	// return it
948 	return iCount;
949 }
950 
GetJoinIssuedPlayerCount() const951 int32_t C4PlayerInfoList::GetJoinIssuedPlayerCount() const
952 {
953 	// count players of all clients
954 	int32_t iCount=0;
955 	for (int32_t i=0; i<iClientCount; ++i)
956 	{
957 		C4ClientPlayerInfos *pClient = ppClients[i];
958 		for (int32_t j=0; j<pClient->GetPlayerCount(); ++j)
959 			if (pClient->GetPlayerInfo(j)->HasJoinIssued())
960 				++iCount;
961 	}
962 	// return it
963 	return iCount;
964 }
965 
GetJoinPendingPlayerCount() const966 int32_t C4PlayerInfoList::GetJoinPendingPlayerCount() const
967 {
968 	// count players of all clients
969 	int32_t iCount = 0;
970 	for (int32_t i = 0; i<iClientCount; ++i)
971 	{
972 		C4ClientPlayerInfos *pClient = ppClients[i];
973 		for (int32_t j = 0; j<pClient->GetPlayerCount(); ++j)
974 			if (pClient->GetPlayerInfo(j)->HasJoinPending())
975 				++iCount;
976 	}
977 	// return it
978 	return iCount;
979 }
980 
GetActivePlayerCount(bool fCountInvisible) const981 int32_t C4PlayerInfoList::GetActivePlayerCount(bool fCountInvisible) const
982 {
983 	// count players of all clients
984 	int32_t iCount=0;
985 	for (int32_t i=0; i<iClientCount; ++i)
986 	{
987 		C4ClientPlayerInfos *pClient = ppClients[i];
988 		for (int32_t j=0; j<pClient->GetPlayerCount(); ++j)
989 		{
990 			C4PlayerInfo *pInfo = pClient->GetPlayerInfo(j);
991 			if (!pInfo->IsRemoved())
992 				if (fCountInvisible || !pInfo->IsInvisible())
993 					++iCount;
994 		}
995 	}
996 	// return it
997 	return iCount;
998 }
999 
GetActiveScriptPlayerCount(bool fCountSavegameResumes,bool fCountInvisible) const1000 int32_t C4PlayerInfoList::GetActiveScriptPlayerCount(bool fCountSavegameResumes, bool fCountInvisible) const
1001 {
1002 	// count players of all clients
1003 	int32_t iCount=0;
1004 	for (int32_t i=0; i<iClientCount; ++i)
1005 	{
1006 		C4ClientPlayerInfos *pClient = ppClients[i];
1007 		for (int32_t j=0; j<pClient->GetPlayerCount(); ++j)
1008 		{
1009 			C4PlayerInfo *pNfo = pClient->GetPlayerInfo(j);
1010 			if (!pNfo->IsRemoved())
1011 				if (pNfo->GetType() == C4PT_Script)
1012 					if (fCountSavegameResumes || !pNfo->GetAssociatedSavegamePlayerID())
1013 						if (fCountInvisible || !pNfo->IsInvisible())
1014 							++iCount;
1015 		}
1016 	}
1017 	// return it
1018 	return iCount;
1019 }
1020 
GetActivePlayerNames(bool fCountInvisible,int32_t iAtClientID) const1021 StdStrBuf C4PlayerInfoList::GetActivePlayerNames(bool fCountInvisible, int32_t iAtClientID) const
1022 {
1023 	// add up players of all clients
1024 	StdStrBuf sPlr;
1025 	int32_t iCount=0;
1026 	for (int32_t i=0; i<iClientCount; ++i)
1027 	{
1028 		C4ClientPlayerInfos *pClient = ppClients[i];
1029 		if (iAtClientID != -1 && pClient->GetClientID() != iAtClientID) continue;
1030 		for (int32_t j=0; j<pClient->GetPlayerCount(); ++j)
1031 		{
1032 			C4PlayerInfo *pInfo = pClient->GetPlayerInfo(j);
1033 			if (!pInfo->IsRemoved()) if (fCountInvisible || !pInfo->IsInvisible())
1034 				{
1035 					if (iCount++)
1036 					{
1037 						// not first name: Add separator
1038 						sPlr.Append(", ");
1039 					}
1040 					sPlr.Append(pInfo->GetName());
1041 				}
1042 		}
1043 	}
1044 	// return it
1045 	return sPlr;
1046 }
1047 
GetPlayerInfoByIndex(int32_t index) const1048 C4PlayerInfo *C4PlayerInfoList::GetPlayerInfoByIndex(int32_t index) const
1049 {
1050 	// check all packets for a player
1051 	for (int32_t i=0; i<iClientCount; ++i)
1052 	{
1053 		int32_t j=0; C4PlayerInfo *pInfo;
1054 		while ((pInfo = ppClients[i]->GetPlayerInfo(j++)))
1055 			if (index-- <= 0)
1056 				return pInfo;
1057 	}
1058 	// nothing found
1059 	return nullptr;
1060 }
1061 
GetPlayerInfoByID(int32_t id) const1062 C4PlayerInfo *C4PlayerInfoList::GetPlayerInfoByID(int32_t id) const
1063 {
1064 	// must be a valid ID
1065 	assert(id);
1066 	// check all packets for a player
1067 	for (int32_t i=0; i<iClientCount; ++i)
1068 	{
1069 		int32_t j=0; C4PlayerInfo *pInfo;
1070 		while ((pInfo = ppClients[i]->GetPlayerInfo(j++)))
1071 			if (pInfo->GetID() == id) return pInfo;
1072 	}
1073 	// nothing found
1074 	return nullptr;
1075 }
1076 
GetClientInfoByPlayerID(int32_t id) const1077 C4ClientPlayerInfos *C4PlayerInfoList::GetClientInfoByPlayerID(int32_t id) const
1078 {
1079 	// get client info that contains a specific player
1080 	assert(id);
1081 	for (int32_t i=0; i<iClientCount; ++i)
1082 	{
1083 		int32_t j=0; C4PlayerInfo *pInfo;
1084 		while ((pInfo = ppClients[i]->GetPlayerInfo(j++)))
1085 			if (pInfo->GetID() == id) return ppClients[i];
1086 	}
1087 	// nothing found
1088 	return nullptr;
1089 }
1090 
GetPlayerInfoByID(int32_t id,int32_t * pidClient) const1091 C4PlayerInfo *C4PlayerInfoList::GetPlayerInfoByID(int32_t id, int32_t *pidClient) const
1092 {
1093 	// must be a valid ID
1094 	assert(id); assert(pidClient);
1095 	// check all packets for a player
1096 	for (int32_t i=0; i<iClientCount; ++i)
1097 	{
1098 		int32_t j=0; C4PlayerInfo *pInfo;
1099 		while ((pInfo = ppClients[i]->GetPlayerInfo(j++)))
1100 			if (pInfo->GetID() == id)
1101 			{
1102 				*pidClient = ppClients[i]->GetClientID();
1103 				return pInfo;
1104 			}
1105 	}
1106 	// nothing found
1107 	return nullptr;
1108 }
1109 
GetPlayerInfoBySavegameID(int32_t id) const1110 C4PlayerInfo *C4PlayerInfoList::GetPlayerInfoBySavegameID(int32_t id) const
1111 {
1112 	// must be a valid ID
1113 	assert(id);
1114 	// check all packets for a player
1115 	for (int32_t i=0; i<iClientCount; ++i)
1116 	{
1117 		int32_t j=0; C4PlayerInfo *pInfo;
1118 		while ((pInfo = ppClients[i]->GetPlayerInfo(j++)))
1119 			if (pInfo->GetAssociatedSavegamePlayerID() == id) return pInfo;
1120 	}
1121 	// nothing found
1122 	return nullptr;
1123 }
1124 
GetNextPlayerInfoByID(int32_t id) const1125 C4PlayerInfo *C4PlayerInfoList::GetNextPlayerInfoByID(int32_t id) const
1126 {
1127 	// check all packets for players
1128 	C4PlayerInfo *pSmallest=nullptr;
1129 	for (int32_t i=0; i<iClientCount; ++i)
1130 	{
1131 		int32_t j=0; C4PlayerInfo *pInfo;
1132 		while ((pInfo = ppClients[i]->GetPlayerInfo(j++)))
1133 			if (pInfo->GetID() > id)
1134 				if (!pSmallest || pSmallest->GetID()>pInfo->GetID())
1135 					pSmallest = pInfo;
1136 	}
1137 	// return best found
1138 	return pSmallest;
1139 }
1140 
GetActivePlayerInfoByName(const char * szName)1141 C4PlayerInfo *C4PlayerInfoList::GetActivePlayerInfoByName(const char *szName)
1142 {
1143 	// check all packets for matching players
1144 	for (int32_t i=0; i<iClientCount; ++i)
1145 	{
1146 		int32_t j=0; C4PlayerInfo *pInfo;
1147 		while ((pInfo = ppClients[i]->GetPlayerInfo(j++)))
1148 			if (!pInfo->IsRemoved())
1149 				if (SEqualNoCase(szName, pInfo->GetName()))
1150 					return pInfo;
1151 	}
1152 	// nothing found
1153 	return nullptr;
1154 }
1155 
FindSavegameResumePlayerInfo(const C4PlayerInfo * pMatchInfo,MatchingLevel mlMatchStart,MatchingLevel mlMatchEnd) const1156 C4PlayerInfo *C4PlayerInfoList::FindSavegameResumePlayerInfo(const C4PlayerInfo *pMatchInfo, MatchingLevel mlMatchStart, MatchingLevel mlMatchEnd) const
1157 {
1158 	assert(pMatchInfo);
1159 	// try different matching levels using the infamous for-case-paradigm
1160 	for (int iMatchLvl = mlMatchStart; iMatchLvl <= mlMatchEnd; ++iMatchLvl)
1161 	{
1162 		for (int32_t i=0; i<iClientCount; ++i)
1163 		{
1164 			int32_t j=0; C4PlayerInfo *pInfo;
1165 			while ((pInfo = ppClients[i]->GetPlayerInfo(j++)))
1166 				if (!Game.PlayerInfos.GetPlayerInfoByID(pInfo->GetID()) && !Game.PlayerInfos.GetPlayerInfoBySavegameID(pInfo->GetID()))  // only unassigned player infos
1167 					switch (iMatchLvl)
1168 					{
1169 					case PML_PlrFileName: // file name and player name must match
1170 						if (!pMatchInfo->GetFilename() || !pInfo->GetFilename()) break;
1171 						if (!SEqualNoCase(GetFilename(pMatchInfo->GetFilename()), GetFilename(pInfo->GetFilename()))) break;
1172 						// nobreak: Check player name as well
1173 					case PML_PlrName: // match player name
1174 						if (SEqualNoCase(pMatchInfo->GetName(), pInfo->GetName()))
1175 							return pInfo;
1176 						break;
1177 					case PML_PrefColor: // match player color
1178 						if (pMatchInfo->GetOriginalColor() == pInfo->GetOriginalColor())
1179 							return pInfo;
1180 						break;
1181 					case PML_Any: // match anything
1182 						return pInfo;
1183 					}
1184 		}
1185 	}
1186 	// no match
1187 	return nullptr;
1188 }
1189 
FindUnassociatedRestoreInfo(const C4PlayerInfoList & rRestoreInfoList)1190 C4PlayerInfo *C4PlayerInfoList::FindUnassociatedRestoreInfo(const C4PlayerInfoList &rRestoreInfoList)
1191 {
1192 	// search given list for a player that's not associated locally
1193 	C4ClientPlayerInfos *pRestoreClient; int32_t iClient=0;
1194 	while ((pRestoreClient = rRestoreInfoList.GetIndexedInfo(iClient++)))
1195 	{
1196 		C4PlayerInfo *pRestoreInfo; int32_t iInfo=0;
1197 		while ((pRestoreInfo = pRestoreClient->GetPlayerInfo(iInfo++)))
1198 			if (pRestoreInfo->IsJoined())
1199 				// match association either by savegame ID (before C4Game::InitPlayers) or real ID (after C4Game::InitPlayers)
1200 				if (!GetPlayerInfoBySavegameID(pRestoreInfo->GetID()) && !GetPlayerInfoByID(pRestoreInfo->GetID()))
1201 					return pRestoreInfo;
1202 	}
1203 	// no unassociated info found
1204 	return nullptr;
1205 }
1206 
HasSameTeamPlayers(int32_t iClient1,int32_t iClient2) const1207 bool C4PlayerInfoList::HasSameTeamPlayers(int32_t iClient1, int32_t iClient2) const
1208 {
1209 	// compare all player teams of clients
1210 	const C4ClientPlayerInfos *pCnfo1 = GetInfoByClientID(iClient1);
1211 	const C4ClientPlayerInfos *pCnfo2 = GetInfoByClientID(iClient2);
1212 	if (!pCnfo1 || !pCnfo2) return false;
1213 	int32_t i=0,j; const C4PlayerInfo *pNfo1, *pNfo2;
1214 	while ((pNfo1 = pCnfo1->GetPlayerInfo(i++)))
1215 	{
1216 		if (!pNfo1->IsUsingTeam()) continue;
1217 		j=0;
1218 		while ((pNfo2 = pCnfo2->GetPlayerInfo(j++)))
1219 		{
1220 			if (!pNfo2->IsUsingTeam()) continue;
1221 			if (pNfo2->GetTeam() == pNfo1->GetTeam())
1222 				// match found!
1223 				return true;
1224 		}
1225 	}
1226 	// no match
1227 	return false;
1228 }
1229 
Load(C4Group & hGroup,const char * szFromFile,C4LangStringTable * pLang)1230 bool C4PlayerInfoList::Load(C4Group &hGroup, const char *szFromFile, C4LangStringTable *pLang)
1231 {
1232 	// clear previous
1233 	Clear();
1234 	// load file contents
1235 	StdStrBuf Buf;
1236 	if (!hGroup.LoadEntryString(szFromFile, &Buf))
1237 		// no file is OK; means no player infos
1238 		return true;
1239 	// replace strings
1240 	if (pLang) pLang->ReplaceStrings(Buf);
1241 	// (try to) compile
1242 	if (!CompileFromBuf_LogWarn<StdCompilerINIRead>(
1243 	      mkNamingAdapt(*this, "PlayerInfoList"),
1244 	      Buf, szFromFile))
1245 		return false;
1246 	// done, success
1247 	return true;
1248 }
1249 
Save(C4Group & hGroup,const char * szToFile)1250 bool C4PlayerInfoList::Save(C4Group &hGroup, const char *szToFile)
1251 {
1252 	// remove previous entry from group
1253 	hGroup.DeleteEntry(szToFile);
1254 	// anything to save?
1255 	if (!iClientCount) return true;
1256 	// save it
1257 	try
1258 	{
1259 		// decompile
1260 		StdStrBuf Buf = DecompileToBuf<StdCompilerINIWrite>(
1261 		                  mkNamingAdapt(*this, "PlayerInfoList"));
1262 		// save buffer to group
1263 		hGroup.Add(szToFile, Buf, false, true);
1264 	}
1265 	catch (StdCompiler::Exception *)
1266 		{ return false; }
1267 	// done, success
1268 	return true;
1269 }
1270 
InitLocal()1271 bool C4PlayerInfoList::InitLocal()
1272 {
1273 	// not in replay
1274 	if (Game.C4S.Head.Replay) return true;
1275 	// no double init
1276 	assert(!GetInfoCount());
1277 	// no network
1278 	assert(!::Network.isEnabled());
1279 	// create player info for local player joins
1280 	C4ClientPlayerInfos *pLocalInfo = new C4ClientPlayerInfos(Game.PlayerFilenames);
1281 	// register local info immediately
1282 	pLocalInfo = AddInfo(pLocalInfo);
1283 	// Script players in restore infos need to be associated with matching script players in main info list
1284 	CreateRestoreInfosForJoinedScriptPlayers(Game.RestorePlayerInfos);
1285 	// and assign teams
1286 	if (Game.Teams.IsMultiTeams() && pLocalInfo)
1287 		AssignTeams(pLocalInfo, true);
1288 	// done, success
1289 	return true;
1290 }
1291 
LocalJoinUnjoinedPlayersInQueue()1292 bool C4PlayerInfoList::LocalJoinUnjoinedPlayersInQueue()
1293 {
1294 	// local call only - in network, C4Network2Players joins players!
1295 	assert(!::Network.isEnabled());
1296 	// get local players
1297 	C4ClientPlayerInfos **ppkLocal = GetInfoPtrByClientID(::Control.ClientID()), *pkLocal;
1298 	if (!ppkLocal) return false;
1299 	pkLocal = *ppkLocal;
1300 	// check all players
1301 	int32_t i=0; C4PlayerInfo *pInfo;
1302 	while ((pInfo = pkLocal->GetPlayerInfo(i++)))
1303 		// not yet joined?
1304 		if (!pInfo->HasJoinIssued())
1305 		{
1306 			// join will be marked when queue is executed (C4Player::Join)
1307 			// but better mark join now already to prevent permanent sending overkill
1308 			pInfo->SetJoinIssued();
1309 			// join by filename if possible. Script players may not have a filename assigned though
1310 			const char *szFilename = pInfo->GetFilename();
1311 			if (!szFilename && (pInfo->GetType() != C4PT_Script))
1312 			{
1313 				// failure for user players
1314 				const char *szPlrName = pInfo->GetName(); if (!szPlrName) szPlrName="???";
1315 				LogF(LoadResStr("IDS_ERR_JOINQUEUEPLRS"), szPlrName);
1316 				continue;
1317 			}
1318 			Game.Input.Add(CID_JoinPlr,
1319 			               new C4ControlJoinPlayer(szFilename, ::Control.ClientID(), pInfo->GetID()));
1320 		}
1321 	// done, success
1322 	return true;
1323 }
1324 
CreateRestoreInfosForJoinedScriptPlayers(C4PlayerInfoList & rSavegamePlayers)1325 void C4PlayerInfoList::CreateRestoreInfosForJoinedScriptPlayers(C4PlayerInfoList &rSavegamePlayers)
1326 {
1327 	// create matching script player joins for all script playeers in restore info
1328 	// Just copy their infos to the first client
1329 	int32_t i;
1330 	C4ClientPlayerInfos *pHostInfo = GetIndexedInfo(0);
1331 	for (i=0; i<rSavegamePlayers.GetInfoCount(); ++i)
1332 	{
1333 		C4ClientPlayerInfos *pkInfo = rSavegamePlayers.GetIndexedInfo(i);
1334 		int32_t j=0; C4PlayerInfo *pInfo;
1335 		while ((pInfo = pkInfo->GetPlayerInfo(j++)))
1336 			if (pInfo->GetType() == C4PT_Script)
1337 			{
1338 				// safety
1339 				C4PlayerInfo *pRejoinInfo;
1340 				if ((pRejoinInfo = GetPlayerInfoBySavegameID(pInfo->GetID())))
1341 				{
1342 					LogF("Warning: User player %s takes over script player %s!", pRejoinInfo->GetName(), pInfo->GetName());
1343 					continue;
1344 				}
1345 				if (!pHostInfo)
1346 				{
1347 					LogF("Error restoring savegame script players: No host player infos to add to!");
1348 					continue;
1349 				}
1350 				// generate takeover info
1351 				pRejoinInfo = new C4PlayerInfo(*pInfo);
1352 				pRejoinInfo->SetAssociatedSavegamePlayer(pInfo->GetID());
1353 				pHostInfo->AddInfo(pRejoinInfo);
1354 			}
1355 	}
1356 	// teams must recognize the change
1357 	Game.Teams.RecheckPlayers();
1358 }
1359 
RestoreSavegameInfos(C4PlayerInfoList & rSavegamePlayers)1360 bool C4PlayerInfoList::RestoreSavegameInfos(C4PlayerInfoList &rSavegamePlayers)
1361 {
1362 	// any un-associated players?
1363 	if (rSavegamePlayers.GetPlayerCount())
1364 	{
1365 		// for runtime network joins, this should never happen!
1366 		assert(!Game.C4S.Head.NetworkRuntimeJoin);
1367 
1368 		// do savegame player association of real players
1369 		// for non-lobby games do automatic association first
1370 		int32_t iNumGrabbed = 0, i;
1371 		if (!::Network.isEnabled() && Game.C4S.Head.SaveGame)
1372 		{
1373 			// do several passes: First passes using regular player matching; following passes matching anything but with a warning message
1374 			for (int eMatchingLevel = 0; eMatchingLevel <= PML_Any; ++eMatchingLevel)
1375 				for (int32_t i=0; i<iClientCount; ++i)
1376 				{
1377 					C4ClientPlayerInfos *pkInfo = GetIndexedInfo(i);
1378 					int32_t j=0, id; C4PlayerInfo *pInfo, *pSavegameInfo;
1379 					while ((pInfo = pkInfo->GetPlayerInfo(j++)))
1380 						if (!(id = pInfo->GetAssociatedSavegamePlayerID()))
1381 							if ((pSavegameInfo = rSavegamePlayers.FindSavegameResumePlayerInfo(pInfo, (MatchingLevel)eMatchingLevel, (MatchingLevel)eMatchingLevel)))
1382 							{
1383 								pInfo->SetAssociatedSavegamePlayer(pSavegameInfo->GetID());
1384 								if (eMatchingLevel > PML_PlrName)
1385 								{
1386 									// this is a "wild" match: Warn the player (but not in replays)
1387 									StdStrBuf sMsg; sMsg.Format(LoadResStr("IDS_MSG_PLAYERASSIGNMENT"), pInfo->GetName(), pSavegameInfo->GetName());
1388 									Log(sMsg.getData());
1389 									if (::pGUI && FullScreen.Active && !Game.C4S.Head.Replay)
1390 										::pGUI->ShowMessageModal(sMsg.getData(), LoadResStr("IDS_MSG_FREESAVEGAMEPLRS"), C4GUI::MessageDialog::btnOK, C4GUI::Ico_Notify, &Config.Startup.HideMsgPlrTakeOver);
1391 								}
1392 							}
1393 				}
1394 		}
1395 		// association complete: evaluate it
1396 		for (i=0; i<iClientCount; ++i)
1397 		{
1398 			C4ClientPlayerInfos *pkInfo = GetIndexedInfo(i);
1399 			int32_t j=0, id; C4PlayerInfo *pInfo, *pSavegameInfo;
1400 			while ((pInfo = pkInfo->GetPlayerInfo(j++)))
1401 				if ((id = pInfo->GetAssociatedSavegamePlayerID()))
1402 				{
1403 					if ((pSavegameInfo = rSavegamePlayers.GetPlayerInfoByID(id)))
1404 					{
1405 						// pInfo continues for pSavegameInfo
1406 						pInfo->SetSavegameResume(pSavegameInfo);
1407 						++iNumGrabbed;
1408 					}
1409 					else
1410 					{
1411 						// shouldn't happen
1412 						assert(!"Invalid savegame association");
1413 					}
1414 				}
1415 				else
1416 				{
1417 					// no association for this info: Joins as new player
1418 					// in savegames, this is unusual. For regular script player restore, it's not
1419 					if (Game.C4S.Head.SaveGame) LogF(LoadResStr("IDS_PRC_RESUMENOPLRASSOCIATION"), (const char *)pInfo->GetName());
1420 				}
1421 		}
1422 		// otherwise any remaining players
1423 		int32_t iCountRemaining = rSavegamePlayers.GetPlayerCount() - iNumGrabbed;
1424 		if (iCountRemaining)
1425 		{
1426 			// in replay mode, if there are no regular player joins, it must have been a runtime record
1427 			// i.e., a record that was started during the game
1428 			// in this case, the savegame player infos equal the real player infos to be used
1429 			if (::Control.isReplay() && !GetInfoCount())
1430 			{
1431 				*this = rSavegamePlayers;
1432 			}
1433 			else
1434 			{
1435 				// in regular mode, these players must be removed
1436 				LogF(LoadResStr("IDS_PRC_RESUMEREMOVEPLRS"), iCountRemaining);
1437 				// remove them directly from the game
1438 				RemoveUnassociatedPlayers(rSavegamePlayers);
1439 			}
1440 		}
1441 	}
1442 	// now that players are restored, restore teams
1443 	Game.Teams.RecheckPlayers();
1444 	// done, success
1445 	return true;
1446 }
1447 
RecreatePlayerFiles()1448 bool C4PlayerInfoList::RecreatePlayerFiles()
1449 {
1450 	// Note that this method will be called on the main list for savegame resumes (even in network) or regular games with RecreateInfos,
1451 	//  and on RestorePlayerInfos for runtime network joins
1452 	// check all player files that need to be recreated
1453 	for (int32_t i=0; i<iClientCount; ++i)
1454 	{
1455 		C4ClientPlayerInfos *pkInfo = ppClients[i];
1456 		int32_t j=0; C4PlayerInfo *pInfo;
1457 		while ((pInfo = pkInfo->GetPlayerInfo(j++)))
1458 			if (pInfo->IsJoined())
1459 			{
1460 				// all players in replays and runtime joins; script players even in savegames need to be restored from the scenario goup
1461 				if (Game.C4S.Head.Replay || Game.C4S.Head.NetworkRuntimeJoin || pInfo->GetType() == C4PT_Script)
1462 				{
1463 					// in this case, a filename must have been assigned while saving
1464 					// and mark a file inside the scenario file
1465 					// get filename of joined player - this should always be valid!
1466 					const char *szCurrPlrFile;
1467 					StdStrBuf sFilenameInRecord;
1468 					if (Game.C4S.Head.Replay)
1469 					{
1470 						// replay of resumed savegame: RecreatePlayers saves used player files into the record group in this manner
1471 						sFilenameInRecord.Format("Recreate-%d.ocp", pInfo->GetID());
1472 						szCurrPlrFile = sFilenameInRecord.getData();
1473 					}
1474 					else
1475 						szCurrPlrFile = pInfo->GetFilename();
1476 					const char *szPlrName = pInfo->GetName(); if (!szPlrName) szPlrName = "???";
1477 					if (!szCurrPlrFile || !*szCurrPlrFile)
1478 					{
1479 						// that's okay for script players, because those may join w/o recreation files
1480 						if (pInfo->GetType() != C4PT_Script)
1481 						{
1482 							LogF(LoadResStr("IDS_ERR_LOAD_RECR_NOFILE"), szPlrName);
1483 						}
1484 						continue;
1485 					}
1486 					// join from temp file
1487 					StdCopyStrBuf szJoinPath;
1488 					szJoinPath = Config.AtTempPath(GetFilename(szCurrPlrFile));
1489 					// extract player there
1490 					if (!Game.ScenarioFile.FindEntry(GetFilename(szCurrPlrFile)) || !Game.ScenarioFile.Extract(GetFilename(szCurrPlrFile), szJoinPath.getData()))
1491 					{
1492 						// that's okay for script players, because those may join w/o recreation files
1493 						if (pInfo->GetType() != C4PT_Script)
1494 						{
1495 							LogF(LoadResStr("IDS_ERR_LOAD_RECR_NOEXTRACT"), szPlrName, GetFilename(szCurrPlrFile));
1496 						}
1497 						continue;
1498 					}
1499 					// set join source
1500 					pInfo->SetFilename(szJoinPath.getData());
1501 					pInfo->DiscardResource();
1502 					// setting a temp file here will cause the player file to be deleted directly after recreation
1503 					// if recreation fails (e.g. the game gets aborted due to invalid files), the info dtor will delete the file
1504 					pInfo->SetTempFile();
1505 				}
1506 				else
1507 				{
1508 					// regular player in savegame being resumed in network or normal mode:
1509 					// the filenames and/or resources should have been assigned
1510 					// a) either in lobby mode during player re-acquisition
1511 					// b) or when players from rSavegamePlayers were taken over
1512 				}
1513 			}
1514 			else if (!pInfo->HasJoinIssued())
1515 			{
1516 				// new players to be joined into the game:
1517 				// regular control queue join can be done; no special handling needed
1518 			}
1519 	}
1520 	// done, success
1521 	return true;
1522 }
1523 
RecreatePlayers(C4ValueNumbers * numbers)1524 bool C4PlayerInfoList::RecreatePlayers(C4ValueNumbers * numbers)
1525 {
1526 	// check all player infos
1527 	for (int32_t i=0; i<iClientCount; ++i)
1528 	{
1529 		C4ClientPlayerInfos *pkInfo = ppClients[i];
1530 		// skip clients without joined players
1531 		if (!pkInfo->GetJoinedPlayerCount()) continue;
1532 		// determine client ID and name
1533 		// client IDs must be set correctly even in replays,
1534 		// so client-removal packets are executed correctly
1535 		int32_t idAtClient = pkInfo->GetClientID();
1536 		const char *szAtClientName;
1537 		if (Game.C4S.Head.Replay)
1538 			// the client name can currently not really be retrieved in replays
1539 			// but it's not used anyway
1540 			szAtClientName = "Replay";
1541 		else
1542 			// local non-network non-replay games set local name
1543 			if (!::Network.isEnabled())
1544 			{
1545 				assert(idAtClient == ::Control.ClientID());
1546 				szAtClientName = "Local";
1547 			}
1548 			else
1549 			{
1550 				// network non-replay games: find client and set name by it
1551 				const C4Client *pClient = Game.Clients.getClientByID(idAtClient);
1552 				if (pClient)
1553 					szAtClientName = pClient->getName();
1554 				else
1555 				{
1556 					// this shouldn't happen - remove the player info
1557 					LogF(LoadResStr("IDS_PRC_RESUMENOCLIENT"), idAtClient, pkInfo->GetPlayerCount());
1558 					continue;
1559 				}
1560 			}
1561 		// rejoin all joined players of that client
1562 		int32_t j=0; C4PlayerInfo *pInfo;
1563 		while ((pInfo = pkInfo->GetPlayerInfo(j++)))
1564 			if (pInfo->IsJoined())
1565 			{
1566 				// get filename to join from
1567 				const char *szFilename = pInfo->GetLocalJoinFilename();
1568 				// ensure resource is loaded, if joining from resource
1569 				// this may display a waiting dialog and block the thread for a while
1570 				C4Network2Res *pJoinRes = pInfo->GetRes();
1571 				if (szFilename && pJoinRes && pJoinRes->isLoading())
1572 				{
1573 					const char *szName = pInfo->GetName();
1574 					if (!::Network.RetrieveRes(pJoinRes->getCore(), C4NetResRetrieveTimeout,
1575 					                           FormatString(LoadResStr("IDS_NET_RES_PLRFILE"), szName).getData()))
1576 						szFilename=nullptr;
1577 				}
1578 				// file present?
1579 				if (!szFilename || !*szFilename)
1580 				{
1581 					if (pInfo->GetType() == C4PT_User)
1582 					{
1583 						// for user players, this could happen only if the user cancelled the resource
1584 						const char *szPlrName = pInfo->GetName(); if (!szPlrName) szPlrName = "???";
1585 						LogF(LoadResStr("IDS_ERR_LOAD_RECR_NOFILEFROMNET"), szPlrName);
1586 						continue;
1587 					}
1588 					else
1589 					{
1590 						// for script players: Recreation without filename OK
1591 						szFilename = nullptr;
1592 					}
1593 				}
1594 				// record file handling: Save to the record file in the manner it's expected by C4PlayerInfoList::RecreatePlayers
1595 				if (::Control.isRecord() && szFilename)
1596 				{
1597 					StdStrBuf sFilenameInRecord;
1598 					sFilenameInRecord.Format("Recreate-%d.ocp", pInfo->GetID());
1599 					::Control.RecAddFile(szFilename, sFilenameInRecord.getData());
1600 				}
1601 				// recreate join directly
1602 				::Players.Join(szFilename, false, idAtClient, szAtClientName, pInfo, numbers);
1603 				// delete temporary files immediately
1604 				if (pInfo->IsTempFile()) pInfo->DeleteTempFile();
1605 			}
1606 	}
1607 	// done!
1608 	return true;
1609 }
1610 
RemoveUnassociatedPlayers(C4PlayerInfoList & rSavegamePlayers)1611 void C4PlayerInfoList::RemoveUnassociatedPlayers(C4PlayerInfoList &rSavegamePlayers)
1612 {
1613 	// check all joined infos
1614 	C4ClientPlayerInfos *pClient; int iClient=0;
1615 	while ((pClient = rSavegamePlayers.GetIndexedInfo(iClient++)))
1616 	{
1617 		C4PlayerInfo *pInfo; int iInfo = 0;
1618 		while ((pInfo = pClient->GetPlayerInfo(iInfo++)))
1619 		{
1620 			// remove players that were in the game but are not associated
1621 			if (pInfo->IsJoined() && !GetPlayerInfoBySavegameID(pInfo->GetID()))
1622 			{
1623 				if (::Players.RemoveUnjoined(pInfo->GetInGameNumber()))
1624 				{
1625 					LogF(LoadResStr("IDS_PRC_REMOVEPLR"), pInfo->GetName());
1626 				}
1627 			}
1628 			pInfo->SetRemoved();
1629 		}
1630 	}
1631 }
1632 
SetAsRestoreInfos(C4PlayerInfoList & rFromPlayers,bool fSaveUserPlrs,bool fSaveScriptPlrs,bool fSetUserPlrRefToLocalGroup,bool fSetScriptPlrRefToLocalGroup)1633 bool C4PlayerInfoList::SetAsRestoreInfos(C4PlayerInfoList &rFromPlayers, bool fSaveUserPlrs, bool fSaveScriptPlrs, bool fSetUserPlrRefToLocalGroup, bool fSetScriptPlrRefToLocalGroup)
1634 {
1635 	// copy everything
1636 	*this = rFromPlayers;
1637 	// then remove everything that's no longer joined and update the rest
1638 	C4ClientPlayerInfos *pClient; int iClient=0;
1639 	while ((pClient = GetIndexedInfo(iClient++)))
1640 	{
1641 		// update all players for this client
1642 		C4PlayerInfo *pInfo; int iInfo = 0;
1643 		while ((pInfo = pClient->GetPlayerInfo(iInfo++)))
1644 		{
1645 			bool fKeepInfo = false;
1646 			// remove players that are not in the game
1647 			if (pInfo->IsJoined())
1648 			{
1649 				// pre-reset filename
1650 				pInfo->SetFilename(nullptr);
1651 				if (pInfo->GetType() == C4PT_User)
1652 				{
1653 					fKeepInfo = fSaveUserPlrs;
1654 					if (fSetUserPlrRefToLocalGroup)
1655 					{
1656 						// in the game: Set filename for inside savegame file
1657 						StdStrBuf sNewName;
1658 						if (::Network.isEnabled())
1659 						{
1660 							C4Client *pGameClient = Game.Clients.getClientByID(pClient->GetClientID());
1661 							const char *szName = pGameClient ? pGameClient->getName() : "Unknown";
1662 							sNewName.Format("%s-%s", szName, (const char *) GetFilename(pInfo->GetLocalJoinFilename()));
1663 						}
1664 						else
1665 							sNewName.Copy(GetFilename(pInfo->GetFilename()));
1666 
1667 						// O(n) is fast.
1668 						// If not, blame whoever wrote Replace! ;)
1669 						sNewName.Replace("%", "%25", 0);
1670 						for (int ch = 128; ch < 256; ++ch)
1671 						{
1672 							const char* hexChars = "0123456789abcdef";
1673 							char old[] = { char(ch), 0 };
1674 							char safe[] = { '%', 'x', 'x', 0 };
1675 							safe[1] = hexChars[ch / 16];
1676 							safe[2] = hexChars[ch % 16];
1677 							sNewName.Replace(old, safe, 0);
1678 						}
1679 
1680 						pInfo->SetFilename(sNewName.getData());
1681 					}
1682 				}
1683 				else if (pInfo->GetType() == C4PT_Script)
1684 				{
1685 					// Save only if either all players should be saved (fSaveScriptPlrs && fSaveUserPlrs)
1686 					//  or if script players are saved and general scenario saving for this script player is desired
1687 					fKeepInfo = fSaveScriptPlrs && (fSaveUserPlrs || pInfo->IsScenarioSaveDesired());
1688 					if (fSetScriptPlrRefToLocalGroup)
1689 					{
1690 						// just compose a unique filename for script player
1691 						pInfo->SetFilename(FormatString("ScriptPlr-%d.ocp", (int)pInfo->GetID()).getData());
1692 					}
1693 				}
1694 			}
1695 			if (!fKeepInfo)
1696 			{
1697 				pClient->RemoveIndexedInfo(--iInfo);
1698 			}
1699 			else
1700 			{
1701 				pInfo->DiscardResource();
1702 			}
1703 		}
1704 		// remove empty clients
1705 		if (!pClient->GetPlayerCount())
1706 		{
1707 			RemoveInfo(GetInfoPtrByClientID(pClient->GetClientID()));
1708 			delete pClient;
1709 			--iClient;
1710 		}
1711 	}
1712 	// done
1713 	return true;
1714 }
1715 
ResetLeagueProjectedGain(bool fSetUpdated)1716 void C4PlayerInfoList::ResetLeagueProjectedGain(bool fSetUpdated)
1717 {
1718 	C4ClientPlayerInfos *pClient; int iClient=0;
1719 	while ((pClient = GetIndexedInfo(iClient++)))
1720 	{
1721 		C4PlayerInfo *pInfo; int iInfo = 0;
1722 		while ((pInfo = pClient->GetPlayerInfo(iInfo++)))
1723 			if (pInfo->IsLeagueProjectedGainValid())
1724 			{
1725 				pInfo->ResetLeagueProjectedGain();
1726 				if (fSetUpdated)
1727 					pClient->SetUpdated();
1728 			}
1729 	}
1730 }
1731 
CompileFunc(StdCompiler * pComp)1732 void C4PlayerInfoList::CompileFunc(StdCompiler *pComp)
1733 {
1734 	bool deserializing = pComp->isDeserializer();
1735 	if (deserializing) Clear();
1736 	// skip compiling if there is nothing to compile (cosmentics)
1737 	if (!deserializing && pComp->hasNaming() && iLastPlayerID == 0 && iClientCount == 0)
1738 		return;
1739 	// header
1740 	pComp->Value(mkNamingAdapt(iLastPlayerID, "LastPlayerID", 0));
1741 	// client count
1742 	int32_t iTemp = iClientCount;
1743 	pComp->Value(mkNamingCountAdapt<int32_t>(iTemp, "Client"));
1744 	if (iTemp < 0 || iTemp > C4MaxClient)
1745 		{ pComp->excCorrupt("client count out of range"); return; }
1746 	// grow list
1747 	if (deserializing)
1748 	{
1749 		if (iTemp > iClientCapacity) GrowList(iTemp - iClientCapacity);
1750 		iClientCount = iTemp;
1751 		ZeroMem(ppClients, sizeof(*ppClients) * iClientCount);
1752 	}
1753 	// client packets
1754 	pComp->Value(
1755 	  mkNamingAdapt(
1756 	    mkArrayAdaptMap(ppClients, iClientCount, mkPtrAdaptNoNull<C4ClientPlayerInfos>),
1757 	    "Client"));
1758 	// force compiler to specialize
1759 	mkPtrAdaptNoNull<C4ClientPlayerInfos>(*ppClients);
1760 }
1761 
GetStartupCount()1762 int32_t C4PlayerInfoList::GetStartupCount()
1763 {
1764 	// count all joined and to-be-joined
1765 	int32_t iCnt=0;
1766 	for (int32_t i=0; i<iClientCount; ++i)
1767 	{
1768 		int32_t j=0; C4PlayerInfo *pInfo;
1769 		while ((pInfo = ppClients[i]->GetPlayerInfo(j++)))
1770 			if (!pInfo->IsRemoved()) ++iCnt;
1771 	}
1772 	return iCnt;
1773 }
1774 
LoadResources()1775 void C4PlayerInfoList::LoadResources()
1776 {
1777 	// load for all players
1778 	int32_t i = iClientCount; C4ClientPlayerInfos **ppClient=ppClients;
1779 	while (i--) (*ppClient++)->LoadResources();
1780 }
1781 
FixIDCounter()1782 void C4PlayerInfoList::FixIDCounter()
1783 {
1784 	// make sure ID counter is same as largest info
1785 	for (int32_t i=0; i<iClientCount; ++i)
1786 	{
1787 		int32_t j=0; C4PlayerInfo *pInfo;
1788 		while ((pInfo = ppClients[i]->GetPlayerInfo(j++)))
1789 		{
1790 			iLastPlayerID = std::max<int32_t>(pInfo->GetID(), iLastPlayerID);
1791 		}
1792 	}
1793 }
1794 
1795 
1796 /* -- Player info packets -- */
1797 
CompileFunc(StdCompiler * pComp)1798 void C4PacketPlayerInfoUpdRequest::CompileFunc(StdCompiler *pComp)
1799 {
1800 	pComp->Value(Info);
1801 }
1802 
CompileFunc(StdCompiler * pComp)1803 void C4PacketPlayerInfo::CompileFunc(StdCompiler *pComp)
1804 {
1805 	pComp->Value(mkNamingAdapt(fIsRecreationInfo, "Recreation", false));
1806 	pComp->Value(mkNamingAdapt(Info, "Info"));
1807 }
1808