1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2017, The OpenClonk Team and contributors
6 * Copyright (c) 2017-2022, 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#pragma once
19
20#include "C4NetIO.h"
21
22#include "C4Application.h"
23#include "C4Network2Client.h"
24#include "C4Network2Res.h"
25#include "C4Network2IO.h"
26#include "C4Network2Players.h"
27#include "C4GameParameters.h"
28
29#include "C4PlayerInfo.h"
30#include "C4Teams.h"
31#include "C4Control.h"
32#include "C4Gui.h"
33#include "C4GuiDialogs.h"
34
35#ifndef USE_CONSOLE
36#include "C4ToastEventHandler.h"
37#endif
38
39#include <cstdint>
40#include <optional>
41
42// lobby predef - no need to include lobby in header just for the class ptr
43namespace C4GameLobby { class MainDlg; class Countdown; }
44class C4PacketJoinData;
45class C4Record;
46
47// standard ports
48const std::uint16_t C4NetStdPortTCP = 11112,
49 C4NetStdPortUDP = 11113,
50 C4NetStdPortDiscovery = 11114,
51 C4NetStdPortRefServer = 11111,
52 C4NetStdPortPuncher = 11115;
53
54// ressource retrieve wait timeout
55const int C4NetResRetrieveTimeout = 100000; // (ms)
56
57// client (de)activation
58const int C4NetActivationReqInterval = 5000, // (ms)
59 C4NetMaxBehind4Activation = 20, // (ticks)
60 C4NetDeactivationDelay = 500; // (ticks)
61
62// client chase
63const unsigned int C4NetChaseTargetUpdateInterval = 5; // (s)
64
65// reference
66const unsigned int C4NetReferenceUpdateInterval = 120; // (s)
67const unsigned int C4NetMinLeagueUpdateInterval = 10; // (s)
68
69// voting
70const unsigned int C4NetVotingTimeout = 10; // (s)
71const unsigned int C4NetMinVotingInterval = 120; // (s)
72
73// streaming
74const int C4NetStreamingMinBlockSize = 10 * 1024;
75const int C4NetStreamingMaxBlockSize = 20 * 1024;
76const int C4NetStreamingInterval = 30; // (s)
77
78enum C4NetGameState
79{
80 GS_None, // network not active
81 GS_Init, // connecting to host, waiting for join data
82 GS_Lobby, // lobby mode
83 GS_Pause, // game paused
84 GS_Go, // game running
85};
86
87class C4Network2Status : public C4PacketBase
88{
89public:
90 C4Network2Status();
91
92protected:
93 C4NetGameState eState;
94 int32_t iCtrlMode;
95 int32_t iTargetCtrlTick;
96
97public:
98 C4NetGameState getState() const { return eState; }
99 int32_t getCtrlMode() const { return iCtrlMode; }
100 int32_t getTargetCtrlTick() const { return iTargetCtrlTick; }
101 const char *getStateName() const;
102 const char *getDescription() const;
103
104 bool isEnabled() const { return eState != GS_None; }
105 bool isLobbyActive() const { return eState == GS_Lobby; }
106 bool isPastLobby() const { return eState > GS_Lobby; }
107 bool isPaused() const { return eState == GS_Pause; }
108 bool isRunning() const { return eState == GS_Go; }
109
110 void Set(C4NetGameState eState, int32_t iTargetCtrlTick);
111 void SetCtrlMode(int32_t iCtrlMode);
112 void SetTargetTick(int32_t iTargetCtrlTick);
113 void Clear();
114
115 void CompileFunc(StdCompiler *pComp, bool fReference);
116 virtual void CompileFunc(StdCompiler *pComp) override;
117};
118
119class C4Network2
120{
121 friend class C4Network2IO;
122 friend class C4Sec1TimerCallback<C4Network2>;
123
124#ifndef USE_CONSOLE
125 class ReadyCheckDialog final : public C4GUI::TimedDialog, private C4ToastEventHandler
126 {
127 public:
128 ReadyCheckDialog();
129 ~ReadyCheckDialog() override = default;
130
131 public:
132 void UpdateText() override;
133
134 protected:
135 void OnClosed(bool ok) override;
136 const char *GetID() override { return "ReadyCheckDialog"; }
137
138 private:
139 void Activated() override;
140 void OnAction(std::string_view action) override;
141
142 private:
143 std::unique_ptr<class C4Toast> toast;
144 };
145#endif
146
147public:
148 C4Network2();
149 virtual ~C4Network2() { Clear(); }
150
151public:
152 // network i/o class
153 C4Network2IO NetIO;
154
155 // ressource list
156 C4Network2ResList ResList;
157
158 // client list
159 C4Network2ClientList Clients;
160
161 // player list
162 C4Network2Players Players;
163
164 // game status
165 C4Network2Status Status;
166
167 // logger
168 std::shared_ptr<spdlog::logger> Logger;
169
170protected:
171 // role
172 bool fHost;
173
174 // options
175 bool fAllowJoin;
176
177 // join resource
178 C4Network2ResCore ResDynamic;
179
180 // ressources
181 int32_t iDynamicTick;
182 bool fDynamicNeeded;
183
184 // game status flags
185 bool fStatusAck, fStatusReached;
186 bool fChasing;
187
188 // control
189 class C4GameControlNetwork *pControl;
190
191 // lobby
192 C4GameLobby::MainDlg *pLobby;
193 bool fLobbyRunning;
194 C4GameLobby::Countdown *pLobbyCountdown;
195#ifndef USE_CONSOLE
196 ReadyCheckDialog *readyCheckDialog;
197#endif
198
199 // master server used
200 StdStrBuf MasterServerAddress;
201
202 // timer
203 C4Sec1TimerCallback<C4Network2> *pSec1Timer;
204
205 // clients
206 int32_t iNextClientID;
207
208 // chase
209 time_t iLastChaseTargetUpdate;
210
211 // activation
212 time_t iLastActivateRequest;
213
214 // reference
215 time_t iLastReferenceUpdate;
216 time_t iLastLeagueUpdate, iLeagueUpdateDelay;
217 bool fLeagueEndSent;
218
219 // league
220 class C4LeagueClient *pLeagueClient;
221
222 // game password
223 StdStrBuf sPassword;
224
225 // connection failed because password needed?
226 bool fWrongPassword;
227
228 // delayed activation request?
229 bool fDelayedActivateReq;
230
231 // voting
232 C4Control Votes;
233 class C4VoteDialog *pVoteDialog;
234 bool fPausedForVote;
235 time_t iVoteStartTime, iLastOwnVoting;
236
237 // streaming
238 bool fStreaming;
239 time_t iLastStreamAttempt;
240 C4Record *pStreamedRecord;
241 StdBuf StreamingBuf;
242 z_stream StreamCompressor;
243
244 class C4Network2HTTPClient *pStreamer;
245 unsigned int iCurrentStreamAmount, iCurrentStreamPosition;
246
247 // Puncher
248 C4NetpuncherID NetpuncherGameID;
249 StdStrBuf NetpuncherAddr;
250
251public:
252 // data access
253 bool isEnabled() const { return Status.isEnabled(); }
254 bool isLobbyActive() const { return Status.isLobbyActive(); }
255 bool isPastLobby() const { return Status.isPastLobby(); }
256 bool isRunning() const { return Status.isRunning() && isStatusAck(); }
257 bool isPaused() const { return Status.isPaused() && isStatusAck(); }
258 bool isHost() const { return fHost; }
259 bool isStatusAck() const { return fStatusAck; }
260 bool isFrozen() const;
261
262 bool isJoinAllowed() const { return fAllowJoin; }
263
264 class C4GameLobby::MainDlg *GetLobby() const { return pLobby; } // lobby publication
265 const char *GetPassword() const { return sPassword.getData(); } // Oh noez, now the password is public!
266 bool isPassworded() const { return !sPassword.isNull(); }
267
268 // initialization result type
269 enum InitResult
270 {
271 IR_Success, IR_Error, IR_Fatal,
272 };
273
274 // initialization
275 bool InitHost(bool fLobby);
276 InitResult InitClient(const class C4Network2Reference &Ref, bool fObserver);
277 InitResult InitClient(const std::vector<class C4Network2Address> &addrs, const class C4ClientCore &HostCore, const char *szPassword = nullptr);
278 bool DoLobby();
279 bool Start();
280 bool Pause();
281 bool Sync();
282 bool FinalInit();
283
284 bool RetrieveScenario(char *szScenario);
285 C4Network2Res::Ref RetrieveRes(const C4Network2ResCore &Core, int32_t iTimeout, const char *szResName, bool fWaitForCore = false);
286
287 // idle processes
288 void OnSec1Timer();
289 void Execute();
290
291 // termination
292 void Clear();
293
294 // some options
295 bool ToggleAllowJoin();
296 bool ToggleClientListDlg();
297 void AllowJoin(bool fAllow);
298 void SetCtrlMode(int32_t iCtrlMode);
299 void SetPassword(const char *szToPassword);
300 StdStrBuf QueryClientPassword(); // ask client for a password; empty if user canceled
301
302 // interface for C4Network2IO
303 void OnConn(C4Network2IOConnection *pConn);
304 void OnDisconn(C4Network2IOConnection *pConn);
305 void HandlePacket(char cStatus, const C4PacketBase *pBasePkt, C4Network2IOConnection *pConn);
306 void HandleLobbyPacket(char cStatus, const C4PacketBase *pBasePkt, C4Network2IOConnection *pConn);
307 bool HandlePuncherPacket(C4NetpuncherPacket::uptr, C4Network2HostAddress::AddressFamily family);
308 void OnPuncherConnect(C4NetIO::addr_t addr);
309
310 // runtime join stuff
311 void OnGameSynchronized();
312
313 // status
314 void DrawStatus(C4FacetEx &cgo);
315
316 // client activation
317 void RequestActivate();
318 void DeactivateInactiveClients(); // host
319
320 // league
321 void LeagueGameEvaluate(const char *szRecordName = nullptr, const uint8_t *pRecordSHA = nullptr);
322 void LeagueSignupDisable(); // if "internet game" button is switched off in lobby: Remove from league server
323 bool LeagueSignupEnable(); // if "internet game" button is switched on in lobby: (re)Add to league server
324 void InvalidateReference(); // forces a recreation and re-send of the game reference in the next execution cycle
325 bool LeaguePlrAuth(C4PlayerInfo *pInfo); // client: get authentication for a player from the league server
326 bool LeaguePlrAuthCheck(C4PlayerInfo *pInfo); // host: check AUID of player info with league server
327 void LeagueNotifyDisconnect(int32_t iClientID, enum C4LeagueDisconnectReason eReason);
328 void LeagueWaitNotBusy(); // block until league serveris no longer busy. Process update reply if last message was an update
329 void LeagueSurrender(); // forfeit in league - just fake a disconnect
330 void LeagueShowError(const char *szMsg); // show league error msg box in fullscreen; just log in console
331
332 // voting
333 void Vote(C4ControlVoteType eType, bool fApprove = true, int32_t iData = 0);
334 void AddVote(const C4ControlVote &Vote);
335 C4IDPacket *GetVote(int32_t iClientID, C4ControlVoteType eType, int32_t iData);
336 void EndVote(C4ControlVoteType eType, bool fApprove, int32_t iData);
337 void OpenVoteDialog();
338 void OpenSurrenderDialog(C4ControlVoteType eType, int32_t iData);
339 void OnVoteDialogClosed();
340
341 // lobby countdown
342 void StartLobbyCountdown(int32_t iCountdownTime);
343 void AbortLobbyCountdown();
344
345 // streaming
346 size_t getPendingStreamData() const { return StreamingBuf.getSize() - StreamCompressor.avail_out; }
347 bool isStreaming() const;
348 bool StartStreaming(C4Record *pRecord);
349 bool FinishStreaming();
350 bool StopStreaming();
351
352 // Netpuncher
353 C4NetpuncherID::value &getNetpuncherGameID(C4Network2HostAddress::AddressFamily family);
354 C4NetpuncherID getNetpuncherGameID() const { return NetpuncherGameID; }
355 StdStrBuf getNetpuncherAddr() const { return NetpuncherAddr; }
356
357protected:
358 // net i/o initialization
359 bool InitNetIO(bool fNoClientID, bool fHost);
360
361 // handling of own packets
362 void HandleConn(const class C4PacketConn &Pkt, C4Network2IOConnection *pConn, C4Network2Client *pClient);
363 bool CheckConn(const C4ClientCore &CCore, C4Network2IOConnection *pConn, C4Network2Client *pClient, const char *&szReply);
364 bool HostConnect(const C4ClientCore &CCore, C4Network2IOConnection *pConn, const char *&szReply);
365 bool Join(C4ClientCore &CCore, C4Network2IOConnection *pConn, const char *&szReply);
366 void HandleConnRe(const class C4PacketConnRe &Pkt, C4Network2IOConnection *pConn, C4Network2Client *pClient);
367 void HandleStatus(const C4Network2Status &nStatus);
368 void HandleStatusAck(const C4Network2Status &nStatus, C4Network2Client *pClient);
369 void HandleActivateReq(int32_t iTick, C4Network2Client *pClient);
370 void HandleJoinData(const class C4PacketJoinData &rPkt);
371 void HandleReadyCheck(const class C4PacketReadyCheck &packet);
372
373 // events
374 void OnConnect(C4Network2Client *pClient, C4Network2IOConnection *pConn, const char *szMsg, bool fFirstConnection);
375 void OnConnectFail(C4Network2IOConnection *pConn);
376 void OnDisconnect(C4Network2Client *pClient, C4Network2IOConnection *pConn);
377 void OnClientConnect(C4Network2Client *pClient, C4Network2IOConnection *pConn);
378 void OnClientDisconnect(C4Network2Client *pClient);
379
380 void SendJoinData(C4Network2Client *pClient);
381
382 // ressource list
383 bool CreateDynamic(bool fInit);
384 void RemoveDynamic();
385
386 // status changes
387 bool ChangeGameStatus(C4NetGameState enState, int32_t iTargetCtrlTick, int32_t iCtrlMode = -1);
388 void CheckStatusReached(bool fFromFinalInit = false);
389 void CheckStatusAck();
390 void OnStatusReached();
391 void OnStatusAck();
392
393 // chase
394 void UpdateChaseTarget();
395
396 // league
397 bool InitLeague(bool *pCancel);
398 void DeinitLeague();
399 bool LeagueStart(bool *pCancel);
400 bool LeagueUpdate();
401 bool LeagueUpdateProcessReply();
402 bool LeagueEnd(const char *szRecordName = nullptr, const uint8_t *pRecordSHA = nullptr);
403
404 // streaming
405 bool StreamIn(bool fFinish);
406 bool StreamOut();
407
408 // Puncher
409 void InitPuncher();
410};
411
412class C4VoteDialog : public C4GUI::MessageDialog
413{
414public:
415 C4VoteDialog(const char *szText, C4ControlVoteType eVoteType, int32_t iVoteData, bool fSurrender);
416
417private:
418 C4ControlVoteType eVoteType;
419 int32_t iVoteData;
420 bool fSurrender;
421
422public:
423 C4ControlVoteType getVoteType() const { return eVoteType; }
424 int32_t getVoteData() const { return iVoteData; }
425
426private:
427 virtual bool OnEnter() override { UserClose(fOK: false); return true; } // the vote dialog defaults to "No" [MarkFra]
428 virtual void OnClosed(bool fOK) override;
429
430 // true for dialogs that receive full keyboard and mouse input even in shared mode
431 virtual bool IsExclusiveDialog() override { return true; }
432};
433
434// Packets
435
436class C4PacketJoinData : public C4PacketBase
437{
438public:
439 C4PacketJoinData() {}
440
441protected:
442 // the client ID
443 int32_t iClientID;
444
445 // Dynamic data to boot
446 C4Network2ResCore Dynamic;
447
448 // network status
449 C4Network2Status GameStatus;
450
451 // control tick
452 int32_t iStartCtrlTick;
453
454public:
455 // the game parameters
456 C4GameParameters Parameters;
457
458public:
459 const int32_t &getClientID() const { return iClientID; }
460 const C4Network2ResCore &getDynamicCore() const { return Dynamic; }
461 const C4Network2Status &getStatus() const { return GameStatus; }
462 int32_t getStartCtrlTick() const { return iStartCtrlTick; }
463
464 void SetClientID(int32_t inClientID) { iClientID = inClientID; }
465 void SetGameStatus(const C4Network2Status &Status) { GameStatus = Status; }
466 void SetDynamicCore(const C4Network2ResCore &Core) { Dynamic = Core; }
467 void SetStartCtrlTick(int32_t iTick) { iStartCtrlTick = iTick; }
468
469 virtual void CompileFunc(StdCompiler *pComp) override;
470};
471
472template<>
473struct C4LoggerConfig::Name<C4Network2>
474{
475 static constexpr auto Value = "Network";
476};
477
478// shows ready check dialog or sends back information
479
480class C4PacketReadyCheck : public C4PacketBase
481{
482public:
483 enum Data : int32_t
484 {
485 Request = -1,
486 NotReady = 0,
487 Ready = 1
488 };
489
490 C4PacketReadyCheck() = default;
491 C4PacketReadyCheck(int32_t client, Data data) : client{client}, data{data} {}
492 void CompileFunc(StdCompiler *comp) override;
493
494 int32_t GetClientID() const { return client; }
495 auto GetData() const { return static_cast<Data>(data); }
496 bool VoteRequested() const { return data == Request; }
497 bool IsReady() const { return data == Ready; }
498
499private:
500 int32_t client;
501 std::underlying_type_t<Data> data;
502};
503
504class C4PacketActivateReq : public C4PacketBase
505{
506public:
507 C4PacketActivateReq(int32_t iTick = -1) : iTick(iTick) {}
508
509protected:
510 int32_t iTick;
511
512public:
513 int32_t getTick() const { return iTick; }
514
515 virtual void CompileFunc(StdCompiler *pComp) override;
516};
517