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