| 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 | |
| 32 | C4ClientCore::C4ClientCore() |
| 33 | : iID(-1), |
| 34 | fActivated(false), |
| 35 | fObserver(false), |
| 36 | fLobbyReady(false) |
| 37 | { |
| 38 | Name.Ref(pnData: "" ); Nick.Ref(pnData: "" ); |
| 39 | } |
| 40 | |
| 41 | C4ClientCore::~C4ClientCore() {} |
| 42 | |
| 43 | void 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 | |
| 58 | int32_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 | |
| 75 | void 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 | |
| 87 | C4Client::C4Client() |
| 88 | : C4Client{C4ClientCore()} {} |
| 89 | |
| 90 | C4Client::C4Client(const C4ClientCore &Core) |
| 91 | : Core{Core}, fLocal{false}, pNetClient{nullptr}, pNext{nullptr}, last_lobby_ready_change{0}, muted{Config.Sound.MuteSoundCommand} {} |
| 92 | |
| 93 | C4Client::~C4Client() |
| 94 | { |
| 95 | // network client bind must be removed before |
| 96 | assert(!pNetClient); |
| 97 | } |
| 98 | |
| 99 | bool C4Client::TryAllowSound() |
| 100 | { |
| 101 | return Config.Cooldowns.SoundCommand.TryReset(); |
| 102 | } |
| 103 | |
| 104 | void C4Client::SetActivated(bool fnActivated) |
| 105 | { |
| 106 | Core.SetActivated(fnActivated); |
| 107 | // activity |
| 108 | if (fnActivated && pNetClient) |
| 109 | pNetClient->SetLastActivity(Game.FrameCounter); |
| 110 | } |
| 111 | |
| 112 | void C4Client::SetLobbyReady(bool lobbyReady) |
| 113 | { |
| 114 | // Change state |
| 115 | Core.SetLobbyReady(lobbyReady); |
| 116 | } |
| 117 | |
| 118 | void C4Client::SetLocal() |
| 119 | { |
| 120 | // set flag |
| 121 | fLocal = true; |
| 122 | } |
| 123 | |
| 124 | void C4Client::Remove() |
| 125 | { |
| 126 | // remove players for this client |
| 127 | Game.Players.RemoveAtClient(iClient: getID(), fDisconnect: true); |
| 128 | } |
| 129 | |
| 130 | void 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 | |
| 140 | C4ClientList::C4ClientList() |
| 141 | : pFirst(nullptr), pLocal(nullptr), pNetClients(nullptr) {} |
| 142 | |
| 143 | C4ClientList::~C4ClientList() |
| 144 | { |
| 145 | Clear(); |
| 146 | } |
| 147 | |
| 148 | void C4ClientList::Clear() |
| 149 | { |
| 150 | ClearNetwork(); |
| 151 | while (pFirst) |
| 152 | { |
| 153 | C4Client *pClient = pFirst; |
| 154 | Remove(pClient, fTemporary: true); |
| 155 | delete pClient; |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | void 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 | |
| 178 | C4Client *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 | |
| 186 | C4Client *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 | |
| 194 | int32_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 | |
| 202 | void 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 | |
| 209 | void 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 | |
| 220 | void C4ClientList::ClearNetwork() |
| 221 | { |
| 222 | // clear the list (should remove links) |
| 223 | C4Network2ClientList *pOldNetClients = pNetClients; |
| 224 | pNetClients = nullptr; |
| 225 | if (pOldNetClients) pOldNetClients->Clear(); |
| 226 | } |
| 227 | |
| 228 | bool 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 | |
| 255 | C4Client *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 | |
| 268 | C4Client *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 | |
| 284 | void 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 | |
| 293 | void 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 | |
| 306 | void 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 | |
| 321 | C4ClientList &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 | |
| 353 | void 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 | |