forked from Mirrors/openclonk
538 lines
12 KiB
C++
538 lines
12 KiB
C++
/*
|
|
* 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<int>(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<StdCompilerBinWrite>(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<C4ControlSyncCheck *>(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<C4ControlSyncCheck *>(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;
|