1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2011-2018, The OpenClonk Team and contributors
6 * Copyright (c) 2017-2021, The LegacyClonk 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
18#include <C4Include.h>
19#include <C4Network2Client.h>
20
21#include <C4Log.h>
22#include <C4Console.h>
23#include <C4Network2.h>
24#include <C4Network2IO.h>
25#include <C4Network2Stats.h>
26#include <C4GameLobby.h> // fullscreen network lobby
27
28#include <algorithm>
29#include <chrono>
30#include <thread>
31#include <utility>
32
33// *** C4Network2Client
34
35C4Network2Client::C4Network2Client(std::shared_ptr<spdlog::logger> logger, C4Client *pClient)
36 : logger{std::move(logger)},
37 pClient(pClient),
38 eStatus(NCS_Ready),
39 iLastActivity(0),
40 pMsgConn(nullptr), pDataConn(nullptr),
41 iNextConnAttempt(0),
42 pNext(nullptr), pParent(nullptr), pstatPing(nullptr) {}
43
44C4Network2Client::~C4Network2Client()
45{
46 ClearGraphs();
47 if (pMsgConn) { pMsgConn->Close(); pMsgConn->DelRef(); } pMsgConn = nullptr;
48 if (pDataConn) { pDataConn->Close(); pDataConn->DelRef(); } pDataConn = nullptr;
49 if (pClient) pClient->UnlinkNetClient();
50}
51
52namespace
53{
54 template<typename Addrs, typename Property, typename Value>
55 auto getClientAddress(Addrs &&addresses, Property property, Value &&value)
56 {
57 return std::find_if(addresses.begin(), addresses.end(), [property, value](const auto &ownAddr) { return ownAddr.*property == value; });
58 }
59}
60
61bool C4Network2Client::hasConn(C4Network2IOConnection *pConn)
62{
63 return pMsgConn == pConn || pDataConn == pConn;
64}
65
66void C4Network2Client::SetMsgConn(C4Network2IOConnection *pConn)
67{
68 // security
69 if (pConn != pMsgConn)
70 {
71 if (pMsgConn) pMsgConn->DelRef();
72 pMsgConn = pConn;
73 pMsgConn->AddRef();
74 }
75 if (!pDataConn) SetDataConn(pConn);
76}
77
78void C4Network2Client::SetDataConn(C4Network2IOConnection *pConn)
79{
80 // security
81 if (pConn != pDataConn)
82 {
83 if (pDataConn) pDataConn->DelRef();
84 pDataConn = pConn;
85 pDataConn->AddRef();
86 }
87 if (!pMsgConn) SetMsgConn(pConn);
88}
89
90void C4Network2Client::RemoveConn(C4Network2IOConnection *pConn)
91{
92 if (pConn == pMsgConn)
93 {
94 pMsgConn->DelRef(); pMsgConn = nullptr;
95 }
96 if (pConn == pDataConn)
97 {
98 pDataConn->DelRef(); pDataConn = nullptr;
99 }
100 if (pMsgConn && !pDataConn) SetDataConn(pMsgConn);
101 if (!pMsgConn && pDataConn) SetMsgConn(pDataConn);
102}
103
104void C4Network2Client::CloseConns(const char *szMsg)
105{
106 C4PacketConnRe Pkt(false, false, szMsg);
107 C4Network2IOConnection *pConn;
108 while (pConn = pMsgConn)
109 {
110 // send packet, close
111 if (pConn->isOpen())
112 {
113 pConn->Send(rPkt: MkC4NetIOPacket(cStatus: PID_ConnRe, Pkt));
114 pConn->Close();
115 }
116 // remove
117 RemoveConn(pConn);
118 }
119}
120
121bool C4Network2Client::SendMsg(C4NetIOPacket rPkt) const
122{
123 return getMsgConn() && getMsgConn()->Send(rPkt);
124}
125
126bool C4Network2Client::DoConnectAttempt(C4Network2IO *pIO)
127{
128 // local?
129 if (isLocal()) { iNextConnAttempt = 0; return true; }
130 // msg and data connected? Nothing to do
131 if (getMsgConn() != getDataConn()) { iNextConnAttempt = time(timer: nullptr) + 10; return true; }
132 // too early?
133 if (iNextConnAttempt && iNextConnAttempt > time(timer: nullptr)) return true;
134 // find address to try
135 int32_t iBestAddress = -1;
136 for (int32_t i = 0; const auto &addr : Addresses)
137 {
138 // valid address?
139 if (!addr.Addr.GetAddr().IsNullHost()
140 // no connection for this protocol?
141 && (!pDataConn || addr.Addr.GetProtocol() != pDataConn->getProtocol())
142 && (!pMsgConn || addr.Addr.GetProtocol() != pMsgConn->getProtocol())
143 // protocol available?
144 && pIO->getNetIO(eProt: addr.Addr.GetProtocol()))
145 {
146 if (iBestAddress < 0 || addr.ConnectionAttempts < Addresses[iBestAddress].ConnectionAttempts)
147 {
148 iBestAddress = i;
149 }
150 }
151 ++i;
152 }
153 // too many attempts or nothing found?
154 if (iBestAddress < 0 || Addresses[iBestAddress].ConnectionAttempts > C4NetClientConnectAttempts)
155 {
156 iNextConnAttempt = time(timer: nullptr) + 10; return true;
157 }
158 // save attempt
159 ++Addresses[iBestAddress].ConnectionAttempts;
160 iNextConnAttempt = time(timer: nullptr) + C4NetClientConnectInterval;
161 auto addr = Addresses[iBestAddress].Addr.GetAddr();
162 const auto addrProtocol = Addresses[iBestAddress].Addr.GetProtocol();
163
164 // Try TCP simultaneous open if the stars align right
165 if (addr.GetFamily() == C4NetIO::addr_t::IPv6 && // Address needs to be IPv6...
166 !addr.IsLocal() && !addr.IsPrivate() && // ...global unicast...
167 addrProtocol == P_TCP && // ...TCP,
168 !tcpSimOpenSocket && // there is no previous request,
169 pParent->GetLocal()->getID() < getID()) // and make sure that only one client per pair initiates a request.
170 {
171 DoTCPSimultaneousOpen(pIO, addr: C4Network2Address{});
172 }
173
174 const std::set<int> DefaultInterfaceIDs{0};
175 const auto &interfaceIDs = addr.IsLocal() ?
176 Game.Network.Clients.GetLocal()->getInterfaceIDs() : DefaultInterfaceIDs;
177 for (const auto &id : interfaceIDs)
178 {
179 addr.SetScopeId(id);
180 logger->info(fmt: "Network: connecting client {} on {}...", args: getName(), args: addr.ToString());
181 if (pIO->Connect(addr, prot: addrProtocol, ccore: pClient->getCore()))
182 return true;
183 }
184 return false;
185}
186
187bool C4Network2Client::DoTCPSimultaneousOpen(C4Network2IO *const pIO, const C4Network2Address &addr)
188{
189 if (!pIO->getNetIO(eProt: P_TCP)) return false;
190
191 // Did we already bind a socket?
192 if (tcpSimOpenSocket)
193 {
194 logger->info(fmt: "Network: connecting client {} on {} with TCP simultaneous open...", args: getName(), args: addr.GetAddr().ToString());
195 return pIO->ConnectWithSocket(addr: addr.GetAddr(), eProt: addr.GetProtocol(), nCCore: pClient->getCore(), socket: std::move(tcpSimOpenSocket));
196 }
197 else
198 {
199 // No - bind one, inform peer, and schedule a connection attempt.
200 auto NetIOTCP = dynamic_cast<C4NetIOTCP *>(pIO->getNetIO(eProt: P_TCP));
201 auto bindAddr = pParent->GetLocal()->IPv6AddrFromPuncher;
202 // We need to know an address that works.
203 if (bindAddr.IsNull()) return false;
204 bindAddr.SetPort(0);
205 tcpSimOpenSocket = NetIOTCP->Bind(addr: bindAddr);
206 if (!tcpSimOpenSocket) return false;
207 const auto &boundAddr = tcpSimOpenSocket->GetAddress();
208 logger->info(fmt: "Network: {} TCP simultaneous open request for client {} from {}...",
209 args: (addr.isIPNull() ? "initiating" : "responding to"),
210 args: getName(), args: boundAddr.ToString());
211 // Send address we bound to to the client.
212 if (!SendMsg(rPkt: MkC4NetIOPacket(cStatus: PID_TCPSimOpen, Pkt: C4PacketTCPSimOpen{
213 pParent->GetLocal()->getID(), C4Network2Address{boundAddr, P_TCP}})))
214 {
215 return false;
216 }
217 if (!addr.isIPNull())
218 {
219 // We need to delay the connection attempt a bit. Unfortunately,
220 // waiting for the next tick would usually take way too much time.
221 // Instead, we block the main thread for a very short time and hope
222 // that noone notices...
223 const int ping{getMsgConn()->getLag()};
224 std::this_thread::sleep_for(rtime: std::chrono::milliseconds(std::min(a: ping / 2, b: 10)));
225 DoTCPSimultaneousOpen(pIO, addr);
226 }
227 return true;
228 }
229}
230
231bool C4Network2Client::hasAddr(const C4Network2Address &addr) const
232{
233 // Note that the host only knows its own address as 0.0.0.0, so if the real address is being added, that can't be sorted out.
234 return getClientAddress(addresses: Addresses, property: &ClientAddress::Addr, value: addr) != Addresses.end();
235}
236
237void C4Network2Client::AddAddrFromPuncher(const C4NetIO::addr_t &addr)
238{
239 AddAddr(addr: C4Network2Address{addr, P_UDP}, fAnnounce: true, inFront: true);
240 // If the outside port matches the inside port, there is no port translation and the
241 // TCP address will probably work as well.
242 if (addr.GetPort() != Config.Network.PortUDP)
243 {
244 auto udpAddr = addr;
245 udpAddr.SetPort(Config.Network.PortUDP);
246 AddAddr(addr: C4Network2Address{udpAddr, P_UDP}, fAnnounce: true, inFront: true);
247 }
248 if (Config.Network.PortTCP > 0)
249 {
250 auto tcpAddr = addr;
251 tcpAddr.SetPort(Config.Network.PortTCP);
252 AddAddr(addr: C4Network2Address{tcpAddr, P_TCP}, fAnnounce: true, inFront: true);
253 }
254 // Save IPv6 address for TCP simultaneous connect.
255 if (addr.GetFamily() == C4NetIO::addr_t::IPv6)
256 IPv6AddrFromPuncher = addr;
257}
258
259bool C4Network2Client::AddAddr(const C4Network2Address &addr, bool fAnnounce, bool inFront)
260{
261 if (hasAddr(addr)) return true;
262
263 if (inFront)
264 {
265 Addresses.emplace(position: Addresses.begin(), args: addr);
266 }
267 else
268 {
269 Addresses.emplace_back(args: addr);
270 }
271 // attempt to use this one
272 if (!iNextConnAttempt) iNextConnAttempt = time(timer: nullptr);
273 // announce
274 if (fAnnounce)
275 if (!pParent->BroadcastMsgToConnClients(rPkt: MkC4NetIOPacket(cStatus: PID_Addr, Pkt: C4PacketAddr(getID(), addr))))
276 return false;
277 // done
278 return true;
279}
280
281void C4Network2Client::AddLocalAddrs(const std::uint16_t iPortTCP, const std::uint16_t iPortUDP)
282{
283 C4NetIO::addr_t addr{C4Network2HostAddress::AnyIPv4};
284
285 if (iPortTCP != 0)
286 {
287 addr.SetPort(iPortTCP);
288 AddAddr(addr: C4Network2Address(addr, P_TCP), fAnnounce: false);
289 }
290
291 if (iPortUDP != 0)
292 {
293 addr.SetPort(iPortUDP);
294 AddAddr(addr: C4Network2Address(addr, P_UDP), fAnnounce: false);
295 }
296
297 for (const auto &ha : C4NetIO::GetLocalAddresses())
298 {
299 addr.SetAddress(host: ha);
300 if (iPortTCP != 0)
301 {
302 addr.SetPort(iPortTCP);
303 AddAddr(addr: C4Network2Address(addr, P_TCP), fAnnounce: false);
304 }
305
306 if (iPortUDP != 0)
307 {
308 addr.SetPort(iPortUDP);
309 AddAddr(addr: C4Network2Address(addr, P_UDP), fAnnounce: false);
310 }
311
312 if (addr.GetScopeId())
313 {
314 InterfaceIDs.insert(x: addr.GetScopeId());
315 }
316 }
317}
318
319void C4Network2Client::SendAddresses(C4Network2IOConnection *pConn)
320{
321 // send all addresses
322 for (const auto &clientAddr : Addresses)
323 {
324 auto addr = clientAddr.Addr;
325 if (addr.GetAddr().GetScopeId() &&
326 (!pConn || pConn->getPeerAddr().GetScopeId() != addr.GetAddr().GetScopeId()))
327 {
328 continue;
329 }
330
331 addr.GetAddr().SetScopeId(0);
332 const C4NetIOPacket Pkt{MkC4NetIOPacket(cStatus: PID_Addr, Pkt: C4PacketAddr{getID(), addr})};
333 if (pConn)
334 pConn->Send(rPkt: Pkt);
335 else
336 pParent->BroadcastMsgToConnClients(rPkt: Pkt);
337 }
338}
339
340void C4Network2Client::CreateGraphs()
341{
342 // del prev
343 ClearGraphs();
344 // get client color
345 static const uint32_t ClientDefColors[] = { 0xff0000, 0x00ff00, 0xffff00, 0x7f7fff, 0xffffff, 0x00ffff, 0xff00ff, 0x7f7f7f, 0xff7f7f, 0x7fff7f, 0x0000ff };
346 int32_t iClientColorNum = sizeof(ClientDefColors) / sizeof(uint32_t);
347 uint32_t dwClientClr = ClientDefColors[std::max<int32_t>(a: getID(), b: 0) % iClientColorNum];
348 // create graphs
349 pstatPing = new C4TableGraph(C4TableGraph::DefaultBlockLength, Game.pNetworkStatistics ? Game.pNetworkStatistics->SecondCounter : 0);
350 pstatPing->SetColorDw(dwClientClr);
351 pstatPing->SetTitle(getName());
352 // register into stat module
353 if (Game.pNetworkStatistics) Game.pNetworkStatistics->statPings.AddGraph(pAdd: pstatPing);
354}
355
356void C4Network2Client::ClearGraphs()
357{
358 // del all assigned graphs
359 if (pstatPing && Game.pNetworkStatistics)
360 {
361 Game.pNetworkStatistics->statPings.RemoveGraph(pRemove: pstatPing);
362 }
363 delete pstatPing;
364 pstatPing = nullptr;
365}
366
367// *** C4Network2ClientList
368
369C4Network2ClientList::C4Network2ClientList(C4Network2IO *pIO)
370 : pFirst(nullptr), pLocal(nullptr), pIO(pIO) {}
371
372C4Network2ClientList::~C4Network2ClientList()
373{
374 Clear();
375}
376
377C4Network2Client *C4Network2ClientList::GetClientByID(int32_t iID) const
378{
379 for (C4Network2Client *pClient = pFirst; pClient; pClient = pClient->pNext)
380 if (pClient->getID() == iID)
381 return pClient;
382 return nullptr;
383}
384
385C4Network2Client *C4Network2ClientList::GetClient(const char *szName) const
386{
387 for (C4Network2Client *pClient = pFirst; pClient; pClient = pClient->pNext)
388 if (SEqual(szStr1: pClient->getName(), szStr2: szName))
389 return pClient;
390 return nullptr;
391}
392
393C4Network2Client *C4Network2ClientList::GetClient(C4Network2IOConnection *pConn) const
394{
395 for (C4Network2Client *pClient = pFirst; pClient; pClient = pClient->pNext)
396 if (pClient->hasConn(pConn))
397 return pClient;
398 return nullptr;
399}
400
401C4Network2Client *C4Network2ClientList::GetClient(const C4ClientCore &CCore, int32_t iMaxDiffLevel)
402{
403 for (C4Network2Client *pClient = pFirst; pClient; pClient = pClient->pNext)
404 if (pClient->getCore().getDiffLevel(CCore2: CCore) <= iMaxDiffLevel)
405 return pClient;
406 return nullptr;
407}
408
409C4Network2Client *C4Network2ClientList::GetHost()
410{
411 return GetClientByID(iID: C4ClientIDHost);
412}
413
414C4Network2Client *C4Network2ClientList::GetNextClient(C4Network2Client *pClient)
415{
416 return pClient ? pClient->pNext : pFirst;
417}
418
419void C4Network2ClientList::Init(std::shared_ptr<spdlog::logger> logger, C4ClientList *pnClientList, bool fnHost)
420{
421 // save flag
422 fHost = fnHost;
423 // initialize
424 pClientList = pnClientList;
425 pClientList->InitNetwork(pNetClients: this);
426 // set logger here, because InitNetwork() calls this->Clear()
427 this->logger = std::move(logger);
428}
429
430C4Network2Client *C4Network2ClientList::RegClient(C4Client *pClient)
431{
432 // security
433 if (pClient->getNetClient())
434 return pClient->getNetClient();
435 // find insert position
436 C4Network2Client *pPos = pFirst, *pLast = nullptr;
437 for (; pPos; pLast = pPos, pPos = pPos->getNext())
438 if (pPos->getID() > pClient->getID())
439 break;
440 assert(!pLast || pLast->getID() != pClient->getID());
441 // create new client
442 C4Network2Client *pNetClient = new C4Network2Client(logger, pClient);
443 // add to list
444 pNetClient->pNext = pPos;
445 (pLast ? pLast->pNext : pFirst) = pNetClient;
446 pNetClient->pParent = this;
447 // local?
448 if (pClient->isLocal())
449 pLocal = pNetClient;
450 else
451 // set auto-accept
452 pIO->AddAutoAccept(CCore: pClient->getCore());
453 // add
454 return pNetClient;
455}
456
457void C4Network2ClientList::DeleteClient(C4Network2Client *pClient)
458{
459 // close connections
460 pClient->CloseConns(szMsg: "removing client");
461 // remove from list
462 if (pClient == pFirst)
463 pFirst = pClient->getNext();
464 else
465 {
466 C4Network2Client *pPrev;
467 for (pPrev = pFirst; pPrev && pPrev->getNext(); pPrev = pPrev->getNext())
468 if (pPrev->getNext() == pClient)
469 break;
470 if (pPrev && pPrev->getNext() == pClient)
471 pPrev->pNext = pClient->getNext();
472 }
473 // remove auto-accept
474 pIO->RemoveAutoAccept(CCore: pClient->getCore());
475 // delete
476 delete pClient;
477}
478
479void C4Network2ClientList::Clear()
480{
481 // remove link to main client list
482 if (pClientList)
483 {
484 C4ClientList *poClientList = pClientList;
485 pClientList = nullptr;
486 poClientList->ClearNetwork();
487 }
488 // delete clients
489 while (pFirst)
490 {
491 DeleteClient(pClient: pFirst);
492 }
493 pLocal = nullptr;
494 logger.reset();
495}
496
497bool C4Network2ClientList::BroadcastMsgToConnClients(const C4NetIOPacket &rPkt)
498{
499 // Send a msg to all clients that are currently directly reachable.
500
501 // lock
502 pIO->BeginBroadcast(fSelectAll: false);
503 // select connections for broadcast
504 for (C4Network2Client *pClient = pFirst; pClient; pClient = pClient->getNext())
505 if (pClient->isConnected())
506 pClient->getMsgConn()->SetBroadcastTarget(true);
507 // broadcast
508 bool fSuccess = pIO->Broadcast(rPkt);
509 // unlock
510 pIO->EndBroadcast();
511 // finished
512 return fSuccess;
513}
514
515bool C4Network2ClientList::BroadcastMsgToClients(const C4NetIOPacket &rPkt, bool includeHost)
516{
517 // Send a msg to all clients, including clients that are not connected to
518 // this computer (will get forwarded by host).
519
520 C4PacketFwd Fwd; Fwd.SetListType(true);
521 // lock
522 pIO->BeginBroadcast(fSelectAll: false);
523 // select connections for broadcast
524 for (C4Network2Client *pClient = pFirst; pClient; pClient = pClient->getNext())
525 if (!pClient->isHost() || includeHost)
526 if (pClient->isConnected())
527 {
528 pClient->getMsgConn()->SetBroadcastTarget(true);
529 Fwd.AddClient(iClient: pClient->getID());
530 }
531 // broadcast
532 bool fSuccess = pIO->Broadcast(rPkt);
533 // unlock
534 pIO->EndBroadcast();
535 // clients: send forward request to host
536 if (!fHost)
537 {
538 Fwd.SetData(rPkt);
539 fSuccess &= SendMsgToHost(rPkt: MkC4NetIOPacket(cStatus: PID_FwdReq, Pkt: Fwd));
540 }
541 return fSuccess;
542}
543
544bool C4Network2ClientList::SendMsgToHost(C4NetIOPacket rPkt)
545{
546 // find host
547 C4Network2Client *pHost = GetHost();
548 if (!pHost) return false;
549 // send message
550 if (!pHost->getMsgConn()) return false;
551 return pHost->SendMsg(rPkt);
552}
553
554bool C4Network2ClientList::SendMsgToClient(int32_t iClient, C4NetIOPacket &&rPkt)
555{
556 // find client
557 C4Network2Client *pClient = GetClientByID(iID: iClient);
558 if (!pClient) return false;
559 // connected? send directly
560 if (pClient->isConnected())
561 return pClient->SendMsg(rPkt);
562 // forward
563 C4PacketFwd Fwd; Fwd.SetListType(false);
564 Fwd.AddClient(iClient);
565 Fwd.SetData(rPkt);
566 return SendMsgToHost(rPkt: MkC4NetIOPacket(cStatus: PID_FwdReq, Pkt: Fwd));
567}
568
569void C4Network2ClientList::HandlePacket(char cStatus, const C4PacketBase *pBasePkt, C4Network2IOConnection *pConn)
570{
571 // find associated client
572 C4Network2Client *pClient = GetClient(pConn);
573 if (!pClient) return;
574
575#define GETPKT(type, name) \
576 assert(pBasePkt); \
577 const type &name = static_cast<const type &>(*pBasePkt);
578
579 switch (cStatus)
580 {
581 case PID_Addr: // address propagation
582 {
583 GETPKT(C4PacketAddr, rPkt);
584 // find client
585 pClient = GetClientByID(iID: rPkt.getClientID());
586 if (pClient)
587 {
588 C4Network2Address addr = rPkt.getAddr();
589 // IP zero? Set to IP from where the packet came
590 if (addr.isIPNull())
591 {
592 addr.SetIP(pConn->getPeerAddr());
593 }
594 // add (no announce)
595 if (pClient->AddAddr(addr, fAnnounce: true))
596 // new address? Try to connect
597 pClient->DoConnectAttempt(pIO);
598 }
599 }
600 break;
601
602 case PID_TCPSimOpen:
603 {
604 GETPKT(C4PacketTCPSimOpen, rPkt);
605 if (const auto &client = GetClientByID(iID: rPkt.GetClientID()))
606 {
607 client->DoTCPSimultaneousOpen(pIO, addr: rPkt.GetAddr());
608 }
609 }
610 break;
611 }
612
613#undef GETPKT
614}
615
616void C4Network2ClientList::SendAddresses(C4Network2IOConnection *pConn)
617{
618 // send all client addresses known
619 for (C4Network2Client *pClient = pFirst; pClient; pClient = pClient->getNext())
620 pClient->SendAddresses(pConn);
621}
622
623void C4Network2ClientList::DoConnectAttempts()
624{
625 // check interval
626 time_t t; time(timer: &t);
627 for (C4Network2Client *pClient = pFirst; pClient; pClient = pClient->getNext())
628 if (!pClient->isLocal() && !pClient->isRemoved() && pClient->getNextConnAttempt() && pClient->getNextConnAttempt() <= t)
629 // attempt connect
630 pClient->DoConnectAttempt(pIO);
631}
632
633void C4Network2ClientList::ResetReady()
634{
635 for (C4Network2Client *pClient = pFirst; pClient; pClient = pClient->getNext())
636 if (pClient->isWaitedFor())
637 pClient->SetStatus(NCS_NotReady);
638}
639
640bool C4Network2ClientList::AllClientsReady() const
641{
642 for (C4Network2Client *pClient = pFirst; pClient; pClient = pClient->getNext())
643 if (!pClient->isLocal() && pClient->isWaitedFor() && !pClient->isReady())
644 return false;
645 return true;
646}
647
648void C4Network2ClientList::UpdateClientActivity()
649{
650 for (C4Network2Client *pClient = pFirst; pClient; pClient = pClient->getNext())
651 if (pClient->isActivated())
652 if (Game.Players.GetAtClient(iClient: pClient->getID()))
653 pClient->SetLastActivity(Game.FrameCounter);
654}
655
656// *** C4PacketAddr
657
658void C4PacketAddr::CompileFunc(StdCompiler *pComp)
659{
660 pComp->Value(rStruct: mkNamingAdapt(rValue: mkIntPackAdapt(rVal&: iClientID), szName: "ClientID", rDefault: C4ClientIDUnknown));
661 pComp->Value(rStruct: mkNamingAdapt(rValue&: addr, szName: "Addr"));
662}
663
664// *** C4PacketTCPSimOpen
665
666void C4PacketTCPSimOpen::CompileFunc(StdCompiler *const comp)
667{
668 comp->Value(rStruct: mkNamingAdapt(rValue: mkIntPackAdapt(rVal&: clientID), szName: "ClientID", rDefault: C4ClientIDUnknown));
669 comp->Value(rStruct: mkNamingAdapt(rValue&: addr, szName: "Addr"));
670}
671