1/*
2 * LegacyClonk
3 *
4 * Copyright (c) RedWolf Design
5 * Copyright (c) 2017-2020, 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/* control management */
18
19#include "C4Include.h"
20
21#include <C4Application.h>
22#include <C4Game.h>
23#include <C4GameControl.h>
24#include <C4GameOverDlg.h>
25#include <C4Record.h>
26#include <C4Log.h>
27#include <C4Network2Stats.h>
28
29#include <cassert>
30
31// *** C4GameControl
32
33C4GameControl::C4GameControl()
34 : Network(this)
35{
36 Default();
37}
38
39C4GameControl::~C4GameControl()
40{
41 Clear();
42}
43
44void C4GameControl::InitLogger()
45{
46 logger = Application.LogSystem.CreateLogger(config&: Config.Logging.GameControl);
47}
48
49bool C4GameControl::InitLocal(C4Client *pLocal)
50{
51 eMode = CM_Local; fPreInit = fInitComplete = true;
52 fHost = true; iClientID = pLocal->getID();
53 ControlRate = 1;
54 // ok
55 return true;
56}
57
58bool C4GameControl::InitNetwork(C4Client *pLocal)
59{
60 // network should already be initialized (by C4Network2)
61 if (!Network.IsEnabled())
62 return false;
63 // set mode
64 eMode = CM_Network; fPreInit = fInitComplete = true;
65 fHost = pLocal->isHost(); iClientID = pLocal->getID();
66 // control rate by parameters
67 ControlRate = Game.Parameters.ControlRate;
68 // ok
69 return true;
70}
71
72bool C4GameControl::InitReplay(C4Group &rGroup)
73{
74 // open replay
75 pPlayback = new C4Playback(Application.LogSystem.CreateLogger(config&: Config.Logging.Playback));
76 if (!pPlayback->Open(rGrp&: rGroup))
77 {
78 LogFatal(id: C4ResStrTableKey::IDS_ERR_REPLAYREAD);
79 delete pPlayback; pPlayback = nullptr;
80 return false;
81 }
82 // set mode
83 eMode = CM_Replay; fInitComplete = true;
84 fHost = false; iClientID = C4ClientIDUnknown;
85 // control rate by parameters
86 ControlRate = Game.Parameters.ControlRate;
87 // just in case
88 StopRecord();
89 // ok
90 return true;
91}
92
93void C4GameControl::ChangeToLocal()
94{
95 // changes from any given mode to local
96 // (emergency - think of network disconnect)
97
98 // remove all non-local clients
99 Game.Clients.RemoveRemote();
100 // activate local client
101 if (Game.Clients.getLocal())
102 Game.Clients.getLocal()->SetActivated(true);
103
104 // network: clear network
105 if (eMode == CM_Network)
106 {
107 Network.Clear();
108 if (Game.Network.isEnabled())
109 Game.Network.Clear();
110 }
111 // replay: close playback
112 else if (eMode == CM_Replay)
113 {
114 delete pPlayback; pPlayback = nullptr;
115 }
116
117 // we're now managing our own player info list; make sure counter works
118 Game.PlayerInfos.FixIDCounter();
119
120 // start the game, if we're not in the game over dialog
121 // (otherwise, clients start game when host disconnected!)
122 if (!C4GameOverDlg::IsShown()) Game.HaltCount = 0;
123
124 // set status
125 eMode = CM_Local; fHost = true;
126 ControlRate = 1;
127}
128
129void C4GameControl::OnGameSynchronizing()
130{
131 // start record if desired
132 if (fRecordNeeded)
133 {
134 fRecordNeeded = false;
135 StartRecord(fInitial: false, fStreaming: false);
136 }
137}
138
139bool C4GameControl::StartRecord(bool fInitial, bool fStreaming)
140{
141 assert(fInitComplete);
142 // already recording?
143 if (pRecord) StopRecord();
144 // start
145 pRecord = new C4Record();
146 if (!pRecord->Start(fInitial))
147 {
148 delete pRecord; pRecord = nullptr;
149 return false;
150 }
151 // streaming
152 if (fStreaming)
153 {
154 if (!pRecord->StartStreaming(fInitial) ||
155 !Game.Network.StartStreaming(pRecord))
156 {
157 delete pRecord; pRecord = nullptr;
158 return false;
159 }
160 }
161 // runtime records executed through queue: Must record initial control
162 if (pExecutingControl)
163 pRecord->Rec(Ctrl: *pExecutingControl, iFrame: Game.FrameCounter);
164 // ok
165 return true;
166}
167
168void C4GameControl::StopRecord(StdStrBuf *pRecordName, uint8_t *pRecordSHA1)
169{
170 if (pRecord)
171 {
172 Game.Network.FinishStreaming();
173 pRecord->Stop(pRecordName, pRecordSHA1);
174 }
175 // just delete
176 delete pRecord; pRecord = nullptr;
177}
178
179void C4GameControl::RequestRuntimeRecord()
180{
181 if (!IsRuntimeRecordPossible()) return; // cannot record
182 fRecordNeeded = true;
183 // request through a synchronize-call
184 // currnetly do not request, but start record with next gamesync, so network runtime join can be debugged
185#ifndef DEBUGREC
186 Game.Control.DoInput(eCtrlType: CID_Synchronize, pPkt: new C4ControlSynchronize(false, true), eDelivery: CDT_Queue);
187#endif
188}
189
190bool C4GameControl::IsRuntimeRecordPossible() const
191{
192 // already requested?
193 if (fRecordNeeded) return false;
194 // not from replay
195 if (isReplay()) return false;
196 // not if recording already
197 if (isRecord()) return false;
198 // Record OK
199 return true;
200}
201
202bool C4GameControl::RecAddFile(const char *szLocalFilename, const char *szAddAs)
203{
204 if (!isRecord() || !pRecord) return false;
205 return pRecord->AddFile(szLocalFilename, szAddAs);
206}
207
208void C4GameControl::Clear()
209{
210 StopRecord();
211 ChangeToLocal();
212 Default();
213}
214
215void C4GameControl::Default()
216{
217 Input.Clear();
218 Network.Clear();
219 eMode = CM_None;
220 fHost = fPreInit = fInitComplete = false;
221 iClientID = C4ClientIDUnknown;
222 pRecord = nullptr;
223 pPlayback = nullptr;
224 SyncChecks.Clear();
225 ControlRate = BoundBy<int>(bval: Config.Network.ControlRate, lbound: 1, rbound: C4MaxControlRate);
226 ControlTick = 0;
227 SyncRate = C4SyncCheckRate;
228 DoSync = false;
229 fRecordNeeded = false;
230 pExecutingControl = nullptr;
231}
232
233bool C4GameControl::Prepare()
234{
235 assert(fInitComplete);
236
237 // Prepare control, return true if everything is ready for GameGo.
238
239 switch (eMode)
240 {
241 // full steam ahead
242 case CM_Local: case CM_Replay:
243 return true;
244
245 case CM_Network:
246 Network.Execute();
247
248 // deactivated and got control: request activate
249 if (Input.firstPkt() && !Game.Clients.getLocal()->isActivated())
250 Game.Network.RequestActivate();
251
252 // needs input?
253 while (Network.CtrlNeeded(iTick: Game.FrameCounter))
254 {
255 Network.DoInput(Input);
256 Input.Clear();
257 }
258
259 // control tick?
260 if (Game.FrameCounter % ControlRate)
261 return true;
262
263 // check GameGo
264 return Network.CtrlReady(iTick: ControlTick);
265
266 case CM_None:
267 assert(!"Unhandled switch case");
268 }
269
270 return false;
271}
272
273void C4GameControl::Execute()
274{
275 // Execute all available control
276
277 assert(fInitComplete);
278
279 // control tick? replay must always be executed.
280 if (!isReplay() && Game.FrameCounter % ControlRate)
281 return;
282
283 // Get control
284 C4Control Control;
285 if (eMode == CM_Local)
286 {
287 // control = input
288 Control.Take(Ctrl&: Input);
289 }
290 if (eMode == CM_Network)
291 {
292 // control = network input
293 if (!Network.GetControl(pCtrl: &Control, iTick: ControlTick))
294 {
295 LogFatalNTr(message: "Network: could not retrieve control from C4GameControlNetwork!");
296 return;
297 }
298 }
299 if (eMode == CM_Replay)
300 {
301 if (!pPlayback) { ChangeToLocal(); return; }
302 // control = replay data
303 pPlayback->ExecuteControl(pCtrl: &Control, iFrame: Game.FrameCounter);
304 }
305
306 // Record: Save ctrl
307 if (pRecord)
308 pRecord->Rec(Ctrl: Control, iFrame: Game.FrameCounter);
309
310 // debug: recheck PreExecute
311 assert(Control.PreExecute(logger));
312
313 // execute
314 pExecutingControl = &Control;
315 Control.Execute(logger);
316 Control.Clear();
317 pExecutingControl = nullptr;
318
319 // statistics record
320 if (Game.pNetworkStatistics) Game.pNetworkStatistics->ExecuteControlFrame();
321}
322
323void C4GameControl::Ticks()
324{
325 assert(fInitComplete);
326
327 if (!(Game.FrameCounter % ControlRate))
328 ControlTick++;
329 if (!(Game.FrameCounter % SyncRate))
330 DoSync = true;
331
332 // calc next tick without waiting for timer? (catchup cases)
333 if (eMode == CM_Network)
334 if (Network.CtrlOverflow(iTick: ControlTick))
335 {
336 Game.GameGo = true;
337 if (Network.GetBehind(iTick: ControlTick) >= 25)
338 if (Game.FrameCounter % ((Network.GetBehind(iTick: ControlTick) + 15) / 20))
339 Game.DoSkipFrame = true;
340 }
341}
342
343bool C4GameControl::CtrlTickReached(int32_t iTick)
344{
345 // 1. control tick reached?
346 if (ControlTick < iTick) return false;
347 // 2. control tick?
348 if (Game.FrameCounter % ControlRate) return false;
349 // ok then
350 return true;
351}
352
353int32_t C4GameControl::getCtrlTick(int32_t iFrame) const
354{
355 // returns control tick by frame. Note this is a guess, as the control rate
356 // can always change, so don't rely on the return value too much.
357
358 return iFrame / ControlRate + ControlTick - Game.FrameCounter / ControlRate;
359}
360
361int32_t C4GameControl::getNextControlTick() const
362{
363 return ControlTick + (Game.FrameCounter % ControlRate ? 1 : 0);
364}
365
366void C4GameControl::AdjustControlRate(int32_t iBy)
367{
368 // control host only
369 if (isCtrlHost())
370 Game.Control.DoInput(eCtrlType: CID_Set, pPkt: new C4ControlSet(C4CVT_ControlRate, iBy), eDelivery: CDT_Decide);
371}
372
373void C4GameControl::SetActivated(bool fnActivated)
374{
375 fActivated = fnActivated;
376 if (eMode == CM_Network)
377 Network.SetActivated(fnActivated);
378}
379
380void C4GameControl::DoInput(C4PacketType eCtrlType, C4ControlPacket *pPkt, C4ControlDeliveryType eDelivery)
381{
382 assert(fPreInit);
383
384 // check if the control can be executed
385 if (eDelivery == CDT_Direct || eDelivery == CDT_Private)
386 assert(!pPkt->Sync());
387 if (!fInitComplete)
388 assert(pPkt->Lobby());
389
390 // decide control type
391 if (eDelivery == CDT_Decide)
392 eDelivery = DecideControlDelivery();
393
394 // queue?
395 if (eDelivery == CDT_Queue)
396 {
397 // add, will be executed/sent later
398 Input.Add(eType: eCtrlType, pCtrl: pPkt);
399 return;
400 }
401
402 // Network?
403 if (isNetwork())
404 {
405 Network.DoInput(eCtrlType, pPkt, eType: eDelivery);
406 }
407 else
408 {
409 // Local mode: execute at once
410 ExecControlPacket(eCtrlType, pPkt);
411 delete pPkt;
412 }
413}
414
415void C4GameControl::DbgRec(C4RecordChunkType eType, const uint8_t *pData, size_t iSize)
416{
417#ifdef DEBUGREC
418 if (DoNoDebugRec > 0) return;
419 // record data
420 if (pRecord)
421 pRecord->Rec(Game.FrameCounter,
422 DecompileToBuf<StdCompilerBinWrite>(C4PktDebugRec(eType, StdBuf(pData, iSize))),
423 eType);
424 // check against playback
425 if (pPlayback)
426 pPlayback->Check(eType, pData, iSize);
427#endif // DEBUGREC
428}
429
430C4ControlDeliveryType C4GameControl::DecideControlDelivery()
431{
432 // network
433 if (eMode == CM_Network)
434 return Network.DecideControlDelivery();
435 // use direct
436 return CDT_Direct;
437}
438
439void C4GameControl::DoSyncCheck()
440{
441 // only once
442 if (!DoSync) return;
443 DoSync = false;
444 // create sync check
445 C4ControlSyncCheck *pSyncCheck = new C4ControlSyncCheck();
446 pSyncCheck->Set();
447 // host?
448 if (fHost)
449 // add sync check to control queue or send it directly if the queue isn't active
450 DoInput(eCtrlType: CID_SyncCheck, pPkt: pSyncCheck, eDelivery: fActivated ? CDT_Queue : CDT_Direct);
451 else
452 {
453 // already have sync check?
454 C4ControlSyncCheck *pSyncCheck2 = GetSyncCheck(iTick: Game.FrameCounter);
455 if (!pSyncCheck2)
456 // add to sync check array
457 SyncChecks.Add(eType: CID_SyncCheck, pCtrl: pSyncCheck);
458 else
459 {
460 // check
461 pSyncCheck->Execute(logger);
462 delete pSyncCheck;
463 }
464 }
465 // remove old
466 RemoveOldSyncChecks();
467}
468
469void C4GameControl::ExecControl(const C4Control &rCtrl)
470{
471 // nothing to do?
472 if (!rCtrl.firstPkt()) return;
473 // execute it
474 if (!rCtrl.PreExecute(logger)) logger->error(msg: "PreExecute failed for sync control!");
475 rCtrl.Execute(logger);
476 // record
477 if (pRecord)
478 pRecord->Rec(Ctrl: rCtrl, iFrame: Game.FrameCounter);
479}
480
481void C4GameControl::ExecControlPacket(C4PacketType eCtrlType, C4ControlPacket *pPkt)
482{
483 // execute it
484 if (!pPkt->PreExecute(logger)) logger->error(msg: "PreExecute failed for direct control!");
485 pPkt->Execute(logger);
486 // record it
487 if (pRecord)
488 pRecord->Rec(eCtrlType, pCtrl: pPkt, iFrame: Game.FrameCounter);
489}
490
491C4ControlSyncCheck *C4GameControl::GetSyncCheck(int32_t iTick)
492{
493 for (C4IDPacket *pPkt = SyncChecks.firstPkt(); pPkt; pPkt = SyncChecks.nextPkt(pPkt))
494 {
495 // should be a sync check
496 if (pPkt->getPktType() != CID_SyncCheck) continue;
497 // get sync check
498 C4ControlSyncCheck *pCheck = static_cast<C4ControlSyncCheck *>(pPkt->getPkt());
499 // packet that's searched for?
500 if (pCheck->getFrame() == iTick)
501 return pCheck;
502 }
503 return nullptr;
504}
505
506void C4GameControl::RemoveOldSyncChecks()
507{
508 C4IDPacket *pNext;
509 for (C4IDPacket *pPkt = SyncChecks.firstPkt(); pPkt; pPkt = pNext)
510 {
511 pNext = SyncChecks.nextPkt(pPkt);
512 // should be a sync check
513 if (pPkt->getPktType() != CID_SyncCheck) continue;
514 // remove?
515 C4ControlSyncCheck *pCheck = static_cast<C4ControlSyncCheck *>(pPkt->getPkt());
516 if (pCheck->getFrame() < Game.FrameCounter - C4SyncCheckMaxKeep)
517 SyncChecks.Delete(pPkt);
518 }
519}
520