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