1 /*
2 * OpenClonk, http://www.openclonk.org
3 *
4 * Copyright (c) 2004-2009, RedWolf Design GmbH, http://www.clonk.de/
5 * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6 *
7 * Distributed under the terms of the ISC license; see accompanying file
8 * "COPYING" for details.
9 *
10 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11 * See accompanying file "TRADEMARK" for details.
12 *
13 * To redistribute this file separately, substitute the full license texts
14 * for the above references.
15 */
16 // NET2 network player management
17 // see header for some additional information
18 // 2do: Handle client joins after game go but before runtime join (Frame 0)?
19 // Those will not receive a player info list right in time
20
21 #include "C4Include.h"
22 #include "network/C4Network2Players.h"
23
24 #include "control/C4Control.h"
25 #include "control/C4GameControl.h"
26 #include "control/C4PlayerInfo.h"
27 #include "control/C4RoundResults.h"
28 #include "gui/C4GameLobby.h"
29 #include "network/C4Network2.h"
30
31 // *** C4Network2Players
32
C4Network2Players()33 C4Network2Players::C4Network2Players() : rInfoList(Game.Parameters.PlayerInfos)
34 {
35 // ctor - init rInfoList-ref to only
36 }
37
Init()38 void C4Network2Players::Init()
39 {
40 // caution: In this call, only local players are joined
41 // remote players may have been added already for runtime joins
42 // not in replay
43 if (Game.C4S.Head.Replay) return;
44 // network only
45 assert(::Network.isEnabled());
46 // must init before game is running
47 assert(!Game.IsRunning);
48 if (::Network.isHost())
49 {
50 // host: Rejoin script players from savegame before joining local players so team distribution is done correctly
51 // But prepare empty host list before recreation
52 JoinLocalPlayer("", true);
53 Game.PlayerInfos.CreateRestoreInfosForJoinedScriptPlayers(Game.RestorePlayerInfos);
54 JoinLocalPlayer(Game.PlayerFilenames, false);
55 }
56 else
57 {
58 // Client: join the local player(s)
59 JoinLocalPlayer(Game.PlayerFilenames, true);
60 }
61 }
62
Clear()63 void C4Network2Players::Clear()
64 {
65 // nothing...
66 }
67
JoinLocalPlayer(const char * szLocalPlayerFilename,bool initial)68 bool C4Network2Players::JoinLocalPlayer(const char *szLocalPlayerFilename, bool initial)
69 {
70 // ignore in replay
71 // shouldn't even come here though
72 assert(!Game.C4S.Head.Replay);
73 if (Game.C4S.Head.Replay) return false;
74 // if observing: don't try
75 if (Game.Clients.getLocal()->isObserver()) return false;
76 // network only
77 assert(::Network.isEnabled());
78 // create join info packet
79 C4ClientPlayerInfos JoinInfo(szLocalPlayerFilename, !initial);
80 // league game: get authentication for players
81 if (Game.Parameters.isLeague())
82 for (int i = 0; i < JoinInfo.GetPlayerCount(); i++)
83 {
84 C4PlayerInfo *pInfo = JoinInfo.GetPlayerInfo(i);
85 if (!::Network.LeaguePlrAuth(pInfo))
86 {
87 JoinInfo.RemoveIndexedInfo(i);
88 i--;
89 }
90 }
91 // host or client?
92 if (::Network.isHost())
93 {
94 // error joining players? Zero players is OK for initial packet; marks host as observer
95 if (!initial && !JoinInfo.GetPlayerCount()) return false;
96 // handle it as a direct request
97 HandlePlayerInfoUpdRequest(&JoinInfo, true);
98 }
99 else
100 {
101 // clients request initial joins at host only
102 // create player info for local player joins
103 C4PacketPlayerInfoUpdRequest JoinRequest(JoinInfo);
104 // any players to join? Zero players is OK for initial packet; marks client as observer
105 // it's also necessary to send the empty player info packet, so the host will answer
106 // with infos of all other clients
107 if (!initial && !JoinRequest.Info.GetPlayerCount()) return false;
108 ::Network.Clients.SendMsgToHost(MkC4NetIOPacket(PID_PlayerInfoUpdReq, JoinRequest));
109 // request activation
110 if (JoinRequest.Info.GetPlayerCount() && !Game.Clients.getLocal()->isActivated())
111 ::Network.RequestActivate();
112 }
113 // done, success
114 return true;
115 }
116
RequestPlayerInfoUpdate(const class C4ClientPlayerInfos & rRequest)117 void C4Network2Players::RequestPlayerInfoUpdate(const class C4ClientPlayerInfos &rRequest)
118 {
119 // network only
120 assert(::Network.isEnabled());
121 // host or client?
122 if (::Network.isHost())
123 {
124 // host processes directly
125 HandlePlayerInfoUpdRequest(&rRequest, true);
126 }
127 else
128 {
129 // client sends request to host
130 C4PacketPlayerInfoUpdRequest UpdateRequest(rRequest);
131 ::Network.Clients.SendMsgToHost(MkC4NetIOPacket(PID_PlayerInfoUpdReq, UpdateRequest));
132 }
133 }
134
HandlePlayerInfoUpdRequest(const class C4ClientPlayerInfos * pInfoPacket,bool fByHost)135 void C4Network2Players::HandlePlayerInfoUpdRequest(const class C4ClientPlayerInfos *pInfoPacket, bool fByHost)
136 {
137 // network host only
138 assert(::Network.isEnabled());
139 assert(::Network.isHost());
140 // copy client infos (need to be adjusted)
141 C4ClientPlayerInfos OwnInfoPacket(*pInfoPacket);
142 // safety: check any duplicate, unjoined players first
143 // check those with unassigned IDs only, so update packets won't be rejected by this
144 if (!OwnInfoPacket.IsInitialPacket())
145 {
146 C4ClientPlayerInfos *pExistingClientInfo = rInfoList.GetInfoByClientID(OwnInfoPacket.GetClientID());
147 if (pExistingClientInfo)
148 {
149 int iCnt=OwnInfoPacket.GetPlayerCount(); C4PlayerInfo *pPlrInfo;
150 C4Network2Res *pRes;
151 while (iCnt--) if ((pPlrInfo=OwnInfoPacket.GetPlayerInfo(iCnt)))
152 if (!pPlrInfo->GetID()) if ((pRes = pPlrInfo->GetRes()))
153 if (pExistingClientInfo->GetPlayerInfoByRes(pRes->getResID()))
154 {
155 // double join: simply deny without message
156 #ifdef _DEBUG
157 Log("Network: Duplicate player join rejected!");
158 #endif
159 OwnInfoPacket.RemoveIndexedInfo(iCnt);
160 }
161 }
162 if (!OwnInfoPacket.GetPlayerCount())
163 {
164 // player join request without players: probably all removed because doubled
165 #ifdef _DEBUG
166 Log("Network: Empty player join request ignored!");
167 #endif
168 return;
169 }
170 }
171 // assign player IDs
172 if (!rInfoList.AssignPlayerIDs(&OwnInfoPacket) && OwnInfoPacket.IsAddPacket())
173 {
174 // no players could be joined in an add request: probably because the maximum player limit has been reached
175 return;
176 }
177 // check doubled savegame player usage
178 UpdateSavegameAssignments(&OwnInfoPacket);
179 // update teams
180 rInfoList.AssignTeams(&OwnInfoPacket, fByHost);
181 // update any other player colors and names
182 // this may only change colors and names of all unjoined players (which is all players in lobby mode)
183 // any affected players will get an updated-flag
184 rInfoList.UpdatePlayerAttributes(&OwnInfoPacket, true);
185 // league score gains may now be different
186 rInfoList.ResetLeagueProjectedGain(true);
187 int32_t iPlrInfo = 0;
188 C4PlayerInfo *pPlrInfo;
189 while ((pPlrInfo = OwnInfoPacket.GetPlayerInfo(iPlrInfo++))) pPlrInfo->ResetLeagueProjectedGain();
190 if (Game.Parameters.isLeague())
191 {
192 // check league authentication for new players
193 for (int i = 0; i < OwnInfoPacket.GetPlayerCount(); i++)
194 {
195 if (!rInfoList.GetPlayerInfoByID(OwnInfoPacket.GetPlayerInfo(i)->GetID()))
196 {
197 C4PlayerInfo *pInfo = OwnInfoPacket.GetPlayerInfo(i);
198 // remove normal (non-script) player infos without authentication or when not in the lobby
199 if (pInfo->GetType() != C4PT_Script && (!::Network.isLobbyActive() || !::Network.LeaguePlrAuthCheck(pInfo)))
200 {
201 OwnInfoPacket.RemoveIndexedInfo(i);
202 i--;
203 }
204 else
205 // always reset authentication ID after check - it's not needed anymore
206 pInfo->SetAuthID("");
207 }
208 }
209 }
210 // send updates to all other clients and reset update flags
211 SendUpdatedPlayers();
212 // finally, add new player join as direct input
213 // this will add the player infos directly on host side (DirectExec as a subcall),
214 // so future player join request will take the other joined clients into consideration
215 // when assigning player colors, etc.; it will also start resource loading
216 // in running mode, this call will also put the actual player joins into the queue
217 ::Control.DoInput(CID_PlrInfo, new C4ControlPlayerInfo(OwnInfoPacket), CDT_Direct);
218 // notify lobby of updates
219 C4GameLobby::MainDlg *pLobby = ::Network.GetLobby();
220 if (pLobby) pLobby->OnPlayersChange();
221 }
222
HandlePlayerInfo(const class C4ClientPlayerInfos & rInfoPacket)223 void C4Network2Players::HandlePlayerInfo(const class C4ClientPlayerInfos &rInfoPacket)
224 {
225 // network only
226 assert(::Network.isEnabled());
227 // copy client player infos out of packet to be used in local list
228 C4ClientPlayerInfos *pClientInfo = new C4ClientPlayerInfos(rInfoPacket);
229 // add client info to local player info list - eventually deleting pClientInfo and
230 // returning a pointer to the new info structure when multiple player infos are merged
231 // may also replace existing info, if this is an update-call
232 pClientInfo = rInfoList.AddInfo(pClientInfo);
233 // make sure team list reflects teams set in player infos
234 Game.Teams.RecheckPlayers();
235 Game.Teams.RecheckTeams(); // recheck random teams - if a player left, teams may need to be rebalanced
236 // make sure resources are loaded for those players
237 rInfoList.LoadResources();
238 // get associated client - note that pClientInfo might be nullptr for empty packets that got discarded
239 if (pClientInfo)
240 {
241 const C4Client *pClient = Game.Clients.getClientByID(pClientInfo->GetClientID());
242 // host, game running and client active already?
243 if (::Network.isHost() && ::Network.isRunning() && pClient && pClient->isActivated())
244 {
245 // then join the players immediately
246 JoinUnjoinedPlayersInControlQueue(pClientInfo);
247 }
248 }
249 // adding the player may have invalidated other players (through team settings). Send them.
250 SendUpdatedPlayers();
251 // lobby: update players
252 C4GameLobby::MainDlg *pLobby = ::Network.GetLobby();
253 if (pLobby) pLobby->OnPlayersChange();
254 // invalidate reference
255 ::Network.InvalidateReference();
256 }
257
SendUpdatedPlayers()258 void C4Network2Players::SendUpdatedPlayers()
259 {
260 // check all clients for update
261 C4ClientPlayerInfos *pUpdInfo; int i=0;
262 while ((pUpdInfo = rInfoList.GetIndexedInfo(i++)))
263 if (pUpdInfo->IsUpdated())
264 {
265 pUpdInfo->ResetUpdated();
266 C4ControlPlayerInfo *pkSend = new C4ControlPlayerInfo(*pUpdInfo);
267 // send info to all
268 ::Control.DoInput(CID_PlrInfo, pkSend, CDT_Direct);
269 }
270 }
271
UpdateSavegameAssignments(C4ClientPlayerInfos * pNewInfo)272 void C4Network2Players::UpdateSavegameAssignments(C4ClientPlayerInfos *pNewInfo)
273 {
274 // safety
275 if (!pNewInfo) return;
276 // check all joins of new info; backwards so they can be deleted
277 C4PlayerInfo *pInfo, *pInfo2, *pSaveInfo; int i=pNewInfo->GetPlayerCount(), j, id;
278 while (i--) if ((pInfo = pNewInfo->GetPlayerInfo(i)))
279 if ((id=pInfo->GetAssociatedSavegamePlayerID()))
280 {
281 // check for non-existant savegame players
282 if (!(pSaveInfo=Game.RestorePlayerInfos.GetPlayerInfoByID(id)))
283 {
284 pInfo->SetAssociatedSavegamePlayer(id=0);
285 pNewInfo->SetUpdated();
286 }
287 // check for duplicates (can't really occur...)
288 if (id)
289 {
290 j=i;
291 while ((pInfo2 = pNewInfo->GetPlayerInfo(++j)))
292 if (pInfo2->GetAssociatedSavegamePlayerID() == id)
293 {
294 // fix it by resetting the savegame info
295 pInfo->SetAssociatedSavegamePlayer(id=0);
296 pNewInfo->SetUpdated(); break;
297 }
298 }
299 // check against all infos of other clients
300 C4ClientPlayerInfos *pkClientInfo; int k=0;
301 while ((pkClientInfo = rInfoList.GetIndexedInfo(k++)) && id)
302 {
303 // if it's not an add packet, don't check own client twice
304 if (pkClientInfo->GetClientID() == pNewInfo->GetClientID() && !(pNewInfo->IsAddPacket()))
305 continue;
306 // check against all players
307 j=0;
308 while ((pInfo2 = pkClientInfo->GetPlayerInfo(j++)))
309 if (pInfo2->GetAssociatedSavegamePlayerID() == id)
310 {
311 // fix it by resetting the savegame info
312 pInfo->SetAssociatedSavegamePlayer(id=0);
313 pNewInfo->SetUpdated(); break;
314 }
315 }
316 // if the player joined just for the savegame assignment, and that failed, delete it
317 if (!id && pInfo->IsJoinForSavegameOnly())
318 pNewInfo->RemoveIndexedInfo(i);
319 // prev info
320 }
321 }
322
ResetUpdatedPlayers()323 void C4Network2Players::ResetUpdatedPlayers()
324 {
325 // mark all client packets as up-to-date
326 C4ClientPlayerInfos *pUpdInfo; int i=0;
327 while ((pUpdInfo = rInfoList.GetIndexedInfo(i++))) pUpdInfo->ResetUpdated();
328 }
329
JoinUnjoinedPlayersInControlQueue(C4ClientPlayerInfos * pNewPacket)330 void C4Network2Players::JoinUnjoinedPlayersInControlQueue(C4ClientPlayerInfos *pNewPacket)
331 {
332 // only host may join any players to the queue
333 assert(::Network.isHost());
334 // check all players
335 int i=0; C4PlayerInfo *pInfo;
336 while ((pInfo = pNewPacket->GetPlayerInfo(i++)))
337 // not yet joined and no savegame assignment?
338 if (!pInfo->HasJoinIssued()) if (!pInfo->GetAssociatedSavegamePlayerID())
339 {
340 // join will be marked when queue is executed (C4Player::Join)
341 // but better mark join now already to prevent permanent sending overkill
342 pInfo->SetJoinIssued();
343 // do so!
344 C4Network2Res *pPlrRes = pInfo->GetRes();
345 C4Network2Client *pClient = ::Network.Clients.GetClientByID(pNewPacket->GetClientID());
346 if (!pPlrRes || (!pClient && pNewPacket->GetClientID() != ::Control.ClientID()))
347 if (pInfo->GetType() != C4PT_Script)
348 {
349 // failure: Non-script players must have a res to join from!
350 const char *szPlrName = pInfo->GetName(); if (!szPlrName) szPlrName="???";
351 LogF("Network: C4Network2Players::JoinUnjoinedPlayersInControlQueue failed to join player %s!", szPlrName);
352 continue;
353 }
354 if (pPlrRes)
355 {
356 // join with resource
357 Game.Input.Add(CID_JoinPlr,
358 new C4ControlJoinPlayer(pPlrRes->getFile(), pNewPacket->GetClientID(), pInfo->GetID(), pPlrRes->getCore()));
359 }
360 else
361 {
362 // join without resource (script player)
363 Game.Input.Add(CID_JoinPlr,
364 new C4ControlJoinPlayer(nullptr, pNewPacket->GetClientID(), pInfo->GetID()));
365 }
366 }
367 }
368
HandlePacket(char cStatus,const C4PacketBase * pPacket,C4Network2IOConnection * pConn)369 void C4Network2Players::HandlePacket(char cStatus, const C4PacketBase *pPacket, C4Network2IOConnection *pConn)
370 {
371 if (!pConn) return;
372
373 // find associated client
374 C4Network2Client *pClient = ::Network.Clients.GetClient(pConn);
375 if (!pClient) pClient = ::Network.Clients.GetClientByID(pConn->getClientID());
376
377 #define GETPKT(type, name) \
378 assert(pPacket); const type &name = \
379 static_cast<const type &>(*pPacket);
380
381 // player join request?
382 if (cStatus == PID_PlayerInfoUpdReq)
383 {
384 GETPKT(C4PacketPlayerInfoUpdRequest, pkPlrInfo);
385 // this packet is sent to the host only, and thus cannot have been sent from the host
386 if (!::Network.isHost()) return;
387 // handle this packet
388 HandlePlayerInfoUpdRequest(&pkPlrInfo.Info, false);
389 }
390 else if (cStatus == PID_LeagueRoundResults)
391 {
392 GETPKT(C4PacketLeagueRoundResults, pkLeagueInfo);
393 // accepted from the host only
394 if (!pClient || !pClient->isHost()) return;
395 // process
396 Game.RoundResults.EvaluateLeague(pkLeagueInfo.sResultsString.getData(), pkLeagueInfo.fSuccess, pkLeagueInfo.Players);
397 }
398
399 #undef GETPKT
400 }
401
OnClientPart(C4Client * pPartClient)402 void C4Network2Players::OnClientPart(C4Client *pPartClient)
403 {
404 // lobby could be notified about the removal - but this would be redundant, because
405 // client leave notification is already done directly; this will delete any associated players
406 C4ClientPlayerInfos **ppCltInfo = rInfoList.GetInfoPtrByClientID(pPartClient->getID());
407 // abort here if no info is registered - client seems to have had a short life only, anyway...
408 if (!ppCltInfo) return;
409 // remove all unjoined player infos
410 for (int32_t i = 0; i < (*ppCltInfo)->GetPlayerCount();)
411 {
412 C4PlayerInfo *pInfo = (*ppCltInfo)->GetPlayerInfo(i);
413 // not joined yet? remove it
414 if (!pInfo->HasJoined())
415 (*ppCltInfo)->RemoveIndexedInfo(i);
416 else
417 // just ignore, the "removed" flag will be set eventually
418 i++;
419 }
420 // empty? remove
421 if (!(*ppCltInfo)->GetPlayerCount())
422 rInfoList.RemoveInfo(ppCltInfo);
423 // update team association to left player
424 Game.Teams.RecheckPlayers();
425 // host: update player data according to leaver
426 if (::Network.isHost() && ::Network.isEnabled())
427 {
428 // host: update any player colors and names
429 rInfoList.UpdatePlayerAttributes();
430 // team distribution of remaining unjoined players may change
431 Game.Teams.RecheckTeams();
432 // league score gains may now be different
433 Game.PlayerInfos.ResetLeagueProjectedGain(true);
434 // send changes to all clients and reset update flags
435 SendUpdatedPlayers();
436 }
437 // invalidate reference
438 if (::Network.isHost())
439 ::Network.InvalidateReference();
440 }
441
OnStatusGoReached()442 void C4Network2Players::OnStatusGoReached()
443 {
444 // host only
445 if (!::Network.isHost()) return;
446 // check all player lists
447 int i=0; C4ClientPlayerInfos *pkInfo;
448 while ((pkInfo = rInfoList.GetIndexedInfo(i++)))
449 // any unsent player joins?
450 if (pkInfo->HasUnjoinedPlayers())
451 {
452 // get client core
453 const C4Client *pClient = Game.Clients.getClientByID(pkInfo->GetClientID());
454 // don't send if client is unknown or not activated yet
455 if (!pClient || !pClient->isActivated()) continue;
456 // send them w/o info packet
457 // info packets are synced during pause mode
458 JoinUnjoinedPlayersInControlQueue(pkInfo);
459 }
460 }
461
GetLocalPlayerInfoPacket() const462 C4ClientPlayerInfos *C4Network2Players::GetLocalPlayerInfoPacket() const
463 {
464 // get local client ID
465 int iLocalClientID = Game.Clients.getLocalID();
466 // check all packets for same client ID as local
467 int i=0; C4ClientPlayerInfos *pkInfo;
468 while ((pkInfo = rInfoList.GetIndexedInfo(i++)))
469 if (pkInfo->GetClientID() == iLocalClientID)
470 // found
471 return pkInfo;
472 // not found
473 return nullptr;
474 }
475
GetIndexedPlayerInfoPacket(int iIndex)476 C4ClientPlayerInfos *C4Network2Players::GetIndexedPlayerInfoPacket(int iIndex)
477 {
478 // just get from info list
479 return rInfoList.GetIndexedInfo(iIndex);
480 }
481
GetClientChatColor(int idForClient,bool fLobby) const482 DWORD C4Network2Players::GetClientChatColor(int idForClient, bool fLobby) const
483 {
484 // return color of first joined player; force to white for unknown
485 // deactivated always white
486 const C4Client *pClient = Game.Clients.getClientByID(idForClient);
487 if (pClient && pClient->isActivated())
488 {
489 // get players for activated
490 C4ClientPlayerInfos *pInfoPacket = rInfoList.GetInfoByClientID(idForClient);
491 C4PlayerInfo *pPlrInfo;
492 if (pInfoPacket && (pPlrInfo = pInfoPacket->GetPlayerInfo(0, C4PT_User)))
493 {
494 if (fLobby)
495 return pPlrInfo->GetLobbyColor();
496 else
497 return pPlrInfo->GetColor();
498 }
499 }
500 // default color
501 return 0xffffff;
502 }
503
504