/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/ * Copyright (c) 2009-2016, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ /* control management */ #include "C4Include.h" #include "game/C4Application.h" #include "game/C4Game.h" #include "control/C4GameControl.h" #include "gui/C4GameOverDlg.h" #include "control/C4Record.h" #include "lib/C4Log.h" #include "network/C4Network2Stats.h" #include "gui/C4MouseControl.h" #include "platform/C4GamePadCon.h" #include "player/C4PlayerList.h" #include "player/C4Player.h" #ifdef _MSC_VER #pragma warning (disable: 4355) #endif // *** C4GameControl C4GameControl::C4GameControl() : Network(this) { Default(); } C4GameControl::~C4GameControl() { Clear(); } bool C4GameControl::InitLocal(C4Client *pLocal) { eMode = CM_Local; fInitComplete = true; fHost = true; iClientID = pLocal->getID(); ControlRate = 1; // ok return true; } bool C4GameControl::InitNetwork(C4Client *pLocal) { // network should already be initialized (by C4Network2) if (!Network.IsEnabled()) return false; // set mode eMode = CM_Network; fInitComplete = true; fHost = pLocal->isHost(); iClientID = pLocal->getID(); // control rate by parameters ControlRate = Game.Parameters.ControlRate; // ok return true; } bool C4GameControl::InitReplay(C4Group &rGroup) { // open replay pPlayback = new C4Playback(); if (!pPlayback->Open(rGroup)) { LogFatal(LoadResStr("IDS_ERR_REPLAYREAD")); delete pPlayback; pPlayback = NULL; return false; } // set mode eMode = CM_Replay; fInitComplete = true; fHost = false; iClientID = C4ClientIDUnknown; // control rate by parameters ControlRate = Game.Parameters.ControlRate; // just in case StopRecord(); // ok return true; } void C4GameControl::ChangeToLocal() { // changes from any given mode to local // (emergency - think of network disconnect) // remove all non-local clients Game.Clients.RemoveRemote(); // activate local client if (Game.Clients.getLocal()) Game.Clients.getLocal()->SetActivated(true); // network: clear network if (eMode == CM_Network) { Network.Clear(); if (::Network.isEnabled()) ::Network.Clear(); } // replay: close playback else if (eMode == CM_Replay) { delete pPlayback; pPlayback = NULL; } // we're now managing our own player info list; make sure counter works Game.PlayerInfos.FixIDCounter(); // start the game, if we're not in the game over dialog // (otherwise, clients start game when host disconnected!) if (!C4GameOverDlg::IsShown()) Game.HaltCount = 0; // set status eMode = CM_Local; fHost = true; ControlRate = 1; } void C4GameControl::OnGameSynchronizing() { // start record if desired if (fRecordNeeded) { fRecordNeeded = false; StartRecord(false, false); } } bool C4GameControl::StartRecord(bool fInitial, bool fStreaming) { assert(fInitComplete); // already recording? if (pRecord) StopRecord(); // start pRecord = new C4Record(); if (!pRecord->Start(fInitial)) { delete pRecord; pRecord = NULL; return false; } // streaming if (fStreaming) { if (!pRecord->StartStreaming(fInitial) || !::Network.StartStreaming(pRecord)) { delete pRecord; pRecord = NULL; return false; } } // runtime records executed through queue: Must record initial control if (pExecutingControl) pRecord->Rec(*pExecutingControl, Game.FrameCounter); // ok return true; } void C4GameControl::StopRecord(StdStrBuf *pRecordName, BYTE *pRecordSHA1) { if (pRecord) { ::Network.FinishStreaming(); pRecord->Stop(pRecordName, pRecordSHA1); // just delete delete pRecord; pRecord = NULL; } } void C4GameControl::RequestRuntimeRecord() { if (!IsRuntimeRecordPossible()) return; // cannot record fRecordNeeded = true; // request through a synchronize-call // currnetly do not request, but start record with next gamesync, so network runtime join can be debugged if (Config.General.DebugRec) ::Control.DoInput(CID_Synchronize, new C4ControlSynchronize(false, true), CDT_Queue); } bool C4GameControl::IsRuntimeRecordPossible() const { // already requested? if (fRecordNeeded) return false; // not from replay if (isReplay()) return false; // not if recording already if (isRecord()) return false; // Record OK return true; } bool C4GameControl::RecAddFile(const char *szLocalFilename, const char *szAddAs) { if (!isRecord() || !pRecord) return false; return pRecord->AddFile(szLocalFilename, szAddAs); } void C4GameControl::Clear() { StopRecord(); ChangeToLocal(); Default(); } void C4GameControl::Default() { Input.Clear(); Network.Clear(); eMode = CM_None; fHost = fInitComplete = false; iClientID = C4ClientIDUnknown; pRecord = NULL; pPlayback = NULL; SyncChecks.Clear(); ControlRate = Clamp(Config.Network.ControlRate, 1, C4MaxControlRate); ControlTick = 0; SyncRate = C4SyncCheckRate; DoSync = false; fRecordNeeded = false; pExecutingControl = NULL; } bool C4GameControl::Prepare() { assert(fInitComplete); // Prepare control, return true if everything is ready for GameGo. bool is_input_prepared = false; switch (eMode) { // full steam ahead case CM_Local: case CM_Replay: return true; case CM_Network: Network.Execute(); // deactivated and got control: request activate if (Input.firstPkt() && !Game.Clients.getLocal()->isActivated()) ::Network.RequestActivate(); // needs input? while (Network.CtrlNeeded(Game.FrameCounter)) { if (!is_input_prepared && Game.Clients.getLocal()->isActivated()) { // add per-controlframe input PrepareInput(); is_input_prepared = true; } Network.DoInput(Input); Input.Clear(); } // control tick? if (Game.FrameCounter % ControlRate) return true; // check GameGo return Network.CtrlReady(ControlTick); default: return false; } } void C4GameControl::Execute() { // Execute all available control assert(fInitComplete); // control tick? replay must always be executed. if (!isReplay() && Game.FrameCounter % ControlRate) return; // Get control C4Control Control; if (eMode == CM_Local) { // control = input PrepareInput(); // add per-controlframe input Control.Take(Input); } if (eMode == CM_Network) { // control = network input if (!Network.GetControl(&Control, ControlTick)) { LogFatal("Network: could not retrieve control from C4GameControlNetwork!"); return; } } if (eMode == CM_Replay) { if (!pPlayback) { ChangeToLocal(); return; } // control = replay data pPlayback->ExecuteControl(&Control, Game.FrameCounter); } // Record: Save ctrl if (pRecord) pRecord->Rec(Control, Game.FrameCounter); // debug: recheck PreExecute assert(Control.PreExecute()); // execute pExecutingControl = &Control; Control.Execute(); Control.Clear(); pExecutingControl = NULL; // statistics record if (Game.pNetworkStatistics) Game.pNetworkStatistics->ExecuteControlFrame(); } void C4GameControl::Ticks() { assert(fInitComplete); if (!(Game.FrameCounter % ControlRate)) ControlTick++; if (!(Game.FrameCounter % SyncRate)) DoSync = true; // calc next tick without waiting for timer? (catchup cases) if (eMode == CM_Network) if (Network.CtrlOverflow(ControlTick)) { Game.GameGo = true; if (Network.GetBehind(ControlTick) >= 25) if (Game.FrameCounter % ((Network.GetBehind(ControlTick) + 15) / 20)) Game.DoSkipFrame = true; } } bool C4GameControl::CtrlTickReached(int32_t iTick) { // 1. control tick reached? if (ControlTick < iTick) return false; // 2. control tick? if (Game.FrameCounter % ControlRate) return false; // ok then return true; } int32_t C4GameControl::getCtrlTick(int32_t iFrame) const { // returns control tick by frame. Note this is a guess, as the control rate // can always change, so don't rely on the return value too much. return iFrame / ControlRate + ControlTick - Game.FrameCounter / ControlRate; } int32_t C4GameControl::getNextControlTick() const { return ControlTick + (Game.FrameCounter % ControlRate ? 1 : 0); } void C4GameControl::AdjustControlRate(int32_t iBy) { // control host only if (isCtrlHost()) ::Control.DoInput(CID_Set, new C4ControlSet(C4CVT_ControlRate, iBy), CDT_Decide); } void C4GameControl::SetActivated(bool fnActivated) { fActivated = fnActivated; if (eMode == CM_Network) Network.SetActivated(fnActivated); } void C4GameControl::DoInput(C4PacketType eCtrlType, C4ControlPacket *pPkt, C4ControlDeliveryType eDelivery) { assert(fInitComplete || pPkt->Lobby()); // check if the control can be executed if (eDelivery == CDT_Direct || eDelivery == CDT_Private) assert(!pPkt->Sync()); // decide control type if (eDelivery == CDT_Decide) eDelivery = DecideControlDelivery(); // queue? if (eDelivery == CDT_Queue) { // add, will be executed/sent later Input.Add(eCtrlType, pPkt); return; } // Network? if (isNetwork()) { Network.DoInput(eCtrlType, pPkt, eDelivery); } else { // Local mode: execute at once ExecControlPacket(eCtrlType, pPkt); delete pPkt; } } void C4GameControl::DbgRec(C4RecordChunkType eType, const uint8_t *pData, size_t iSize) { if (Config.General.DebugRec) { if (DoNoDebugRec>0) return; // record data if (pRecord) { C4PktDebugRec dr(eType, StdBuf(pData, iSize)); pRecord->Rec(Game.FrameCounter, DecompileToBuf(dr), eType); } // check against playback if (pPlayback) pPlayback->Check(eType, pData, iSize); } } C4ControlDeliveryType C4GameControl::DecideControlDelivery() { // network if (eMode == CM_Network) return Network.DecideControlDelivery(); // use direct return CDT_Direct; } void C4GameControl::DoSyncCheck() { // only once if (!DoSync) return; DoSync = false; // create sync check C4ControlSyncCheck *pSyncCheck = new C4ControlSyncCheck(); pSyncCheck->Set(); // host? if (fHost) // add sync check to control queue or send it directly if the queue isn't active DoInput(CID_SyncCheck, pSyncCheck, fActivated ? CDT_Queue : CDT_Direct); else { // already have sync check? C4ControlSyncCheck* pSyncCheck2 = GetSyncCheck(Game.FrameCounter); if (!pSyncCheck2) // add to sync check array SyncChecks.Add(CID_SyncCheck, pSyncCheck); else { // check pSyncCheck->Execute(); delete pSyncCheck; } } // remove old RemoveOldSyncChecks(); } void C4GameControl::ExecControl(const C4Control &rCtrl) { // nothing to do? if (!rCtrl.firstPkt()) return; // execute it if (!rCtrl.PreExecute()) Log("Control: PreExecute failed for sync control!"); rCtrl.Execute(); // record if (pRecord) pRecord->Rec(rCtrl, Game.FrameCounter); } void C4GameControl::ExecControlPacket(C4PacketType eCtrlType, C4ControlPacket *pPkt) { // execute it if (!pPkt->PreExecute()) Log("Control: PreExecute failed for direct control!"); pPkt->Execute(); // record it if (pRecord) pRecord->Rec(eCtrlType, pPkt, Game.FrameCounter); } C4ControlSyncCheck *C4GameControl::GetSyncCheck(int32_t iTick) { for (C4IDPacket *pPkt = SyncChecks.firstPkt(); pPkt; pPkt = SyncChecks.nextPkt(pPkt)) { // should be a sync check if (pPkt->getPktType() != CID_SyncCheck) continue; // get sync check C4ControlSyncCheck *pCheck = static_cast(pPkt->getPkt()); // packet that's searched for? if (pCheck->getFrame() == iTick) return pCheck; } return NULL; } void C4GameControl::RemoveOldSyncChecks() { C4IDPacket *pNext; for (C4IDPacket *pPkt = SyncChecks.firstPkt(); pPkt; pPkt = pNext) { pNext = SyncChecks.nextPkt(pPkt); // should be a sync check if (pPkt->getPktType() != CID_SyncCheck) continue; // remove? C4ControlSyncCheck *pCheck = static_cast(pPkt->getPkt()); if (pCheck->getFrame() < Game.FrameCounter - C4SyncCheckMaxKeep) SyncChecks.Delete(pPkt); } } void C4GameControl::PrepareInput() { // add per-controlframe input ::MouseControl.DoMoveInput(); if (Application.pGamePadControl) Application.pGamePadControl->DoAxisInput(); // per-player input C4Player *plr; int32_t i=0; while ((plr = ::Players.GetLocalByIndex(i++))) plr->Control.PrepareInput(); } C4GameControl Control;