1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2017-2021, The LegacyClonk 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
17#include <C4Include.h>
18#include <C4Client.h>
19
20#include <C4Config.h>
21#include <C4Network2Client.h>
22#include <C4Game.h>
23#include <C4Log.h>
24
25#ifndef _WIN32
26#include <netdb.h>
27#include <sys/socket.h> /* for AF_INET */
28#endif
29
30// C4ClientCore
31
32C4ClientCore::C4ClientCore()
33 : iID(-1),
34 fActivated(false),
35 fObserver(false),
36 fLobbyReady(false)
37{
38 Name.Ref(pnData: ""); Nick.Ref(pnData: "");
39}
40
41C4ClientCore::~C4ClientCore() {}
42
43void C4ClientCore::SetLocal(int32_t inID, bool fnActivated, bool fnObserver)
44{
45 // status
46 iID = inID;
47 fActivated = fnActivated;
48 fObserver = fnObserver;
49 // misc
50 Name.CopyValidated(sFromVal: Config.Network.LocalName);
51
52 ValidatedStdStrBuf<C4InVal::VAL_NameNoEmpty> NickBuf;
53 NickBuf.Copy(Buf2: Config.Network.Nick);
54 if (!NickBuf.getLength()) NickBuf.CopyValidated(sFromVal: Name);
55 Nick.CopyValidated(sFromVal: NickBuf);
56}
57
58int32_t C4ClientCore::getDiffLevel(const C4ClientCore &CCore2) const
59{
60 // nick won't ever be changed
61 if (Nick != CCore2.getNick())
62 return C4ClientCoreDL_Different;
63 // identification changed?
64 if (iID != CCore2.getID() || Name != CCore2.getName())
65 return C4ClientCoreDL_IDChange;
66 // status change?
67 if (fActivated != CCore2.isActivated() || fObserver != CCore2.isObserver() || fLobbyReady != CCore2.isLobbyReady())
68 return C4ClientCoreDL_IDMatch;
69 // otherwise: identical
70 return C4ClientCoreDL_None;
71}
72
73// C4PacketBase virtuals
74
75void C4ClientCore::CompileFunc(StdCompiler *pComp)
76{
77 pComp->Value(rStruct: mkNamingAdapt(rValue&: iID, szName: "ID", rDefault: C4ClientIDUnknown));
78 pComp->Value(rStruct: mkNamingAdapt(rValue&: fActivated, szName: "Activated", rDefault: false));
79 pComp->Value(rStruct: mkNamingAdapt(rValue&: fObserver, szName: "Observer", rDefault: false));
80 pComp->Value(rStruct: mkNamingAdapt(rValue&: Name, szName: "Name", rDefault: ""));
81 pComp->Value(rStruct: mkNamingAdapt(rValue&: Nick, szName: "Nick", rDefault: ""));
82 pComp->Value(rStruct: mkNamingAdapt(rValue&: fLobbyReady, szName: "LobbyReady", rDefault: false));
83}
84
85// *** C4Client
86
87C4Client::C4Client()
88 : C4Client{C4ClientCore()} {}
89
90C4Client::C4Client(const C4ClientCore &Core)
91 : Core{Core}, fLocal{false}, pNetClient{nullptr}, pNext{nullptr}, last_lobby_ready_change{0}, muted{Config.Sound.MuteSoundCommand} {}
92
93C4Client::~C4Client()
94{
95 // network client bind must be removed before
96 assert(!pNetClient);
97}
98
99bool C4Client::TryAllowSound()
100{
101 return Config.Cooldowns.SoundCommand.TryReset();
102}
103
104void C4Client::SetActivated(bool fnActivated)
105{
106 Core.SetActivated(fnActivated);
107 // activity
108 if (fnActivated && pNetClient)
109 pNetClient->SetLastActivity(Game.FrameCounter);
110}
111
112void C4Client::SetLobbyReady(bool lobbyReady)
113{
114 // Change state
115 Core.SetLobbyReady(lobbyReady);
116}
117
118void C4Client::SetLocal()
119{
120 // set flag
121 fLocal = true;
122}
123
124void C4Client::Remove()
125{
126 // remove players for this client
127 Game.Players.RemoveAtClient(iClient: getID(), fDisconnect: true);
128}
129
130void C4Client::CompileFunc(StdCompiler *pComp)
131{
132 pComp->Value(rStruct&: Core);
133 // Assume this is a non-local client
134 if (pComp->isCompiler())
135 fLocal = false;
136}
137
138// C4ClientList
139
140C4ClientList::C4ClientList()
141 : pFirst(nullptr), pLocal(nullptr), pNetClients(nullptr) {}
142
143C4ClientList::~C4ClientList()
144{
145 Clear();
146}
147
148void C4ClientList::Clear()
149{
150 ClearNetwork();
151 while (pFirst)
152 {
153 C4Client *pClient = pFirst;
154 Remove(pClient, fTemporary: true);
155 delete pClient;
156 }
157}
158
159void C4ClientList::Add(C4Client *pClient)
160{
161 // local client?
162 if (pClient->isLocal()) pLocal = pClient;
163 // already in list?
164 assert(!getClientByID(pClient->getID()));
165 // find insert position
166 C4Client *pPos = pFirst, *pPrev = nullptr;
167 for (; pPos; pPrev = pPos, pPos = pPos->pNext)
168 if (pPos->getID() > pClient->getID())
169 break;
170 // add to list
171 pClient->pNext = pPos;
172 (pPrev ? pPrev->pNext : pFirst) = pClient;
173 // register to network
174 if (pNetClients)
175 pClient->pNetClient = pNetClients->RegClient(pClient);
176}
177
178C4Client *C4ClientList::getClientByID(int32_t iID) const
179{
180 for (C4Client *pClient = pFirst; pClient; pClient = pClient->pNext)
181 if (pClient->getID() == iID)
182 return pClient;
183 return nullptr;
184}
185
186C4Client *C4ClientList::getClientByName(const char *szName) const
187{
188 for (C4Client *pClient = pFirst; pClient; pClient = pClient->pNext)
189 if (SEqual(szStr1: pClient->getName(), szStr2: szName))
190 return pClient;
191 return nullptr;
192}
193
194int32_t C4ClientList::getClientCnt() const
195{
196 int32_t iCnt = 0;
197 for (C4Client *pClient = pFirst; pClient; pClient = pClient->pNext)
198 iCnt++;
199 return iCnt;
200}
201
202void C4ClientList::Init(int32_t iLocalClientID)
203{
204 Clear();
205 // Add local client (activated, not observing)
206 AddLocal(iID: iLocalClientID, fActivated: true, fObserver: false);
207}
208
209void C4ClientList::InitNetwork(C4Network2ClientList *pnNetClients)
210{
211 // clear list before
212 pnNetClients->Clear();
213 // set
214 pNetClients = pnNetClients;
215 // copy client list, create links
216 for (C4Client *pClient = pFirst; pClient; pClient = pClient->pNext)
217 pClient->pNetClient = pNetClients->RegClient(pClient);
218}
219
220void C4ClientList::ClearNetwork()
221{
222 // clear the list (should remove links)
223 C4Network2ClientList *pOldNetClients = pNetClients;
224 pNetClients = nullptr;
225 if (pOldNetClients) pOldNetClients->Clear();
226}
227
228bool C4ClientList::Remove(C4Client *pClient, bool fTemporary)
229{
230 // first client in list?
231 if (pClient == pFirst)
232 pFirst = pClient->pNext;
233 else
234 {
235 // find previous
236 C4Client *pPrev;
237 for (pPrev = pFirst; pPrev && pPrev->pNext != pClient; pPrev = pPrev->pNext);
238 // not found?
239 if (!pPrev) return false;
240 // unlink
241 pPrev->pNext = pClient->pNext;
242 }
243 if (pClient == pLocal) pLocal = nullptr;
244 if (!fTemporary)
245 {
246 pClient->Remove();
247 // remove network client
248 if (pNetClients && pClient->getNetClient())
249 pNetClients->DeleteClient(pClient: pClient->getNetClient());
250 }
251 // done
252 return true;
253}
254
255C4Client *C4ClientList::Add(const C4ClientCore &Core)
256{
257 // client with same ID in list?
258 if (getClientByID(iID: Core.getID()))
259 {
260 spdlog::error(msg: "ClientList: Duplicated client ID!"); return nullptr;
261 }
262 // create, add
263 C4Client *pClient = new C4Client(Core);
264 Add(pClient);
265 return pClient;
266}
267
268C4Client *C4ClientList::AddLocal(int32_t iID, bool fActivated, bool fObserver)
269{
270 // only one local client allowed
271 assert(!pLocal);
272 // create core
273 C4ClientCore LocalCore;
274 LocalCore.SetLocal(inID: iID, fnActivated: fActivated, fnObserver: fObserver);
275 // create client
276 C4Client *pClient = new C4Client(LocalCore);
277 pClient->SetLocal();
278 // add
279 Add(pClient);
280 assert(pLocal);
281 return pClient;
282}
283
284void C4ClientList::SetLocalID(int32_t iID)
285{
286 if (!pLocal) return;
287 pLocal->SetID(iID);
288 // resort local client
289 C4Client *pSavedLocal = pLocal;
290 Remove(pClient: pLocal, fTemporary: true); Add(pClient: pSavedLocal);
291}
292
293void C4ClientList::CtrlRemove(const C4Client *pClient, const char *szReason)
294{
295 // host only
296 if (!pLocal || !pLocal->isHost()) return;
297 // Net client? flag
298 if (pClient->getNetClient())
299 pClient->getNetClient()->SetStatus(NCS_Remove);
300 // add control
301 Game.Control.DoInput(eCtrlType: CID_ClientRemove,
302 pPkt: new C4ControlClientRemove(pClient->getID(), szReason),
303 eDelivery: CDT_Sync);
304}
305
306void C4ClientList::RemoveRemote()
307{
308 // remove all remote clients
309 for (C4Client *pClient = pFirst, *pNext; pClient; pClient = pNext)
310 {
311 pNext = pClient->pNext;
312 // remove all clients except the local client
313 if (!pClient->isLocal())
314 {
315 Remove(pClient);
316 delete pClient;
317 }
318 }
319}
320
321C4ClientList &C4ClientList::operator=(const C4ClientList &List)
322{
323 // remove clients that are not in the list
324 C4Client *pClient, *pNext;
325 for (pClient = pFirst; pClient; pClient = pNext)
326 {
327 pNext = pClient->pNext;
328 if (!List.getClientByID(iID: pClient->getID()))
329 {
330 Remove(pClient);
331 delete pClient;
332 }
333 }
334 // add all clients
335 for (pClient = List.pFirst; pClient; pClient = pClient->pNext)
336 {
337 // already in list?
338 C4Client *pNewClient = getClientByID(iID: pClient->getID());
339 if (pNewClient)
340 {
341 // core change
342 pNewClient->SetCore(pClient->getCore());
343 }
344 else
345 {
346 // add
347 pNewClient = Add(Core: pClient->getCore());
348 }
349 }
350 return *this;
351}
352
353void C4ClientList::CompileFunc(StdCompiler *pComp)
354{
355 // Clear existing data
356 bool fCompiler = pComp->isCompiler();
357 if (fCompiler) Clear();
358 // Client count
359 uint32_t iClientCnt = getClientCnt();
360 pComp->Value(rStruct: mkNamingCountAdapt(iCount&: iClientCnt, szName: "Client"));
361 // Compile all clients
362 if (pComp->isCompiler())
363 for (uint32_t i = 0; i < iClientCnt; i++)
364 {
365 C4Client *pClient = new C4Client();
366 pComp->Value(rStruct: mkNamingAdapt(rValue&: *pClient, szName: "Client"));
367 Add(pClient);
368 }
369 else
370 for (C4Client *pClient = pFirst; pClient; pClient = pClient->pNext)
371 pComp->Value(rStruct: mkNamingAdapt(rValue&: *pClient, szName: "Client"));
372}
373