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