openclonk/src/control/C4GameControl.cpp

538 lines
12 KiB
C++
Raw Normal View History

2009-05-08 13:28:41 +00:00
/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
2016-04-03 18:18:29 +00:00
* Copyright (c) 2009-2016, The OpenClonk Team and contributors
2009-05-08 13:28:41 +00:00
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
2009-05-08 13:28:41 +00:00
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
2009-05-08 13:28:41 +00:00
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
2009-05-08 13:28:41 +00:00
*/
/* 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"
2009-05-08 13:28:41 +00:00
#ifdef _MSC_VER
#pragma warning (disable: 4355)
#endif
// *** C4GameControl
C4GameControl::C4GameControl()
2010-03-28 18:58:01 +00:00
: Network(this)
2009-05-08 13:28:41 +00:00
{
Default();
2009-05-08 13:28:41 +00:00
}
C4GameControl::~C4GameControl()
{
Clear();
}
bool C4GameControl::InitLocal(C4Client *pLocal)
{
eMode = CM_Local; fInitComplete = true;
2009-05-08 13:28:41 +00:00
fHost = true; iClientID = pLocal->getID();
ControlRate = 1;
// ok
return true;
}
bool C4GameControl::InitNetwork(C4Client *pLocal)
{
// network should already be initialized (by C4Network2)
2010-03-28 18:58:01 +00:00
if (!Network.IsEnabled())
2009-05-08 13:28:41 +00:00
return false;
// set mode
eMode = CM_Network; fInitComplete = true;
2009-05-08 13:28:41 +00:00
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();
2010-03-28 18:58:01 +00:00
if (!pPlayback->Open(rGroup))
2009-05-08 13:28:41 +00:00
{
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
2010-03-28 18:58:01 +00:00
if (Game.Clients.getLocal())
2009-05-08 13:28:41 +00:00
Game.Clients.getLocal()->SetActivated(true);
// network: clear network
2010-03-28 18:58:01 +00:00
if (eMode == CM_Network)
{
2009-05-08 13:28:41 +00:00
Network.Clear();
2010-03-28 18:58:01 +00:00
if (::Network.isEnabled())
2009-06-05 15:19:46 +00:00
::Network.Clear();
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
// replay: close playback
2010-03-28 18:58:01 +00:00
else if (eMode == CM_Replay)
2009-05-08 13:28:41 +00:00
{ 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
2009-05-08 13:28:41 +00:00
eMode = CM_Local; fHost = true;
ControlRate = 1;
}
void C4GameControl::OnGameSynchronizing()
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// start record if desired
if (fRecordNeeded)
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
fRecordNeeded = false;
StartRecord(false, false);
}
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
bool C4GameControl::StartRecord(bool fInitial, bool fStreaming)
{
assert(fInitComplete);
2009-05-08 13:28:41 +00:00
// already recording?
2010-03-28 18:58:01 +00:00
if (pRecord) StopRecord();
2009-05-08 13:28:41 +00:00
// start
pRecord = new C4Record();
2010-03-28 18:58:01 +00:00
if (!pRecord->Start(fInitial))
2009-05-08 13:28:41 +00:00
{
delete pRecord; pRecord = NULL;
return false;
}
// streaming
2010-03-28 18:58:01 +00:00
if (fStreaming)
2009-05-08 13:28:41 +00:00
{
2010-03-28 18:58:01 +00:00
if (!pRecord->StartStreaming(fInitial) ||
!::Network.StartStreaming(pRecord))
2009-05-08 13:28:41 +00:00
{
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)
{
2010-03-28 18:58:01 +00:00
if (pRecord)
2009-05-08 13:28:41 +00:00
{
2009-06-05 15:19:46 +00:00
::Network.FinishStreaming();
pRecord->Stop(pRecordName, pRecordSHA1);
2009-05-08 13:28:41 +00:00
// just delete
delete pRecord; pRecord = NULL;
}
}
void C4GameControl::RequestRuntimeRecord()
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
if (!IsRuntimeRecordPossible()) return; // cannot record
fRecordNeeded = true;
2009-05-08 13:28:41 +00:00
// 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);
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
bool C4GameControl::IsRuntimeRecordPossible() const
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// 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;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
bool C4GameControl::RecAddFile(const char *szLocalFilename, const char *szAddAs)
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
if (!isRecord() || !pRecord) return false;
return pRecord->AddFile(szLocalFilename, szAddAs);
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
void C4GameControl::Clear()
{
StopRecord();
ChangeToLocal();
Default();
2009-05-08 13:28:41 +00:00
}
void C4GameControl::Default()
{
Input.Clear();
Network.Clear();
2009-05-08 13:28:41 +00:00
eMode = CM_None;
fHost = fInitComplete = false;
2009-05-08 13:28:41 +00:00
iClientID = C4ClientIDUnknown;
pRecord = NULL;
pPlayback = NULL;
SyncChecks.Clear();
ControlRate = Clamp<int>(Config.Network.ControlRate, 1, C4MaxControlRate);
ControlTick = 0;
SyncRate = C4SyncCheckRate;
DoSync = false;
2009-05-08 13:28:41 +00:00
fRecordNeeded = false;
pExecutingControl = NULL;
}
bool C4GameControl::Prepare()
{
assert(fInitComplete);
2009-05-08 13:28:41 +00:00
// Prepare control, return true if everything is ready for GameGo.
bool is_input_prepared = false;
2009-05-08 13:28:41 +00:00
2010-03-28 18:58:01 +00:00
switch (eMode)
2009-05-08 13:28:41 +00:00
{
2010-03-28 18:58:01 +00:00
// full steam ahead
2009-05-08 13:28:41 +00:00
case CM_Local: case CM_Replay:
return true;
case CM_Network:
Network.Execute();
// deactivated and got control: request activate
2010-03-28 18:58:01 +00:00
if (Input.firstPkt() && !Game.Clients.getLocal()->isActivated())
::Network.RequestActivate();
2009-05-08 13:28:41 +00:00
// needs input?
2010-03-28 18:58:01 +00:00
while (Network.CtrlNeeded(Game.FrameCounter))
2009-05-08 13:28:41 +00:00
{
if (!is_input_prepared && Game.Clients.getLocal()->isActivated())
{
// add per-controlframe input
PrepareInput();
is_input_prepared = true;
}
2009-05-08 13:28:41 +00:00
Network.DoInput(Input);
Input.Clear();
}
// control tick?
2010-03-28 18:58:01 +00:00
if (Game.FrameCounter % ControlRate)
2009-05-08 13:28:41 +00:00
return true;
// check GameGo
return Network.CtrlReady(ControlTick);
2010-01-25 04:00:59 +00:00
default:
return false;
2009-05-08 13:28:41 +00:00
}
}
void C4GameControl::Execute()
{
// Execute all available control
assert(fInitComplete);
2009-05-08 13:28:41 +00:00
// control tick? replay must always be executed.
2010-03-28 18:58:01 +00:00
if (!isReplay() && Game.FrameCounter % ControlRate)
2009-05-08 13:28:41 +00:00
return;
// Get control
C4Control Control;
2010-03-28 18:58:01 +00:00
if (eMode == CM_Local)
2009-05-08 13:28:41 +00:00
{
// control = input
PrepareInput(); // add per-controlframe input
2009-05-08 13:28:41 +00:00
Control.Take(Input);
}
2010-03-28 18:58:01 +00:00
if (eMode == CM_Network)
2009-05-08 13:28:41 +00:00
{
// control = network input
2010-03-28 18:58:01 +00:00
if (!Network.GetControl(&Control, ControlTick))
2009-05-08 13:28:41 +00:00
{
LogFatal("Network: could not retrieve control from C4GameControlNetwork!");
return;
}
}
2010-03-28 18:58:01 +00:00
if (eMode == CM_Replay)
2009-05-08 13:28:41 +00:00
{
2010-03-28 18:58:01 +00:00
if (!pPlayback) { ChangeToLocal(); return; }
2009-05-08 13:28:41 +00:00
// control = replay data
pPlayback->ExecuteControl(&Control, Game.FrameCounter);
}
// Record: Save ctrl
2010-03-28 18:58:01 +00:00
if (pRecord)
2009-05-08 13:28:41 +00:00
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);
2009-05-08 13:28:41 +00:00
2010-03-28 18:58:01 +00:00
if (!(Game.FrameCounter % ControlRate))
2009-05-08 13:28:41 +00:00
ControlTick++;
2010-03-28 18:58:01 +00:00
if (!(Game.FrameCounter % SyncRate))
2009-05-08 13:28:41 +00:00
DoSync = true;
// calc next tick without waiting for timer? (catchup cases)
2010-03-28 18:58:01 +00:00
if (eMode == CM_Network)
if (Network.CtrlOverflow(ControlTick))
2009-05-08 13:28:41 +00:00
{
Game.GameGo = true;
2010-03-28 18:58:01 +00:00
if (Network.GetBehind(ControlTick) >= 25)
if (Game.FrameCounter % ((Network.GetBehind(ControlTick) + 15) / 20))
2009-05-08 13:28:41 +00:00
Game.DoSkipFrame = true;
}
}
bool C4GameControl::CtrlTickReached(int32_t iTick)
{
// 1. control tick reached?
2010-03-28 18:58:01 +00:00
if (ControlTick < iTick) return false;
2009-05-08 13:28:41 +00:00
// 2. control tick?
2010-03-28 18:58:01 +00:00
if (Game.FrameCounter % ControlRate) return false;
// ok then
return true;
2009-05-08 13:28:41 +00:00
}
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
2010-03-28 18:58:01 +00:00
if (isCtrlHost())
2009-06-15 22:06:37 +00:00
::Control.DoInput(CID_Set, new C4ControlSet(C4CVT_ControlRate, iBy), CDT_Decide);
2009-05-08 13:28:41 +00:00
}
void C4GameControl::SetActivated(bool fnActivated)
{
fActivated = fnActivated;
2010-03-28 18:58:01 +00:00
if (eMode == CM_Network)
2009-05-08 13:28:41 +00:00
Network.SetActivated(fnActivated);
}
void C4GameControl::DoInput(C4PacketType eCtrlType, C4ControlPacket *pPkt, C4ControlDeliveryType eDelivery)
{
assert(fInitComplete || pPkt->Lobby());
2009-05-08 13:28:41 +00:00
// check if the control can be executed
2010-03-28 18:58:01 +00:00
if (eDelivery == CDT_Direct || eDelivery == CDT_Private)
2009-05-08 13:28:41 +00:00
assert(!pPkt->Sync());
// decide control type
2010-03-28 18:58:01 +00:00
if (eDelivery == CDT_Decide)
2009-05-08 13:28:41 +00:00
eDelivery = DecideControlDelivery();
// queue?
2010-03-28 18:58:01 +00:00
if (eDelivery == CDT_Queue)
2009-05-08 13:28:41 +00:00
{
// add, will be executed/sent later
Input.Add(eCtrlType, pPkt);
return;
}
// Network?
2010-03-28 18:58:01 +00:00
if (isNetwork())
2009-05-08 13:28:41 +00:00
{
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)
2009-08-25 15:50:51 +00:00
{
if (DoNoDebugRec>0) return;
// record data
if (pRecord)
{
C4PktDebugRec dr(eType, StdBuf(pData, iSize));
pRecord->Rec(Game.FrameCounter, DecompileToBuf<StdCompilerBinWrite>(dr), eType);
}
// check against playback
if (pPlayback)
pPlayback->Check(eType, pData, iSize);
2009-08-25 15:50:51 +00:00
}
2009-05-08 13:28:41 +00:00
}
C4ControlDeliveryType C4GameControl::DecideControlDelivery()
{
// network
2010-03-28 18:58:01 +00:00
if (eMode == CM_Network)
2009-05-08 13:28:41 +00:00
return Network.DecideControlDelivery();
// use direct
return CDT_Direct;
}
void C4GameControl::DoSyncCheck()
{
// only once
2010-03-28 18:58:01 +00:00
if (!DoSync) return;
DoSync = false;
2009-05-08 13:28:41 +00:00
// create sync check
C4ControlSyncCheck *pSyncCheck = new C4ControlSyncCheck();
pSyncCheck->Set();
// host?
2010-03-28 18:58:01 +00:00
if (fHost)
2009-05-08 13:28:41 +00:00
// 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
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// already have sync check?
C4ControlSyncCheck* pSyncCheck2 = GetSyncCheck(Game.FrameCounter);
2010-03-28 18:58:01 +00:00
if (!pSyncCheck2)
2009-05-08 13:28:41 +00:00
// add to sync check array
SyncChecks.Add(CID_SyncCheck, pSyncCheck);
else
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// check
pSyncCheck->Execute();
delete pSyncCheck;
}
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
// remove old
RemoveOldSyncChecks();
}
void C4GameControl::ExecControl(const C4Control &rCtrl)
{
// nothing to do?
2010-03-28 18:58:01 +00:00
if (!rCtrl.firstPkt()) return;
// execute it
2010-03-28 18:58:01 +00:00
if (!rCtrl.PreExecute()) Log("Control: PreExecute failed for sync control!");
rCtrl.Execute();
// record
2010-03-28 18:58:01 +00:00
if (pRecord)
pRecord->Rec(rCtrl, Game.FrameCounter);
2009-05-08 13:28:41 +00:00
}
void C4GameControl::ExecControlPacket(C4PacketType eCtrlType, C4ControlPacket *pPkt)
{
// execute it
2010-03-28 18:58:01 +00:00
if (!pPkt->PreExecute()) Log("Control: PreExecute failed for direct control!");
2009-05-08 13:28:41 +00:00
pPkt->Execute();
// record it
2010-03-28 18:58:01 +00:00
if (pRecord)
2009-05-08 13:28:41 +00:00
pRecord->Rec(eCtrlType, pPkt, Game.FrameCounter);
}
C4ControlSyncCheck *C4GameControl::GetSyncCheck(int32_t iTick)
{
2010-03-28 18:58:01 +00:00
for (C4IDPacket *pPkt = SyncChecks.firstPkt(); pPkt; pPkt = SyncChecks.nextPkt(pPkt))
2009-05-08 13:28:41 +00:00
{
// should be a sync check
2010-03-28 18:58:01 +00:00
if (pPkt->getPktType() != CID_SyncCheck) continue;
2009-05-08 13:28:41 +00:00
// get sync check
C4ControlSyncCheck *pCheck = static_cast<C4ControlSyncCheck *>(pPkt->getPkt());
// packet that's searched for?
2010-03-28 18:58:01 +00:00
if (pCheck->getFrame() == iTick)
2009-05-08 13:28:41 +00:00
return pCheck;
}
return NULL;
}
void C4GameControl::RemoveOldSyncChecks()
{
C4IDPacket *pNext;
2010-03-28 18:58:01 +00:00
for (C4IDPacket *pPkt = SyncChecks.firstPkt(); pPkt; pPkt = pNext)
2009-05-08 13:28:41 +00:00
{
pNext = SyncChecks.nextPkt(pPkt);
// should be a sync check
2010-03-28 18:58:01 +00:00
if (pPkt->getPktType() != CID_SyncCheck) continue;
2009-05-08 13:28:41 +00:00
// remove?
C4ControlSyncCheck *pCheck = static_cast<C4ControlSyncCheck *>(pPkt->getPkt());
2010-03-28 18:58:01 +00:00
if (pCheck->getFrame() < Game.FrameCounter - C4SyncCheckMaxKeep)
2009-05-08 13:28:41 +00:00
SyncChecks.Delete(pPkt);
}
}
2009-06-15 22:06:37 +00:00
void C4GameControl::PrepareInput()
{
// add per-controlframe input
::MouseControl.DoMoveInput();
if (Application.pGamePadControl) Application.pGamePadControl->DoAxisInput();
// per-player input
C4Player *plr; int32_t i=0;
2011-03-28 18:58:42 +00:00
while ((plr = ::Players.GetLocalByIndex(i++)))
plr->Control.PrepareInput();
}
2009-06-15 22:06:37 +00:00
C4GameControl Control;