openclonk/src/network/C4Network2.cpp

2911 lines
84 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/
* Copyright (c) 2009-2013, 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
*/
2009-11-25 18:38:54 +00:00
2009-05-08 13:28:41 +00:00
#include <C4Include.h>
#include <C4Network2.h>
#include <C4Version.h>
2009-05-08 13:28:41 +00:00
#include <C4Log.h>
#include <C4Application.h>
#include <C4Console.h>
#include <C4GameSave.h>
#include <C4RoundResults.h>
#include <C4Game.h>
#include <C4GraphicsSystem.h>
#include <C4GraphicsResource.h>
2009-06-15 22:06:37 +00:00
#include <C4GameControl.h>
2009-05-08 13:28:41 +00:00
// lobby
#include <C4Gui.h>
#include <C4GameLobby.h>
#include <C4Network2Dialogs.h>
#include <C4League.h>
#ifdef _WIN32
#include <direct.h>
#endif
#ifndef HAVE_WINSOCK
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
// compile options
#ifdef _MSC_VER
#pragma warning (disable: 4355)
#endif
// *** C4Network2Status
C4Network2Status::C4Network2Status()
2010-03-28 18:58:01 +00:00
: eState(GS_None), iTargetCtrlTick(-1)
2009-05-08 13:28:41 +00:00
{
}
const char *C4Network2Status::getStateName() const
{
2010-03-28 18:58:01 +00:00
switch (eState)
2009-05-08 13:28:41 +00:00
{
case GS_None: return "none";
case GS_Init: return "init";
case GS_Lobby: return "lobby";
case GS_Pause: return "pause";
case GS_Go: return "go";
}
return "???";
}
const char *C4Network2Status::getDescription() const
{
2010-03-28 18:58:01 +00:00
switch (eState)
2009-05-08 13:28:41 +00:00
{
case GS_None: return LoadResStr("IDS_DESC_NOTINITED");
case GS_Init: return LoadResStr("IDS_DESC_WAITFORHOST");
case GS_Lobby: return LoadResStr("IDS_DESC_EXPECTING");
case GS_Pause: return LoadResStr("IDS_DESC_GAMEPAUSED");
case GS_Go: return LoadResStr("IDS_DESC_GAMERUNNING");
}
return LoadResStr("IDS_DESC_UNKNOWNGAMESTATE");
}
void C4Network2Status::Set(C4NetGameState enState, int32_t inTargetTick)
{
eState = enState; iTargetCtrlTick = inTargetTick;
}
void C4Network2Status::SetCtrlMode(int32_t inCtrlMode)
{
iCtrlMode = inCtrlMode;
}
void C4Network2Status::SetTargetTick(int32_t inTargetCtrlTick)
{
iTargetCtrlTick = inTargetCtrlTick;
}
void C4Network2Status::Clear()
{
eState = GS_None; iTargetCtrlTick = -1;
}
void C4Network2Status::CompileFunc(StdCompiler *pComp)
{
CompileFunc(pComp, false);
}
void C4Network2Status::CompileFunc(StdCompiler *pComp, bool fReference)
{
StdEnumEntry<C4NetGameState> GameStates[] =
{
2010-03-28 18:58:01 +00:00
{ "None", GS_None },
{ "Init", GS_Init },
{ "Lobby", GS_Lobby },
{ "Paused", GS_Pause },
{ "Running", GS_Go },
2009-05-08 13:28:41 +00:00
};
pComp->Value(mkNamingAdapt(mkEnumAdaptT<uint8_t>(eState, GameStates), "State", GS_None));
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iCtrlMode), "CtrlMode", -1));
2009-05-08 13:28:41 +00:00
2010-03-28 18:58:01 +00:00
if (!fReference)
2009-05-08 13:28:41 +00:00
pComp->Value(mkNamingAdapt(mkIntPackAdapt(iTargetCtrlTick), "TargetTick", -1));
}
// *** C4Network2
C4Network2::C4Network2()
2010-03-28 18:58:01 +00:00
: Clients(&NetIO),
2009-05-08 13:28:41 +00:00
fAllowJoin(false),
iDynamicTick(-1), fDynamicNeeded(false),
fStatusAck(false), fStatusReached(false),
fChasing(false),
pControl(NULL),
2010-01-25 04:00:59 +00:00
pLobby(NULL), fLobbyRunning(false), pLobbyCountdown(NULL),
2009-05-08 13:28:41 +00:00
iNextClientID(0),
iLastChaseTargetUpdate(0),
tLastActivateRequest(C4TimeMilliseconds::NegativeInfinity),
2010-01-25 04:00:59 +00:00
iLastReferenceUpdate(0),
iLastLeagueUpdate(0),
pLeagueClient(NULL),
2009-05-08 13:28:41 +00:00
fDelayedActivateReq(false),
pVoteDialog(NULL),
fPausedForVote(false),
iLastOwnVoting(0),
fStreaming(false)
2009-05-08 13:28:41 +00:00
{
}
C4Network2::~C4Network2()
{
Clear();
}
bool C4Network2::InitHost(bool fLobby)
{
2010-03-28 18:58:01 +00:00
if (isEnabled()) Clear();
2009-05-08 13:28:41 +00:00
// initialize everything
2009-06-15 22:06:37 +00:00
Status.Set(fLobby ? GS_Lobby : GS_Go, ::Control.ControlTick);
2009-05-08 13:28:41 +00:00
Status.SetCtrlMode(Config.Network.ControlMode);
fHost = true;
fStatusAck = fStatusReached = true;
fChasing = false;
fAllowJoin = false;
iNextClientID = C4ClientIDStart;
// initialize client list
Clients.Init(&Game.Clients, true);
// initialize resource list
2010-03-28 18:58:01 +00:00
if (!ResList.Init(Game.Clients.getLocalID(), &NetIO))
{ LogFatal("Network: failed to initialize resource list!"); Clear(); return false; }
if (!Game.Parameters.InitNetwork(&ResList))
2009-05-08 13:28:41 +00:00
return false;
// create initial dynamic
2010-03-28 18:58:01 +00:00
if (!CreateDynamic(true))
2009-05-08 13:28:41 +00:00
return false;
// initialize net i/o
2010-03-28 18:58:01 +00:00
if (!InitNetIO(false, true))
2009-05-08 13:28:41 +00:00
{ Clear(); return false; }
// init network control
2009-06-15 22:06:37 +00:00
pControl = &::Control.Network;
pControl->Init(C4ClientIDHost, true, ::Control.getNextControlTick(), true, this);
// init league
2009-05-08 13:28:41 +00:00
bool fCancel = true;
2010-03-28 18:58:01 +00:00
if (!InitLeague(&fCancel) || !LeagueStart(&fCancel))
2009-05-08 13:28:41 +00:00
{
// deinit league
DeinitLeague();
// user cancelled?
2010-03-28 18:58:01 +00:00
if (fCancel)
2009-05-08 13:28:41 +00:00
return false;
// in console mode, bail out
#ifdef USE_CONSOLE
return false;
#endif
}
// allow connect
NetIO.SetAcceptMode(true);
2009-05-08 13:28:41 +00:00
// timer
Application.Add(this);
// ok
return true;
}
C4Network2::InitResult C4Network2::InitClient(const C4Network2Reference &Ref, bool fObserver)
{
2010-03-28 18:58:01 +00:00
if (isEnabled()) Clear();
2009-05-08 13:28:41 +00:00
// Get host core
const C4ClientCore &HostCore = Ref.Parameters.Clients.getHost()->getCore();
// host core revision check
if (!SEqualNoCase(HostCore.getRevision(), Application.GetRevision()))
{
StdStrBuf msg;
msg.Format(LoadResStr("IDS_NET_ERR_VERSIONMISMATCH"), HostCore.getRevision(), Application.GetRevision());
if (!Application.isEditor)
{
if (!pGUI->ShowMessageModal(msg.getData(), "[!]Network warning", C4GUI::MessageDialog::btnOKAbort, C4GUI::Ico_Notify, NULL /* do not allow to skip this message! */))
return IR_Fatal;
}
else
{
Log(msg.getData());
}
}
2009-05-08 13:28:41 +00:00
// repeat if wrong password
fWrongPassword = Ref.isPasswordNeeded();
StdStrBuf Password;
2010-03-28 18:58:01 +00:00
for (;;)
2009-05-08 13:28:41 +00:00
{
// ask for password (again)?
2010-03-28 18:58:01 +00:00
if (fWrongPassword)
2009-05-08 13:28:41 +00:00
{
Password.Take(QueryClientPassword());
2010-03-28 18:58:01 +00:00
if (!Password.getLength())
2009-05-08 13:28:41 +00:00
return IR_Error;
fWrongPassword = false;
}
// copy addresses
C4Network2Address Addrs[C4ClientMaxAddr];
2010-03-28 18:58:01 +00:00
for (int i = 0; i < Ref.getAddrCnt(); i++)
2009-05-08 13:28:41 +00:00
Addrs[i] = Ref.getAddr(i);
// Try to connect to host
2010-03-28 18:58:01 +00:00
if (InitClient(Addrs, Ref.getAddrCnt(), HostCore, Password.getData()) == IR_Fatal)
2009-05-08 13:28:41 +00:00
return IR_Fatal;
// success?
2010-03-28 18:58:01 +00:00
if (isEnabled())
2009-05-08 13:28:41 +00:00
break;
// Retry only for wrong password
2010-03-28 18:58:01 +00:00
if (!fWrongPassword)
2009-05-08 13:28:41 +00:00
{
LogSilent("Network: Could not connect!");
return IR_Error;
}
}
// initialize resources
2010-03-28 18:58:01 +00:00
if (!Game.Parameters.InitNetwork(&ResList))
2009-05-08 13:28:41 +00:00
return IR_Fatal;
// init league
2010-03-28 18:58:01 +00:00
if (!InitLeague(NULL))
2009-05-08 13:28:41 +00:00
{
// deinit league
DeinitLeague();
return IR_Fatal;
}
// allow connect
NetIO.SetAcceptMode(true);
2009-05-08 13:28:41 +00:00
// timer
Application.Add(this);
// ok, success
return IR_Success;
}
C4Network2::InitResult C4Network2::InitClient(const class C4Network2Address *pAddrs, int iAddrCount, const C4ClientCore &HostCore, const char *szPassword)
{
// initialization
Status.Set(GS_Init, -1);
fHost = false;
fStatusAck = fStatusReached = true;
fChasing = true;
fAllowJoin = false;
// initialize client list
Game.Clients.Init(C4ClientIDUnknown);
Clients.Init(&Game.Clients, false);
// initialize resource list
2010-03-28 18:58:01 +00:00
if (!ResList.Init(Game.Clients.getLocalID(), &NetIO))
2009-05-08 13:28:41 +00:00
{ LogFatal(LoadResStr("IDS_NET_ERR_INITRESLIST")); Clear(); return IR_Fatal; }
// initialize net i/o
2010-03-28 18:58:01 +00:00
if (!InitNetIO(true, false))
2009-05-08 13:28:41 +00:00
{ Clear(); return IR_Fatal; }
// set network control
2009-06-15 22:06:37 +00:00
pControl = &::Control.Network;
2009-05-08 13:28:41 +00:00
// set exclusive connection mode
NetIO.SetExclusiveConnMode(true);
// try to connect host
StdStrBuf strAddresses; int iSuccesses = 0;
2010-03-28 18:58:01 +00:00
for (int i = 0; i < iAddrCount; i++)
if (!pAddrs[i].isIPNull())
{
2009-05-08 13:28:41 +00:00
// connection
2010-03-28 18:58:01 +00:00
if (!NetIO.Connect(pAddrs[i].getAddr(), pAddrs[i].getProtocol(), HostCore, szPassword))
2009-05-08 13:28:41 +00:00
continue;
// format for message
2010-03-28 18:58:01 +00:00
if (strAddresses.getLength())
2009-05-08 13:28:41 +00:00
strAddresses.Append(", ");
strAddresses.Append(pAddrs[i].toString());
iSuccesses++;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
// no connection attempt running?
2010-03-28 18:58:01 +00:00
if (!iSuccesses)
2009-05-08 13:28:41 +00:00
{ Clear(); return IR_Error; }
// log
StdStrBuf strMessage = FormatString(LoadResStr("IDS_NET_CONNECTHOST"), strAddresses.getData());
Log(strMessage.getData());
// show box
C4GUI::MessageDialog *pDlg = NULL;
if (!Application.isEditor)
2009-05-08 13:28:41 +00:00
{
// create & show
pDlg = new C4GUI::MessageDialog(strMessage.getData(), LoadResStr("IDS_NET_JOINGAME"),
2010-03-28 18:58:01 +00:00
C4GUI::MessageDialog::btnAbort, C4GUI::Ico_NetWait, C4GUI::MessageDialog::dsMedium);
if (!pDlg->Show(::pGUI, true)) { Clear(); return IR_Fatal; }
2009-05-08 13:28:41 +00:00
}
// wait for connect / timeout / abort by user (host will change status on succesful connect)
2010-03-28 18:58:01 +00:00
while (Status.getState() == GS_Init)
2009-05-08 13:28:41 +00:00
{
2010-03-28 18:58:01 +00:00
if (!Application.ScheduleProcs(100))
{ delete pDlg; return IR_Fatal;}
2010-03-28 18:58:01 +00:00
if (pDlg && pDlg->IsAborted())
{ delete pDlg; return IR_Fatal; }
2009-05-08 13:28:41 +00:00
}
// Close dialog
delete pDlg;
2009-05-08 13:28:41 +00:00
// error?
2010-03-28 18:58:01 +00:00
if (!isEnabled())
2009-05-08 13:28:41 +00:00
return IR_Error;
// deactivate exclusive connection mode
NetIO.SetExclusiveConnMode(false);
return IR_Success;
}
bool C4Network2::DoLobby()
{
// shouldn't do lobby?
2010-03-28 18:58:01 +00:00
if (!isEnabled() || (!isHost() && !isLobbyActive()))
2009-05-08 13:28:41 +00:00
return true;
// lobby runs
fLobbyRunning = true;
fAllowJoin = true;
Log(LoadResStr("IDS_NET_LOBBYWAITING"));
2009-05-08 13:28:41 +00:00
// client: lobby status reached, message to host
2010-03-28 18:58:01 +00:00
if (!isHost())
2009-05-08 13:28:41 +00:00
CheckStatusReached();
// host: set lobby mode
else
ChangeGameStatus(GS_Lobby, 0);
// determine lobby type
if (Console.Active)
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// console lobby - update console
if (Console.Active) Console.UpdateMenus();
// init lobby countdown if specified
if (Game.iLobbyTimeout) StartLobbyCountdown(Game.iLobbyTimeout);
// do console lobby
2010-03-28 18:58:01 +00:00
while (isLobbyActive())
2009-05-08 13:28:41 +00:00
if (!Application.ScheduleProcs())
{ Clear(); return false; }
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
else
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// fullscreen lobby
// init lobby dialog
pLobby = new C4GameLobby::MainDlg(isHost());
if (!pLobby->FadeIn(::pGUI)) { delete pLobby; pLobby = NULL; Clear(); return false; }
2009-05-08 13:28:41 +00:00
// init lobby countdown if specified
if (Game.iLobbyTimeout) StartLobbyCountdown(Game.iLobbyTimeout);
// while state lobby: keep looping
while (isLobbyActive() && pLobby && pLobby->IsShown())
2009-05-08 13:28:41 +00:00
if (!Application.ScheduleProcs())
{ Clear(); return false; }
// check whether lobby was aborted
2009-05-08 13:28:41 +00:00
if (pLobby && pLobby->IsAborted()) { delete pLobby; pLobby = NULL; Clear(); return false; }
// deinit lobby
if (pLobby && pLobby->IsShown()) pLobby->Close(true);
delete pLobby; pLobby = NULL;
// close any other dialogs
::pGUI->CloseAllDialogs(false);
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
// lobby end
delete pLobbyCountdown; pLobbyCountdown = NULL;
fLobbyRunning = false;
fAllowJoin = !Config.Network.NoRuntimeJoin;
2009-05-08 13:28:41 +00:00
// notify user that the lobby has ended (for people who tasked out)
Application.NotifyUserIfInactive();
// notify lobby end
bool fGameGo = isEnabled();
if (fGameGo) Log(LoadResStr("IDS_PRC_GAMEGO"));;
// disabled?
return fGameGo;
}
bool C4Network2::Start()
{
2010-03-28 18:58:01 +00:00
if (!isEnabled() || !isHost()) return false;
2009-05-08 13:28:41 +00:00
// change mode: go
2009-06-15 22:06:37 +00:00
ChangeGameStatus(GS_Go, ::Control.ControlTick);
2009-05-08 13:28:41 +00:00
return true;
}
bool C4Network2::Pause()
{
2010-03-28 18:58:01 +00:00
if (!isEnabled() || !isHost()) return false;
2009-05-08 13:28:41 +00:00
// change mode: pause
2009-06-15 22:06:37 +00:00
return ChangeGameStatus(GS_Pause, ::Control.getNextControlTick());
2009-05-08 13:28:41 +00:00
}
bool C4Network2::Sync()
{
// host only
2010-03-28 18:58:01 +00:00
if (!isEnabled() || !isHost()) return false;
2009-05-08 13:28:41 +00:00
// already syncing the network?
2010-03-28 18:58:01 +00:00
if (!fStatusAck)
{
2009-05-08 13:28:41 +00:00
// maybe we are already sync?
2010-03-28 18:58:01 +00:00
if (fStatusReached) CheckStatusAck();
2009-05-08 13:28:41 +00:00
return true;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
// already sync?
2010-03-28 18:58:01 +00:00
if (isFrozen()) return true;
2009-05-08 13:28:41 +00:00
// ok, so let's do a sync: change in the same state we are already in
2009-06-15 22:06:37 +00:00
return ChangeGameStatus(Status.getState(), ::Control.getNextControlTick());
2009-05-08 13:28:41 +00:00
}
bool C4Network2::FinalInit()
{
// check reach
CheckStatusReached(true);
// reached, waiting for ack?
2010-03-28 18:58:01 +00:00
if (fStatusReached && !fStatusAck)
2009-05-08 13:28:41 +00:00
{
// wait for go acknowledgement
Log(LoadResStr("IDS_NET_JOINREADY"));
2010-12-24 13:17:42 +00:00
// any pending keyboard commands should not be routed to cancel the wait dialog - flush the message queue!
2010-03-28 18:58:01 +00:00
if (!Application.FlushMessages()) return false;
2009-05-08 13:28:41 +00:00
// show box
C4GUI::Dialog *pDlg = NULL;
if (!Application.isEditor)
2009-05-08 13:28:41 +00:00
{
2010-04-01 21:08:06 +00:00
// separate dlgs for host/client
2009-05-08 13:28:41 +00:00
if (isHost())
pDlg = new C4Network2StartWaitDlg();
else
pDlg = new C4GUI::MessageDialog(LoadResStr("IDS_NET_WAITFORSTART"), LoadResStr("IDS_NET_CAPTION"),
2010-03-28 18:58:01 +00:00
C4GUI::MessageDialog::btnAbort, C4GUI::Ico_NetWait, C4GUI::MessageDialog::dsSmall);
2009-05-08 13:28:41 +00:00
// show it
2010-03-28 18:58:01 +00:00
if (!pDlg->Show(::pGUI, true)) return false;
2009-05-08 13:28:41 +00:00
}
// wait for acknowledgement
2010-03-28 18:58:01 +00:00
while (fStatusReached && !fStatusAck)
2009-05-08 13:28:41 +00:00
{
2010-03-28 18:58:01 +00:00
if (pDlg)
2009-05-08 13:28:41 +00:00
{
// execute
2010-03-28 18:58:01 +00:00
if (!pDlg->Execute()) { delete pDlg; Clear(); return false; }
2009-05-08 13:28:41 +00:00
// aborted?
2010-03-28 18:58:01 +00:00
if (pDlg->IsAborted()) { delete pDlg; Clear(); return false; }
2009-05-08 13:28:41 +00:00
}
2010-03-28 18:58:01 +00:00
else if (!Application.ScheduleProcs())
2009-05-08 13:28:41 +00:00
{ Clear(); return false; }
}
delete pDlg;
2009-05-08 13:28:41 +00:00
// log
Log(LoadResStr("IDS_NET_START"));
}
// synchronize
Game.SyncClearance();
Game.Synchronize(false);
// finished
2009-05-08 13:28:41 +00:00
return isEnabled();
}
bool C4Network2::RetrieveScenario(char *szScenario)
{
// client only
2010-03-28 18:58:01 +00:00
if (isHost()) return false;
2009-05-08 13:28:41 +00:00
// wait for scenario
C4Network2Res::Ref pScenario = RetrieveRes(*Game.Parameters.Scenario.getResCore(),
2010-03-28 18:58:01 +00:00
C4NetResRetrieveTimeout, LoadResStr("IDS_NET_RES_SCENARIO"));
if (!pScenario)
2009-05-08 13:28:41 +00:00
return false;
// wait for dynamic data
C4Network2Res::Ref pDynamic = RetrieveRes(ResDynamic, C4NetResRetrieveTimeout, LoadResStr("IDS_NET_RES_DYNAMIC"));
2010-03-28 18:58:01 +00:00
if (!pDynamic)
2009-05-08 13:28:41 +00:00
return false;
// create unpacked copy of scenario
2011-03-13 15:11:55 +00:00
if (!ResList.FindTempResFileName(FormatString("Combined%d.ocs", Game.Clients.getLocalID()).getData(), szScenario) ||
2010-03-28 18:58:01 +00:00
!C4Group_CopyItem(pScenario->getFile(), szScenario) ||
!C4Group_UnpackDirectory(szScenario))
2009-05-08 13:28:41 +00:00
return false;
// create unpacked copy of dynamic data
char szTempDynamic[_MAX_PATH + 1];
2010-03-28 18:58:01 +00:00
if (!ResList.FindTempResFileName(pDynamic->getFile(), szTempDynamic) ||
!C4Group_CopyItem(pDynamic->getFile(), szTempDynamic) ||
!C4Group_UnpackDirectory(szTempDynamic))
2009-05-08 13:28:41 +00:00
return false;
2011-03-13 15:16:45 +00:00
// unpack Material.ocg if materials need to be merged
2009-05-08 13:28:41 +00:00
StdStrBuf MaterialScenario, MaterialDynamic;
MaterialScenario.Format("%s" DirSep C4CFN_Material, szScenario);
MaterialDynamic.Format("%s" DirSep C4CFN_Material, szTempDynamic);
2010-03-28 18:58:01 +00:00
if (FileExists(MaterialScenario.getData()) && FileExists(MaterialDynamic.getData()))
if (!C4Group_UnpackDirectory(MaterialScenario.getData()) ||
!C4Group_UnpackDirectory(MaterialDynamic.getData()))
return false;
2009-05-08 13:28:41 +00:00
// move all dynamic files to scenario
C4Group ScenGrp;
2010-03-28 18:58:01 +00:00
if (!ScenGrp.Open(szScenario) ||
!ScenGrp.Merge(szTempDynamic))
2009-05-08 13:28:41 +00:00
return false;
ScenGrp.Close();
// remove dynamic temp file
EraseDirectory(szTempDynamic);
// remove dynamic - isn't needed any more and will soon be out-of-date
pDynamic->Remove();
return true;
}
void C4Network2::OnSec1Timer()
{
Execute();
}
void C4Network2::Execute()
{
// client connections
Clients.DoConnectAttempts();
// status reached?
CheckStatusReached();
2010-03-28 18:58:01 +00:00
if (isHost())
2009-05-08 13:28:41 +00:00
{
// remove dynamic
2010-03-28 18:58:01 +00:00
if (!ResDynamic.isNull() && ::Control.ControlTick > iDynamicTick)
2009-05-08 13:28:41 +00:00
RemoveDynamic();
// Set chase target
UpdateChaseTarget();
// check for inactive clients and deactivate them
DeactivateInactiveClients();
// reference
2010-03-28 18:58:01 +00:00
if (!iLastReferenceUpdate || time(NULL) > (time_t) (iLastReferenceUpdate + C4NetReferenceUpdateInterval))
if (NetIO.IsReferenceNeeded())
{
// create
C4Network2Reference *pRef = new C4Network2Reference();
pRef->InitLocal();
// set
NetIO.SetReference(pRef);
iLastReferenceUpdate = time(NULL);
}
// league server reference
2010-03-28 18:58:01 +00:00
if (!iLastLeagueUpdate || time(NULL) > (time_t) (iLastLeagueUpdate + iLeagueUpdateDelay))
{
LeagueUpdate();
}
2009-05-08 13:28:41 +00:00
// league update reply receive
2010-03-28 18:58:01 +00:00
if (pLeagueClient && fHost && !pLeagueClient->isBusy() && pLeagueClient->getCurrentAction() == C4LA_Update)
2009-05-08 13:28:41 +00:00
{
LeagueUpdateProcessReply();
2009-05-08 13:28:41 +00:00
}
// voting timeout
2010-03-28 18:58:01 +00:00
if (Votes.firstPkt() && time(NULL) > (time_t) (iVoteStartTime + C4NetVotingTimeout))
2009-05-08 13:28:41 +00:00
{
C4ControlVote *pVote = static_cast<C4ControlVote *>(Votes.firstPkt()->getPkt());
2009-06-15 22:06:37 +00:00
::Control.DoInput(
2010-03-28 18:58:01 +00:00
CID_VoteEnd,
new C4ControlVoteEnd(pVote->getType(), false, pVote->getData()),
CDT_Sync);
2009-05-08 13:28:41 +00:00
iVoteStartTime = time(NULL);
}
// record streaming
2010-03-28 18:58:01 +00:00
if (fStreaming)
{
2009-05-08 13:28:41 +00:00
StreamIn(false);
StreamOut();
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
}
else
{
// request activate, if neccessary
if (!tLastActivateRequest.IsInfinite()) RequestActivate();
}
2009-05-08 13:28:41 +00:00
}
void C4Network2::Clear()
{
// stop timer
Application.Remove(this);
// stop streaming
StopStreaming();
// clear league
2010-03-28 18:58:01 +00:00
if (pLeagueClient)
{
2009-05-08 13:28:41 +00:00
LeagueEnd();
DeinitLeague();
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
// stop lobby countdown
delete pLobbyCountdown; pLobbyCountdown = NULL;
// cancel lobby
delete pLobby; pLobby = NULL;
fLobbyRunning = false;
// deactivate
Status.Clear();
fStatusAck = fStatusReached = true;
// if control mode is network: change to local
2010-03-28 18:58:01 +00:00
if (::Control.isNetwork())
2009-06-15 22:06:37 +00:00
::Control.ChangeToLocal();
2009-05-08 13:28:41 +00:00
// clear all player infos
Players.Clear();
// remove all clients
Clients.Clear();
// close net classes
NetIO.Clear();
// clear resources
2009-05-08 13:28:41 +00:00
ResList.Clear();
// clear password
sPassword.Clear();
// stuff
fAllowJoin = false;
iDynamicTick = -1; fDynamicNeeded = false;
tLastActivateRequest = C4TimeMilliseconds::NegativeInfinity;
iLastChaseTargetUpdate = iLastReferenceUpdate = iLastLeagueUpdate = 0;
2009-05-08 13:28:41 +00:00
fDelayedActivateReq = false;
delete pVoteDialog; pVoteDialog = NULL;
2009-05-08 13:28:41 +00:00
fPausedForVote = false;
iLastOwnVoting = 0;
Votes.Clear();
2009-05-08 13:28:41 +00:00
// don't clear fPasswordNeeded here, it's needed by InitClient
}
bool C4Network2::ToggleAllowJoin()
{
// just toggle
AllowJoin(!fAllowJoin);
return true; // toggled
}
bool C4Network2::ToggleClientListDlg()
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
C4Network2ClientListDlg::Toggle();
return true;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
void C4Network2::SetPassword(const char *szToPassword)
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
bool fHadPassword = isPassworded();
// clear password?
if (!szToPassword || !*szToPassword)
sPassword.Clear();
else
// no? then set it
sPassword.Copy(szToPassword);
// if the has-password-state has changed, the reference is invalidated
if (fHadPassword != isPassworded()) InvalidateReference();
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
StdStrBuf C4Network2::QueryClientPassword()
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// ask client for a password; return nothing if user canceled
StdStrBuf sCaption; sCaption.Copy(LoadResStr("IDS_MSG_ENTERPASSWORD"));
C4GUI::InputDialog *pInputDlg = new C4GUI::InputDialog(LoadResStr("IDS_MSG_ENTERPASSWORD"), sCaption.getData(), C4GUI::Ico_Ex_Locked, NULL, false);
pInputDlg->SetDelOnClose(false);
2009-06-05 16:53:56 +00:00
if (!::pGUI->ShowModalDlg(pInputDlg, false))
2010-03-28 18:58:01 +00:00
{
delete pInputDlg;
2009-05-08 13:28:41 +00:00
return StdStrBuf();
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
// copy to buffer
StdStrBuf Buf; Buf.Copy(pInputDlg->GetInputText());
delete pInputDlg;
return Buf;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
void C4Network2::AllowJoin(bool fAllow)
{
2010-03-28 18:58:01 +00:00
if (!isHost()) return;
2009-05-08 13:28:41 +00:00
fAllowJoin = fAllow;
if (Game.IsRunning)
2010-03-28 18:58:01 +00:00
{
::GraphicsSystem.FlashMessage(LoadResStr(fAllowJoin ? "IDS_NET_RUNTIMEJOINFREE" : "IDS_NET_RUNTIMEJOINBARRED"));
2009-05-08 13:28:41 +00:00
Config.Network.NoRuntimeJoin = !fAllowJoin;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
}
void C4Network2::SetAllowObserve(bool fAllow)
{
2010-03-28 18:58:01 +00:00
if (!isHost()) return;
fAllowObserve = fAllow;
2009-05-08 13:28:41 +00:00
}
void C4Network2::SetCtrlMode(int32_t iCtrlMode)
{
2010-03-28 18:58:01 +00:00
if (!isHost()) return;
// no change?
2010-03-28 18:58:01 +00:00
if (iCtrlMode == Status.getCtrlMode()) return;
// change game status
ChangeGameStatus(Status.getState(), ::Control.ControlTick, iCtrlMode);
2009-05-08 13:28:41 +00:00
}
void C4Network2::OnConn(C4Network2IOConnection *pConn)
{
// Nothing to do atm... New pending connections are managed mainly by C4Network2IO
// until they are accepted, see PID_Conn/PID_ConnRe handlers in HandlePacket.
// Note this won't get called anymore because of this (see C4Network2IO::OnConn)
}
void C4Network2::OnDisconn(C4Network2IOConnection *pConn)
{
// could not establish host connection?
2010-03-28 18:58:01 +00:00
if (Status.getState() == GS_Init && !isHost())
2009-05-08 13:28:41 +00:00
{
2010-03-28 18:58:01 +00:00
if (!NetIO.getConnectionCount())
2009-05-08 13:28:41 +00:00
Clear();
return;
}
// connection failed?
2010-03-28 18:58:01 +00:00
if (pConn->isFailed())
2009-05-08 13:28:41 +00:00
{
// call handler
OnConnectFail(pConn);
return;
}
// search client
C4Network2Client *pClient = Clients.GetClient(pConn);
// not found? Search by ID (not associated yet, half-accepted connection)
2010-03-28 18:58:01 +00:00
if (!pClient) pClient = Clients.GetClientByID(pConn->getClientID());
2009-05-08 13:28:41 +00:00
// not found? ignore
2010-03-28 18:58:01 +00:00
if (!pClient) return;
2009-05-08 13:28:41 +00:00
// remove connection
pClient->RemoveConn(pConn);
// create post-mortem if needed
C4PacketPostMortem PostMortem;
2010-03-28 18:58:01 +00:00
if (pConn->CreatePostMortem(&PostMortem))
2009-05-08 13:28:41 +00:00
{
LogSilentF("Network: Sending %d packets for recovery (%d-%d)", PostMortem.getPacketCount(), pConn->getOutPacketCounter() - PostMortem.getPacketCount(), pConn->getOutPacketCounter() - 1);
// This might fail because of this disconnect
// (If it's the only host connection. We're toast then anyway.)
2010-03-28 18:58:01 +00:00
if (!Clients.SendMsgToClient(pConn->getClientID(), MkC4NetIOPacket(PID_PostMortem, PostMortem)))
2009-05-08 13:28:41 +00:00
assert(isHost() || !Clients.GetHost()->isConnected());
}
// call handler
OnDisconnect(pClient, pConn);
}
void C4Network2::HandlePacket(char cStatus, const C4PacketBase *pPacket, C4Network2IOConnection *pConn)
{
// find associated client
C4Network2Client *pClient = Clients.GetClient(pConn);
2010-03-28 18:58:01 +00:00
if (!pClient) pClient = Clients.GetClientByID(pConn->getClientID());
2009-05-08 13:28:41 +00:00
// local? ignore
2010-03-28 18:58:01 +00:00
if (pClient && pClient->isLocal()) { pConn->Close(); return; }
2009-05-08 13:28:41 +00:00
2010-03-28 18:58:01 +00:00
#define GETPKT(type, name) \
assert(pPacket); const type &name = \
/*dynamic_cast*/ static_cast<const type &>(*pPacket);
2009-05-08 13:28:41 +00:00
2010-03-28 18:58:01 +00:00
switch (cStatus)
2009-05-08 13:28:41 +00:00
{
case PID_Conn: // connection request
{
2010-03-28 18:58:01 +00:00
if (!pConn->isOpen()) break;
2009-05-08 13:28:41 +00:00
GETPKT(C4PacketConn, rPkt);
HandleConn(rPkt, pConn, pClient);
}
break;
case PID_ConnRe: // connection request reply
{
GETPKT(C4PacketConnRe, rPkt);
HandleConnRe(rPkt, pConn, pClient);
}
break;
case PID_JoinData:
{
// host->client only
2010-03-28 18:58:01 +00:00
if (isHost() || !pClient || !pClient->isHost()) break;
if (!pConn->isOpen()) break;
// handle
GETPKT(C4PacketJoinData, rPkt)
HandleJoinData(rPkt);
}
break;
2009-05-08 13:28:41 +00:00
case PID_Status: // status change
{
// by host only
2010-03-28 18:58:01 +00:00
if (isHost() || !pClient || !pClient->isHost()) break;
if (!pConn->isOpen()) break;
2009-05-08 13:28:41 +00:00
// must be initialized
2010-03-28 18:58:01 +00:00
if (Status.getState() == GS_Init) break;
2009-05-08 13:28:41 +00:00
// handle
GETPKT(C4Network2Status, rPkt);
HandleStatus(rPkt);
}
break;
case PID_StatusAck: // status change acknowledgement
{
// host->client / client->host only
2010-03-28 18:58:01 +00:00
if (!pClient) break;
if (!isHost() && !pClient->isHost()) break;
2009-05-08 13:28:41 +00:00
// must be initialized
2010-03-28 18:58:01 +00:00
if (Status.getState() == GS_Init) break;
2009-05-08 13:28:41 +00:00
// handle
GETPKT(C4Network2Status, rPkt);
HandleStatusAck(rPkt, pClient);
}
break;
case PID_ClientActReq: // client activation request
{
// client->host only
2010-03-28 18:58:01 +00:00
if (!isHost() || !pClient || pClient->isHost()) break;
2009-05-08 13:28:41 +00:00
// must be initialized
2010-03-28 18:58:01 +00:00
if (Status.getState() == GS_Init) break;
// handle
GETPKT(C4PacketActivateReq, rPkt)
HandleActivateReq(rPkt.getTick(), pClient);
}
break;
2009-05-08 13:28:41 +00:00
}
2010-03-28 18:58:01 +00:00
#undef GETPKT
2009-05-08 13:28:41 +00:00
}
void C4Network2::HandleLobbyPacket(char cStatus, const C4PacketBase *pBasePkt, C4Network2IOConnection *pConn)
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// find associated client
C4Network2Client *pClient = Clients.GetClient(pConn);
2010-03-28 18:58:01 +00:00
if (!pClient) pClient = Clients.GetClientByID(pConn->getClientID());
2009-05-08 13:28:41 +00:00
// forward directly to lobby
if (pLobby) pLobby->HandlePacket(cStatus, pBasePkt, pClient);
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
void C4Network2::OnGameSynchronized()
{
// savegame needed?
2010-03-28 18:58:01 +00:00
if (fDynamicNeeded)
2009-05-08 13:28:41 +00:00
{
// create dynamic
bool fSuccess = CreateDynamic(false);
// check for clients that still need join-data
C4Network2Client *pClient = NULL;
2010-03-28 18:58:01 +00:00
while ((pClient = Clients.GetNextClient(pClient)))
if (!pClient->hasJoinData())
2010-01-25 04:00:59 +00:00
{
2010-03-28 18:58:01 +00:00
if (fSuccess)
2009-05-08 13:28:41 +00:00
// now we can provide join data: send it
SendJoinData(pClient);
else
// join data could not be created: emergency kick
Game.Clients.CtrlRemove(pClient->getClient(), LoadResStr("IDS_ERR_ERRORWHILECREATINGJOINDAT"));
2010-01-25 04:00:59 +00:00
}
2009-05-08 13:28:41 +00:00
}
}
void C4Network2::DrawStatus(C4TargetFacet &cgo)
{
2010-03-28 18:58:01 +00:00
if (!isEnabled()) return;
2009-05-08 13:28:41 +00:00
C4Network2Client *pLocal = Clients.GetLocal();
StdStrBuf Stat;
2009-05-08 13:28:41 +00:00
// local client status
Stat.AppendFormat("Local: %s %s %s (ID %d)",
2010-03-28 18:58:01 +00:00
pLocal->isObserver() ? "Observing" : pLocal->isActivated() ? "Active" : "Inactive", pLocal->isHost() ? "host" : "client",
pLocal->getName(), pLocal->getID());
2009-05-08 13:28:41 +00:00
// game status
Stat.AppendFormat( "|Game Status: %s (tick %d)%s%s",
2010-03-28 18:58:01 +00:00
Status.getStateName(), Status.getTargetCtrlTick(),
fStatusReached ? " reached" : "", fStatusAck ? " ack" : "");
2009-05-08 13:28:41 +00:00
// available protocols
C4NetIO *pMsgIO = NetIO.MsgIO(), *pDataIO = NetIO.DataIO();
2010-03-28 18:58:01 +00:00
if (pMsgIO && pDataIO)
2009-05-08 13:28:41 +00:00
{
C4Network2IOProtocol eMsgProt = NetIO.getNetIOProt(pMsgIO),
2010-03-28 18:58:01 +00:00
eDataProt = NetIO.getNetIOProt(pDataIO);
2009-05-08 13:28:41 +00:00
int32_t iMsgPort = 0, iDataPort = 0;
2010-03-28 18:58:01 +00:00
switch (eMsgProt)
{
2009-05-08 13:28:41 +00:00
case P_TCP: iMsgPort = Config.Network.PortTCP; break;
case P_UDP: iMsgPort = Config.Network.PortUDP; break;
2010-01-25 04:00:59 +00:00
case P_NONE: assert(eMsgProt != P_NONE); break;
2009-05-08 13:28:41 +00:00
}
2010-03-28 18:58:01 +00:00
switch (eDataProt)
2009-05-08 13:28:41 +00:00
{
case P_TCP: iDataPort = Config.Network.PortTCP; break;
case P_UDP: iDataPort = Config.Network.PortUDP; break;
2010-01-25 04:00:59 +00:00
case P_NONE: assert(eMsgProt != P_NONE); break;
2009-05-08 13:28:41 +00:00
}
Stat.AppendFormat( "|Protocols: %s: %s (%d i%d o%d bc%d)",
2010-03-28 18:58:01 +00:00
pMsgIO != pDataIO ? "Msg" : "Msg/Data",
NetIO.getNetIOName(pMsgIO), iMsgPort,
NetIO.getProtIRate(eMsgProt), NetIO.getProtORate(eMsgProt), NetIO.getProtBCRate(eMsgProt));
if (pMsgIO != pDataIO)
Stat.AppendFormat( ", Data: %s (%d i%d o%d bc%d)",
NetIO.getNetIOName(pDataIO), iDataPort,
NetIO.getProtIRate(eDataProt), NetIO.getProtORate(eDataProt), NetIO.getProtBCRate(eDataProt));
2009-05-08 13:28:41 +00:00
}
else
Stat.Append("|Protocols: none");
// some control statistics
Stat.AppendFormat( "|Control: %s, Tick %d, Behind %d, Rate %d, PreSend %d, ACT: %d",
2010-03-28 18:58:01 +00:00
Status.getCtrlMode() == CNM_Decentral ? "Decentral" : Status.getCtrlMode() == CNM_Central ? "Central" : "Async",
::Control.ControlTick, pControl->GetBehind(::Control.ControlTick),
::Control.ControlRate, pControl->getControlPreSend(), pControl->getAvgControlSendTime());
2009-05-08 13:28:41 +00:00
// Streaming statistics
2010-03-28 18:58:01 +00:00
if (fStreaming)
2010-01-25 04:00:59 +00:00
Stat.AppendFormat( "|Streaming: %lu waiting, %u in, %lu out, %lu sent",
2010-04-28 21:43:25 +00:00
static_cast<unsigned long>(pStreamedRecord ? pStreamedRecord->GetStreamingBuf().getSize() : 0),
2010-03-28 18:58:01 +00:00
pStreamedRecord ? pStreamedRecord->GetStreamingPos() : 0,
2010-04-28 21:43:25 +00:00
static_cast<unsigned long>(getPendingStreamData()),
2010-03-28 18:58:01 +00:00
static_cast<unsigned long>(iCurrentStreamPosition));
2009-05-08 13:28:41 +00:00
// clients
Stat.Append("|Clients:");
2010-03-28 18:58:01 +00:00
for (C4Network2Client *pClient = Clients.GetNextClient(NULL); pClient; pClient = Clients.GetNextClient(pClient))
2009-05-08 13:28:41 +00:00
{
// ignore local
2010-03-28 18:58:01 +00:00
if (pClient->isLocal()) continue;
2009-05-08 13:28:41 +00:00
// client status
const C4ClientCore &Core = pClient->getCore();
2010-01-25 04:00:59 +00:00
const char *szClientStatus;
2010-03-28 18:58:01 +00:00
switch (pClient->getStatus())
2009-05-08 13:28:41 +00:00
{
case NCS_Joining: szClientStatus = " (joining)"; break;
case NCS_Chasing: szClientStatus = " (chasing)"; break;
case NCS_NotReady: szClientStatus = " (!rdy)"; break;
case NCS_Remove: szClientStatus = " (removed)"; break;
2010-01-25 04:00:59 +00:00
default: szClientStatus = ""; break;
2009-05-08 13:28:41 +00:00
}
Stat.AppendFormat( "|- %s %s %s (ID %d) (wait %d ms, behind %d)%s%s",
2010-03-28 18:58:01 +00:00
Core.isObserver() ? "Observing" : Core.isActivated() ? "Active" : "Inactive", Core.isHost() ? "host" : "client",
Core.getName(), Core.getID(),
pControl->ClientPerfStat(pClient->getID()),
::Control.ControlTick - pControl->ClientNextControl(pClient->getID()),
szClientStatus,
pClient->isActivated() && !pControl->ClientReady(pClient->getID(), ::Control.ControlTick) ? " (!ctrl)" : "");
2009-05-08 13:28:41 +00:00
// connections
2010-03-28 18:58:01 +00:00
if (pClient->isConnected())
2009-05-08 13:28:41 +00:00
{
Stat.AppendFormat( "| Connections: %s: %s (%s:%d p%d l%d)",
2010-03-28 18:58:01 +00:00
pClient->getMsgConn() == pClient->getDataConn() ? "Msg/Data" : "Msg",
NetIO.getNetIOName(pClient->getMsgConn()->getNetClass()),
inet_ntoa(pClient->getMsgConn()->getPeerAddr().sin_addr),
htons(pClient->getMsgConn()->getPeerAddr().sin_port),
pClient->getMsgConn()->getPingTime(),
pClient->getMsgConn()->getPacketLoss());
if (pClient->getMsgConn() != pClient->getDataConn())
Stat.AppendFormat( ", Data: %s (%s:%d p%d l%d)",
NetIO.getNetIOName(pClient->getDataConn()->getNetClass()),
inet_ntoa(pClient->getDataConn()->getPeerAddr().sin_addr),
htons(pClient->getDataConn()->getPeerAddr().sin_port),
pClient->getDataConn()->getPingTime(),
pClient->getDataConn()->getPacketLoss());
2009-05-08 13:28:41 +00:00
}
else
Stat.Append("| Not connected");
}
2010-03-28 18:58:01 +00:00
if (!Clients.GetNextClient(NULL))
2009-05-08 13:28:41 +00:00
Stat.Append("| - none -");
// draw
2011-10-03 14:30:18 +00:00
pDraw->TextOut(Stat.getData(), ::GraphicsResource.FontRegular, 1.0, cgo.Surface,cgo.X + 20,cgo.Y + 50);
2009-05-08 13:28:41 +00:00
}
bool C4Network2::InitNetIO(bool fNoClientID, bool fHost)
{
// clear
NetIO.Clear();
Config.Network.CheckPortsForCollisions();
2009-05-08 13:28:41 +00:00
// discovery: disable for client
int16_t iPortDiscovery = fHost ? Config.Network.PortDiscovery : -1;
int16_t iPortRefServer = fHost ? Config.Network.PortRefServer : -1;
// init subclass
2010-03-28 18:58:01 +00:00
if (!NetIO.Init(Config.Network.PortTCP, Config.Network.PortUDP, iPortDiscovery, iPortRefServer, fHost))
2009-05-08 13:28:41 +00:00
return false;
// set core (unset ID if sepecified, has to be set later)
C4ClientCore Core = Game.Clients.getLocalCore();
2010-03-28 18:58:01 +00:00
if (fNoClientID) Core.SetID(C4ClientIDUnknown);
2009-05-08 13:28:41 +00:00
NetIO.SetLocalCCore(Core);
// safe addresses of local client
Clients.GetLocal()->AddLocalAddrs(
2010-03-28 18:58:01 +00:00
NetIO.hasTCP() ? Config.Network.PortTCP : -1,
NetIO.hasUDP() ? Config.Network.PortUDP : -1);
2009-05-08 13:28:41 +00:00
// ok
return true;
}
void C4Network2::HandleConn(const C4PacketConn &Pkt, C4Network2IOConnection *pConn, C4Network2Client *pClient)
{
// security
2010-03-28 18:58:01 +00:00
if (!pConn) return;
2009-05-08 13:28:41 +00:00
// Handles a connect request (packet PID_Conn).
// Check if this peer should be allowed to connect, make space for the new connection.
// connection is closed?
2010-03-28 18:58:01 +00:00
if (pConn->isClosed())
2009-05-08 13:28:41 +00:00
return;
// set up core
const C4ClientCore &CCore = Pkt.getCCore();
C4ClientCore NewCCore = CCore;
// accept connection?
2009-05-11 13:09:53 +00:00
StdStrBuf reply;
2009-05-08 13:28:41 +00:00
bool fOK = false;
// search client
2010-03-28 18:58:01 +00:00
if (!pClient && Pkt.getCCore().getID() != C4ClientIDUnknown)
2009-05-08 13:28:41 +00:00
pClient = Clients.GetClient(Pkt.getCCore());
// check engine version
bool fWrongPassword = false;
2012-10-18 21:54:50 +00:00
if (Pkt.getVer() != C4XVER1*10000 + C4XVER2*100 + C4XVER3)
2009-05-08 13:28:41 +00:00
{
2012-10-18 21:54:50 +00:00
reply.Format("wrong engine (%d.%d.%d, I have %d.%d.%d)", Pkt.getVer()/10000, Pkt.getVer()/100%100, Pkt.getVer()%100, C4XVER1, C4XVER2, C4XVER3);
2009-05-08 13:28:41 +00:00
fOK = false;
}
else
{
2010-03-28 18:58:01 +00:00
if (pClient)
if (CheckConn(NewCCore, pConn, pClient, &reply))
2009-05-08 13:28:41 +00:00
{
// accept
2010-03-28 18:58:01 +00:00
if (!reply) reply = "connection accepted";
2009-05-08 13:28:41 +00:00
fOK = true;
}
// client: host connection?
2010-03-28 18:58:01 +00:00
if (!fOK && !isHost() && Status.getState() == GS_Init && !Clients.GetHost())
if (HostConnect(NewCCore, pConn, &reply))
2009-05-08 13:28:41 +00:00
{
// accept
2010-03-28 18:58:01 +00:00
if (!reply) reply = "host connection accepted";
2009-05-08 13:28:41 +00:00
fOK = true;
}
// host: client join? (NewCCore will be changed by Join()!)
2010-03-28 18:58:01 +00:00
if (!fOK && isHost() && !pClient)
2009-05-08 13:28:41 +00:00
{
// check password
2010-03-28 18:58:01 +00:00
if (!sPassword.isNull() && !SEqual(Pkt.getPassword(), sPassword.getData()))
2009-05-08 13:28:41 +00:00
{
2009-05-11 13:09:53 +00:00
reply = "wrong password";
2009-05-08 13:28:41 +00:00
fWrongPassword = true;
}
// accept join
else if (Join(NewCCore, pConn, &reply))
2009-05-08 13:28:41 +00:00
{
// save core
2010-03-28 18:58:01 +00:00
pConn->SetCCore(NewCCore);
// accept
2010-03-28 18:58:01 +00:00
if (!reply) reply = "join accepted";
2009-05-08 13:28:41 +00:00
fOK = true;
}
}
}
// denied? set default reason
2010-03-28 18:58:01 +00:00
if (!fOK && !reply) reply = "connection denied";
2009-05-08 13:28:41 +00:00
// OK and already half accepted? Skip (double-checked: ok).
2010-03-28 18:58:01 +00:00
if (fOK && pConn->isHalfAccepted())
2009-05-08 13:28:41 +00:00
return;
// send answer
2009-05-11 13:09:53 +00:00
C4PacketConnRe pcr(fOK, fWrongPassword, reply.getData());
2010-03-28 18:58:01 +00:00
if (!pConn->Send(MkC4NetIOPacket(PID_ConnRe, pcr)))
2009-05-08 13:28:41 +00:00
return;
// accepted?
2010-03-28 18:58:01 +00:00
if (fOK)
2009-05-08 13:28:41 +00:00
{
// set status
2010-03-28 18:58:01 +00:00
if (!pConn->isClosed())
2009-05-08 13:28:41 +00:00
pConn->SetHalfAccepted();
}
// denied? close
else
{
// log & close
2009-05-11 13:09:53 +00:00
LogSilentF("Network: connection by %s (%s:%d) blocked: %s", CCore.getName(), inet_ntoa(pConn->getPeerAddr().sin_addr), htons(pConn->getPeerAddr().sin_port), reply.getData());
2009-05-08 13:28:41 +00:00
pConn->Close();
}
}
bool C4Network2::CheckConn(const C4ClientCore &CCore, C4Network2IOConnection *pConn, C4Network2Client *pClient, StdStrBuf * szReply)
2009-05-08 13:28:41 +00:00
{
2010-03-28 18:58:01 +00:00
if (!pConn || !pClient) return false;
2009-05-08 13:28:41 +00:00
// already connected? (shouldn't happen really)
2010-03-28 18:58:01 +00:00
if (pClient->hasConn(pConn))
{ *szReply = "already connected"; return true; }
2009-05-08 13:28:41 +00:00
// check core
2010-03-28 18:58:01 +00:00
if (CCore.getDiffLevel(pClient->getCore()) > C4ClientCoreDL_IDMatch)
{ *szReply = "wrong client core"; return false; }
2009-05-08 13:28:41 +00:00
// check address
2010-03-28 18:58:01 +00:00
if (pClient->isConnected() && pClient->getMsgConn()->getPeerAddr().sin_addr.s_addr != pConn->getPeerAddr().sin_addr.s_addr)
{ *szReply = "wrong address"; return false; }
2009-05-08 13:28:41 +00:00
// accept
return true;
}
bool C4Network2::HostConnect(const C4ClientCore &CCore, C4Network2IOConnection *pConn, StdStrBuf *szReply)
2009-05-08 13:28:41 +00:00
{
2010-03-28 18:58:01 +00:00
if (!pConn) return false;
if (!CCore.isHost()) { *szReply = "not host"; return false; }
2009-05-08 13:28:41 +00:00
// create client class for host
// (core is unofficial, see InitClient() - will be overwritten later in HandleJoinData)
C4Client *pClient = Game.Clients.Add(CCore);
2010-03-28 18:58:01 +00:00
if (!pClient) return false;
2009-05-08 13:28:41 +00:00
// accept
return true;
}
bool C4Network2::Join(C4ClientCore &CCore, C4Network2IOConnection *pConn, StdStrBuf *szReply)
2009-05-08 13:28:41 +00:00
{
2010-03-28 18:58:01 +00:00
if (!pConn) return false;
2009-05-08 13:28:41 +00:00
// security
if (!isHost()) { *szReply = "not host"; return false; }
if (!fAllowJoin && !fAllowObserve) { *szReply = "join denied"; return false; }
if (CCore.getID() != C4ClientIDUnknown) { *szReply = "join with set id not allowed"; return false; }
2009-05-08 13:28:41 +00:00
// find free client id
CCore.SetID(iNextClientID++);
// observer?
2010-03-28 18:58:01 +00:00
if (!fAllowJoin) CCore.SetObserver(true);
// deactivate - client will have to ask for activation.
CCore.SetActivated(false);
2009-05-08 13:28:41 +00:00
// Name already in use? Find unused one
2010-03-28 18:58:01 +00:00
if (Clients.GetClient(CCore.getName()))
2009-05-08 13:28:41 +00:00
{
char szNameTmpl[256+1], szNewName[256+1];
SCopy(CCore.getName(), szNameTmpl, 254); SAppend("%d", szNameTmpl, 256);
int32_t i = 1;
do
sprintf(szNewName, szNameTmpl, ++i);
2010-03-28 18:58:01 +00:00
while (Clients.GetClient(szNewName));
2009-05-08 13:28:41 +00:00
CCore.SetName(szNewName);
}
// join client
2009-06-15 22:06:37 +00:00
::Control.DoInput(CID_ClientJoin, new C4ControlClientJoin(CCore), CDT_Direct);
2009-05-08 13:28:41 +00:00
// get client, set status
C4Network2Client *pClient = Clients.GetClient(CCore);
2010-03-28 18:58:01 +00:00
if (pClient) pClient->SetStatus(NCS_Joining);
// warn if client revision doesn't match our host revision
if (!SEqualNoCase(CCore.getRevision(), Application.GetRevision()))
{
LogF("[!]WARNING! Client %s engine revision (%s) differs from local revision (%s). Client might run out of sync.", CCore.getName(), CCore.getRevision(), Application.GetRevision());
}
2009-05-08 13:28:41 +00:00
// ok, client joined.
return true;
// Note that the connection isn't fully accepted at this point and won't be
// associated with the client. The new-created client is waiting for connect.
// Somewhat ironically, the connection may still timeout (resulting in an instant
// removal and maybe some funny message sequences).
// The final client initialization will be done at OnClientConnect.
}
void C4Network2::HandleConnRe(const C4PacketConnRe &Pkt, C4Network2IOConnection *pConn, C4Network2Client *pClient)
{
// Handle the connection request reply. After this handling, the connection should
// be either fully associated with a client (fully accepted) or closed.
// Note that auto-accepted connection have to processed here once, too, as the
// client must get associated with the connection. After doing so, the connection
// auto-accept flag will be reset to mark the connection fully accepted.
// security
2010-03-28 18:58:01 +00:00
if (!pConn) return;
if (!pClient) { pConn->Close(); return; }
2009-05-08 13:28:41 +00:00
// negative reply?
2010-03-28 18:58:01 +00:00
if (!Pkt.isOK())
2009-05-08 13:28:41 +00:00
{
// wrong password?
fWrongPassword = Pkt.isPasswordWrong();
// show message
LogSilentF("Network: connection to %s (%s:%d) refused: %s", pClient->getName(), inet_ntoa(pConn->getPeerAddr().sin_addr), htons(pConn->getPeerAddr().sin_port), Pkt.getMsg());
// close connection
pConn->Close();
return;
}
// connection is closed?
2010-03-28 18:58:01 +00:00
if (!pConn->isOpen())
2009-05-08 13:28:41 +00:00
return;
// already accepted? ignore
2010-03-28 18:58:01 +00:00
if (pConn->isAccepted() && !pConn->isAutoAccepted()) return;
2009-05-08 13:28:41 +00:00
// first connection?
bool fFirstConnection = !pClient->isConnected();
// accept connection
pConn->SetAccepted(); pConn->ResetAutoAccepted();
// add connection
pConn->SetCCore(pClient->getCore());
2010-03-28 18:58:01 +00:00
if (pConn->getNetClass() == NetIO.MsgIO()) pClient->SetMsgConn(pConn);
if (pConn->getNetClass() == NetIO.DataIO()) pClient->SetDataConn(pConn);
2009-05-08 13:28:41 +00:00
// add peer connect address to client address list
2010-03-28 18:58:01 +00:00
if (pConn->getConnectAddr().sin_addr.s_addr)
2009-05-08 13:28:41 +00:00
{
C4Network2Address Addr(pConn->getConnectAddr(), pConn->getProtocol());
pClient->AddAddr(Addr, Status.getState() != GS_Init);
}
// handle
OnConnect(pClient, pConn, Pkt.getMsg(), fFirstConnection);
}
void C4Network2::HandleStatus(const C4Network2Status &nStatus)
{
// set
Status = nStatus;
// log
LogSilentF("Network: going into status %s (tick %d)", Status.getStateName(), nStatus.getTargetCtrlTick());
// reset flags
fStatusReached = fStatusAck = false;
// check: reached?
CheckStatusReached();
}
void C4Network2::HandleStatusAck(const C4Network2Status &nStatus, C4Network2Client *pClient)
{
// security
2010-03-28 18:58:01 +00:00
if (!pClient->hasJoinData() || pClient->isRemoved()) return;
2009-05-08 13:28:41 +00:00
// status doesn't match?
2010-03-28 18:58:01 +00:00
if (nStatus.getState() != Status.getState() || nStatus.getTargetCtrlTick() < Status.getTargetCtrlTick())
2009-05-08 13:28:41 +00:00
return;
// host: wait until all clients are ready
2010-03-28 18:58:01 +00:00
if (isHost())
2009-05-08 13:28:41 +00:00
{
// check: target tick change?
2010-03-28 18:58:01 +00:00
if (!fStatusAck && nStatus.getTargetCtrlTick() > Status.getTargetCtrlTick())
2009-05-08 13:28:41 +00:00
// take the new status
ChangeGameStatus(nStatus.getState(), nStatus.getTargetCtrlTick());
// already acknowledged? Send another ack
2010-03-28 18:58:01 +00:00
if (fStatusAck)
2009-05-08 13:28:41 +00:00
pClient->SendMsg(MkC4NetIOPacket(PID_StatusAck, nStatus));
// mark as ready (will clear chase-flag)
pClient->SetStatus(NCS_Ready);
// check: everyone ready?
2010-03-28 18:58:01 +00:00
if (!fStatusAck && fStatusReached)
2009-05-08 13:28:41 +00:00
CheckStatusAck();
}
else
{
// target tick doesn't match? ignore
2010-03-28 18:58:01 +00:00
if (nStatus.getTargetCtrlTick() != Status.getTargetCtrlTick())
2009-05-08 13:28:41 +00:00
return;
// reached?
// can be ignored safely otherwise - when the status is reached, we will send
// status ack on which the host should generate another status ack (see above)
2010-03-28 18:58:01 +00:00
if (fStatusReached)
2009-05-08 13:28:41 +00:00
{
// client: set flags, call handler
fStatusAck = true; fChasing = false;
OnStatusAck();
}
}
}
void C4Network2::HandleActivateReq(int32_t iTick, C4Network2Client *pByClient)
{
2010-03-28 18:58:01 +00:00
if (!isHost()) return;
// not allowed or already activated? ignore
2010-03-28 18:58:01 +00:00
if (pByClient->isObserver() || pByClient->isActivated()) return;
2009-05-08 13:28:41 +00:00
// not joined completely yet? ignore
2010-03-28 18:58:01 +00:00
if (!pByClient->isWaitedFor()) return;
// check behind limit
2010-03-28 18:58:01 +00:00
if (isRunning())
{
// make a guess how much the client lags.
int32_t iLagFrames = BoundBy(pByClient->getMsgConn()->getPingTime() * Game.FPS / 500, 0, 100);
2010-03-28 18:58:01 +00:00
if (iTick < Game.FrameCounter - iLagFrames - C4NetMaxBehind4Activation)
return;
}
// activate him
2009-06-15 22:06:37 +00:00
::Control.DoInput(CID_ClientUpdate,
2010-03-28 18:58:01 +00:00
new C4ControlClientUpdate(pByClient->getID(), CUT_Activate, true),
CDT_Sync);
2009-05-08 13:28:41 +00:00
}
void C4Network2::HandleJoinData(const C4PacketJoinData &rPkt)
{
// init only
2010-03-28 18:58:01 +00:00
if (Status.getState() != GS_Init)
2009-05-08 13:28:41 +00:00
{ LogSilentF("Network: unexpected join data received!"); return; }
// get client ID
2010-03-28 18:58:01 +00:00
if (rPkt.getClientID() == C4ClientIDUnknown)
2009-05-08 13:28:41 +00:00
{ LogSilentF("Network: host didn't set client ID!"); Clear(); return; }
// set local ID
ResList.SetLocalID(rPkt.getClientID());
Game.Parameters.Clients.SetLocalID(rPkt.getClientID());
// read and validate status
HandleStatus(rPkt.getStatus());
2010-03-28 18:58:01 +00:00
if (Status.getState() != GS_Lobby && Status.getState() != GS_Pause && Status.getState() != GS_Go)
2009-05-08 13:28:41 +00:00
{ LogSilentF("Network: join data has bad game status: %s", Status.getStateName()); Clear(); return; }
// copy parameters
Game.Parameters = rPkt.Parameters;
// set local client
C4Client *pLocalClient = Game.Clients.getClientByID(rPkt.getClientID());
2010-03-28 18:58:01 +00:00
if (!pLocalClient)
2009-05-08 13:28:41 +00:00
{ LogSilentF("Network: Could not find local client in join data!"); Clear(); return; }
// save back dynamic data
ResDynamic = rPkt.getDynamicCore();
iDynamicTick = rPkt.getStartCtrlTick();
// initialize control
2009-06-15 22:06:37 +00:00
::Control.ControlRate = rPkt.Parameters.ControlRate;
2009-05-08 13:28:41 +00:00
pControl->Init(rPkt.getClientID(), false, rPkt.getStartCtrlTick(), pLocalClient->isActivated(), this);
pControl->CopyClientList(Game.Parameters.Clients);
// set local core
NetIO.SetLocalCCore(pLocalClient->getCore());
// add the resources to the network resource list
2009-05-08 13:28:41 +00:00
Game.Parameters.GameRes.InitNetwork(&ResList);
// load dynamic
2010-03-28 18:58:01 +00:00
if (!ResList.AddByCore(ResDynamic))
2009-05-08 13:28:41 +00:00
{ LogFatal("Network: can not not retrieve dynamic!"); Clear(); return; }
// load player resources
2009-05-08 13:28:41 +00:00
Game.Parameters.PlayerInfos.LoadResources();
// send additional addresses
Clients.SendAddresses(NULL);
}
void C4Network2::OnConnect(C4Network2Client *pClient, C4Network2IOConnection *pConn, const char *szMsg, bool fFirstConnection)
{
// log
LogSilentF("Network: %s %s connected (%s:%d/%s) (%s)", pClient->isHost() ? "host" : "client",
2010-03-28 18:58:01 +00:00
pClient->getName(), inet_ntoa(pConn->getPeerAddr().sin_addr), htons(pConn->getPeerAddr().sin_port),
NetIO.getNetIOName(pConn->getNetClass()), szMsg ? szMsg : "");
2009-05-08 13:28:41 +00:00
// first connection for this peer? call special handler
2010-03-28 18:58:01 +00:00
if (fFirstConnection) OnClientConnect(pClient, pConn);
2009-05-08 13:28:41 +00:00
}
void C4Network2::OnConnectFail(C4Network2IOConnection *pConn)
{
LogSilentF("Network: %s connection to %s:%d failed!", NetIO.getNetIOName(pConn->getNetClass()),
2010-03-28 18:58:01 +00:00
inet_ntoa(pConn->getPeerAddr().sin_addr), htons(pConn->getPeerAddr().sin_port));
2009-05-08 13:28:41 +00:00
// maybe client connection failure
// (happens if the connection is not fully accepted and the client disconnects.
// See C4Network2::Join)
C4Network2Client *pClient = Clients.GetClientByID(pConn->getClientID());
2010-03-28 18:58:01 +00:00
if (pClient && !pClient->isConnected())
2009-05-08 13:28:41 +00:00
OnClientDisconnect(pClient);
}
void C4Network2::OnDisconnect(C4Network2Client *pClient, C4Network2IOConnection *pConn)
{
LogSilentF("Network: %s connection to %s (%s:%d) lost!", NetIO.getNetIOName(pConn->getNetClass()),
2010-03-28 18:58:01 +00:00
pClient->getName(), inet_ntoa(pConn->getPeerAddr().sin_addr), htons(pConn->getPeerAddr().sin_port));
2009-05-08 13:28:41 +00:00
// connection lost?
2010-03-28 18:58:01 +00:00
if (!pClient->isConnected())
2009-05-08 13:28:41 +00:00
OnClientDisconnect(pClient);
}
void C4Network2::OnClientConnect(C4Network2Client *pClient, C4Network2IOConnection *pConn)
{
// host: new client?
2010-03-28 18:58:01 +00:00
if (isHost())
2009-05-08 13:28:41 +00:00
{
// dynamic available?
2010-03-28 18:58:01 +00:00
if (!pClient->hasJoinData())
2009-05-08 13:28:41 +00:00
SendJoinData(pClient);
// notice lobby (doesn't do anything atm?)
C4GameLobby::MainDlg *pDlg = GetLobby();
if (isLobbyActive()) pDlg->OnClientConnect(pClient->getClient(), pConn);
}
// discover resources
ResList.OnClientConnect(pConn);
}
void C4Network2::OnClientDisconnect(C4Network2Client *pClient)
{
// league: Notify regular client disconnect within the game
if (pLeagueClient && (isHost() || pClient->isHost())) LeagueNotifyDisconnect(pClient->getID(), C4LDR_ConnectionFailed);
// host? Remove this client from the game.
2010-03-28 18:58:01 +00:00
if (isHost())
2009-05-08 13:28:41 +00:00
{
// log
LogSilentF(LoadResStr("IDS_NET_CLIENTDISCONNECTED"), pClient->getName()); // silent, because a duplicate message with disconnect reason will follow
// remove the client
Game.Clients.CtrlRemove(pClient->getClient(), LoadResStr("IDS_MSG_DISCONNECTED"));
// check status ack (disconnected client might be the last that was waited for)
CheckStatusAck();
// unreached pause/go? retry setting the state with current control tick
// (client might be the only one claiming to have the given control)
2010-03-28 18:58:01 +00:00
if (!fStatusReached)
if (Status.getState() == GS_Go || Status.getState() == GS_Pause)
2009-06-15 22:06:37 +00:00
ChangeGameStatus(Status.getState(), ::Control.ControlTick);
2009-05-08 13:28:41 +00:00
}
// host disconnected? Clear up
2010-03-28 18:58:01 +00:00
if (!isHost() && pClient->isHost())
2009-05-08 13:28:41 +00:00
{
StdStrBuf sMsg; sMsg.Format(LoadResStr("IDS_NET_HOSTDISCONNECTED"), pClient->getName());
Log(sMsg.getData());
// host connection lost: clear up everything
Game.RoundResults.EvaluateNetwork(C4RoundResults::NR_NetError, sMsg.getData());
Clear();
}
}
void C4Network2::SendJoinData(C4Network2Client *pClient)
{
2010-03-28 18:58:01 +00:00
if (pClient->hasJoinData()) return;
2009-05-08 13:28:41 +00:00
// host only, scenario must be available
assert(isHost());
// dynamic available?
2010-03-28 18:58:01 +00:00
if (ResDynamic.isNull() || iDynamicTick < ::Control.ControlTick)
2009-05-08 13:28:41 +00:00
{
fDynamicNeeded = true;
// add synchronization control (will callback, see C4Game::Synchronize)
2009-06-15 22:06:37 +00:00
::Control.DoInput(CID_Synchronize, new C4ControlSynchronize(false, true), CDT_Sync);
2009-05-08 13:28:41 +00:00
return;
}
// save his client ID
C4PacketJoinData JoinData;
JoinData.SetClientID(pClient->getID());
// save status into packet
JoinData.SetGameStatus(Status);
// parameters
JoinData.Parameters = Game.Parameters;
// core join data
JoinData.SetStartCtrlTick(iDynamicTick);
JoinData.SetDynamicCore(ResDynamic);
// send
pClient->SendMsg(MkC4NetIOPacket(PID_JoinData, JoinData));
// send addresses
Clients.SendAddresses(pClient->getMsgConn());
// flag client (he will have to accept the network status sent next)
pClient->SetStatus(NCS_Chasing);
2010-03-28 18:58:01 +00:00
if (!iLastChaseTargetUpdate) iLastChaseTargetUpdate = time(NULL);
2009-05-08 13:28:41 +00:00
}
C4Network2Res::Ref C4Network2::RetrieveRes(const C4Network2ResCore &Core, int32_t iTimeoutLen, const char *szResName, bool fWaitForCore)
{
C4GUI::ProgressDialog *pDlg = NULL;
bool fLog = false;
int32_t iProcess = -1;
C4TimeMilliseconds tTimeout = C4TimeMilliseconds::Now() + iTimeoutLen;
// wait for resource
2010-03-28 18:58:01 +00:00
while (isEnabled())
2009-05-08 13:28:41 +00:00
{
// find resource
2009-05-08 13:28:41 +00:00
C4Network2Res::Ref pRes = ResList.getRefRes(Core.getID());
// res not found?
2010-03-28 18:58:01 +00:00
if (!pRes)
{
2010-03-28 18:58:01 +00:00
if (Core.isNull())
2009-05-08 13:28:41 +00:00
{
// should wait for core?
2010-03-28 18:58:01 +00:00
if (!fWaitForCore) return NULL;
2009-05-08 13:28:41 +00:00
}
else
{
// start loading
pRes = ResList.AddByCore(Core);
}
}
2009-05-08 13:28:41 +00:00
// res found and loaded completely
2010-03-28 18:58:01 +00:00
else if (!pRes->isLoading())
2009-05-08 13:28:41 +00:00
{
// log
2010-03-28 18:58:01 +00:00
if (fLog) LogF(LoadResStr("IDS_NET_RECEIVED"), szResName, pRes->getCore().getFileName());
2009-05-08 13:28:41 +00:00
// return
if (pDlg) delete pDlg;
return pRes;
}
// check: progress?
2010-03-28 18:58:01 +00:00
if (pRes && pRes->getPresentPercent() != iProcess)
2009-05-08 13:28:41 +00:00
{
iProcess = pRes->getPresentPercent();
tTimeout = C4TimeMilliseconds::Now() + iTimeoutLen;
2009-05-08 13:28:41 +00:00
}
else
{
// if not: check timeout
if (C4TimeMilliseconds::Now() > tTimeout)
2009-05-08 13:28:41 +00:00
{
LogFatal(FormatString(LoadResStr("IDS_NET_ERR_RESTIMEOUT"), szResName).getData());
if (pDlg) delete pDlg;
return NULL;
}
}
// log
2010-03-28 18:58:01 +00:00
if (!fLog)
2009-05-08 13:28:41 +00:00
{
LogF(LoadResStr("IDS_NET_WAITFORRES"), szResName);
fLog = true;
}
// show progress dialog
2010-03-28 18:58:01 +00:00
if (!pDlg && !Console.Active && ::pGUI)
2009-05-08 13:28:41 +00:00
{
// create
pDlg = new C4GUI::ProgressDialog(FormatString(LoadResStr("IDS_NET_WAITFORRES"), szResName).getData(),
2010-03-28 18:58:01 +00:00
LoadResStr("IDS_NET_CAPTION"), 100, 0, C4GUI::Ico_NetWait);
2009-05-08 13:28:41 +00:00
// show dialog
2010-03-28 18:58:01 +00:00
if (!pDlg->Show(::pGUI, true)) { delete pDlg; return NULL; }
2009-05-08 13:28:41 +00:00
}
// wait
2010-03-28 18:58:01 +00:00
if (pDlg)
2009-05-08 13:28:41 +00:00
{
// set progress bar
pDlg->SetProgress(iProcess);
// execute (will do message handling)
2010-03-28 18:58:01 +00:00
if (!pDlg->Execute())
2009-05-08 13:28:41 +00:00
{ if (pDlg) delete pDlg; return NULL; }
// aborted?
2010-03-28 18:58:01 +00:00
if (pDlg->IsAborted()) break;
2009-05-08 13:28:41 +00:00
}
else
{
if (!Application.ScheduleProcs(tTimeout - C4TimeMilliseconds::Now()))
2009-05-08 13:28:41 +00:00
{ return NULL; }
}
}
// aborted
delete pDlg;
return NULL;
}
bool C4Network2::CreateDynamic(bool fInit)
{
2010-03-28 18:58:01 +00:00
if (!isHost()) return false;
2009-05-08 13:28:41 +00:00
// remove all existing dynamic data
RemoveDynamic();
// log
Log(LoadResStr("IDS_NET_SAVING"));
// compose file name
char szDynamicBase[_MAX_PATH+1], szDynamicFilename[_MAX_PATH+1];
sprintf(szDynamicBase, Config.AtNetworkPath("Dyn%s"), GetFilename(Game.ScenarioFilename), _MAX_PATH);
2010-03-28 18:58:01 +00:00
if (!ResList.FindTempResFileName(szDynamicBase, szDynamicFilename))
2010-01-25 04:00:59 +00:00
Log(LoadResStr("IDS_NET_SAVE_ERR_CREATEDYNFILE"));
2009-05-08 13:28:41 +00:00
// save dynamic data
C4GameSaveNetwork SaveGame(fInit);
if (!SaveGame.Save(szDynamicFilename) || !SaveGame.Close())
{ Log(LoadResStr("IDS_NET_SAVE_ERR_SAVEDYNFILE")); return false; }
// add resource
2009-05-08 13:28:41 +00:00
C4Network2Res::Ref pRes = ResList.AddByFile(szDynamicFilename, true, NRT_Dynamic);
2010-03-28 18:58:01 +00:00
if (!pRes) { Log(LoadResStr("IDS_NET_SAVE_ERR_ADDDYNDATARES")); return false; }
2009-05-08 13:28:41 +00:00
// save
ResDynamic = pRes->getCore();
2009-06-15 22:06:37 +00:00
iDynamicTick = ::Control.getNextControlTick();
2009-05-08 13:28:41 +00:00
fDynamicNeeded = false;
// ok
return true;
}
void C4Network2::RemoveDynamic()
{
C4Network2Res::Ref pRes = ResList.getRefRes(ResDynamic.getID());
2010-03-28 18:58:01 +00:00
if (pRes) pRes->Remove();
2009-05-08 13:28:41 +00:00
ResDynamic.Clear();
iDynamicTick = -1;
}
bool C4Network2::isFrozen() const
{
// "frozen" means all clients are garantueed to be in the same tick.
// This is only the case if the game is not started yet (lobby) or the
// tick has been ensured (pause) and acknowledged by all joined clients.
// Note unjoined clients must be ignored here - they can't be faster than
// the host, anyway.
2010-03-28 18:58:01 +00:00
if (Status.getState() == GS_Lobby) return true;
if (Status.getState() == GS_Pause && fStatusAck) return true;
2009-05-08 13:28:41 +00:00
return false;
}
bool C4Network2::ChangeGameStatus(C4NetGameState enState, int32_t iTargetCtrlTick, int32_t iCtrlMode)
{
// change game status, announce. Can only be done by host.
2010-03-28 18:58:01 +00:00
if (!isHost()) return false;
2009-05-08 13:28:41 +00:00
// set status
Status.Set(enState, iTargetCtrlTick);
// update reference
InvalidateReference();
// control mode change?
2010-03-28 18:58:01 +00:00
if (iCtrlMode >= 0) Status.SetCtrlMode(iCtrlMode);
2009-05-08 13:28:41 +00:00
// log
LogSilentF("Network: going into status %s (tick %d)", Status.getStateName(), iTargetCtrlTick);
2009-05-08 13:28:41 +00:00
// set flags
Clients.ResetReady();
fStatusReached = fStatusAck = false;
// send new status to all clients
Clients.BroadcastMsgToClients(MkC4NetIOPacket(PID_Status, Status));
// check reach/ack
CheckStatusReached();
// ok
return true;
}
void C4Network2::CheckStatusReached(bool fFromFinalInit)
{
// already reached?
2010-03-28 18:58:01 +00:00
if (fStatusReached) return;
if (Status.getState() == GS_Lobby)
2009-05-08 13:28:41 +00:00
fStatusReached = fLobbyRunning;
// game go / pause: control must be initialized and target tick reached
2010-03-28 18:58:01 +00:00
else if (Status.getState() == GS_Go || Status.getState() == GS_Pause)
2009-05-08 13:28:41 +00:00
{
2010-03-28 18:58:01 +00:00
if (Game.IsRunning || fFromFinalInit)
{
2009-05-08 13:28:41 +00:00
// Make sure we have reached the tick and the control queue is empty (except for chasing)
2010-03-28 18:58:01 +00:00
if (::Control.CtrlTickReached(Status.getTargetCtrlTick()) &&
(fChasing || !pControl->CtrlReady(::Control.ControlTick)))
2009-05-08 13:28:41 +00:00
fStatusReached = true;
else
{
2009-05-08 13:28:41 +00:00
// run ctrl so the tick can be reached
pControl->SetRunning(true, Status.getTargetCtrlTick());
Game.HaltCount = 0;
Console.UpdateHaltCtrls(!! Game.HaltCount);
}
}
2009-05-08 13:28:41 +00:00
}
2010-03-28 18:58:01 +00:00
if (!fStatusReached) return;
2009-05-08 13:28:41 +00:00
// call handler
OnStatusReached();
// host?
2010-03-28 18:58:01 +00:00
if (isHost())
2009-05-08 13:28:41 +00:00
// all clients ready?
CheckStatusAck();
else
{
2009-06-15 22:06:37 +00:00
Status.SetTargetTick(::Control.ControlTick);
2009-05-08 13:28:41 +00:00
// send response to host
Clients.SendMsgToHost(MkC4NetIOPacket(PID_StatusAck, Status));
// do delayed activation request
2010-03-28 18:58:01 +00:00
if (fDelayedActivateReq)
{
2009-05-08 13:28:41 +00:00
fDelayedActivateReq = false;
RequestActivate();
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
}
}
void C4Network2::CheckStatusAck()
{
// host only
2010-03-28 18:58:01 +00:00
if (!isHost()) return;
2009-05-08 13:28:41 +00:00
// status must be reached and not yet acknowledged
2010-03-28 18:58:01 +00:00
if (!fStatusReached || fStatusAck) return;
2009-05-08 13:28:41 +00:00
// all clients ready?
2010-03-28 18:58:01 +00:00
if ((fStatusAck = Clients.AllClientsReady()))
2009-05-08 13:28:41 +00:00
{
// pause/go: check for sync control that can be executed
2010-03-28 18:58:01 +00:00
if (Status.getState() == GS_Go || Status.getState() == GS_Pause)
2009-05-08 13:28:41 +00:00
pControl->ExecSyncControl();
// broadcast ack
Clients.BroadcastMsgToClients(MkC4NetIOPacket(PID_StatusAck, Status));
// handle
OnStatusAck();
}
}
void C4Network2::OnStatusReached()
{
// stop ctrl, wait for ack
2010-03-28 18:58:01 +00:00
if (pControl->IsEnabled())
{
Console.UpdateHaltCtrls(!!++Game.HaltCount);
2010-03-28 18:58:01 +00:00
pControl->SetRunning(false);
}
2009-05-08 13:28:41 +00:00
}
void C4Network2::OnStatusAck()
{
// log it
LogSilentF("Network: status %s (tick %d) reached", Status.getStateName(), Status.getTargetCtrlTick());
// pause?
2010-03-28 18:58:01 +00:00
if (Status.getState() == GS_Pause)
{
// set halt-flag (show hold message)
Console.UpdateHaltCtrls(!!++Game.HaltCount);
}
// go?
2010-03-28 18:58:01 +00:00
if (Status.getState() == GS_Go)
2009-05-08 13:28:41 +00:00
{
// set mode
pControl->SetCtrlMode(static_cast<C4GameControlNetworkMode>(Status.getCtrlMode()));
// notify player list of reached status - will add some input to the queue
Players.OnStatusGoReached();
// start ctrl
pControl->SetRunning(true);
// reset halt-flag
2009-05-08 13:28:41 +00:00
Game.HaltCount = 0;
Console.UpdateHaltCtrls(!!Game.HaltCount);
2009-05-08 13:28:41 +00:00
}
}
void C4Network2::RequestActivate()
{
// neither observer nor activated?
2010-03-28 18:58:01 +00:00
if (Game.Clients.getLocal()->isObserver() || Game.Clients.getLocal()->isActivated())
{
tLastActivateRequest = C4TimeMilliseconds::NegativeInfinity;
return;
}
2009-05-08 13:28:41 +00:00
// host? just do it
2010-03-28 18:58:01 +00:00
if (fHost)
2009-05-08 13:28:41 +00:00
{
// activate him
2009-06-15 22:06:37 +00:00
::Control.DoInput(CID_ClientUpdate,
2010-03-28 18:58:01 +00:00
new C4ControlClientUpdate(C4ClientIDHost, CUT_Activate, true),
CDT_Sync);
2009-05-08 13:28:41 +00:00
return;
}
// ensure interval
if(C4TimeMilliseconds::Now() < tLastActivateRequest + C4NetActivationReqInterval)
return;
2009-05-08 13:28:41 +00:00
// status not reached yet? May be chasing, let's delay this.
2010-03-28 18:58:01 +00:00
if (!fStatusReached)
{
2009-05-08 13:28:41 +00:00
fDelayedActivateReq = true;
return;
2010-03-28 18:58:01 +00:00
}
// request
Clients.SendMsgToHost(MkC4NetIOPacket(PID_ClientActReq, C4PacketActivateReq(Game.FrameCounter)));
// store time
tLastActivateRequest = C4TimeMilliseconds::Now();
2009-05-08 13:28:41 +00:00
}
void C4Network2::DeactivateInactiveClients()
{
// host only
2010-03-28 18:58:01 +00:00
if (!isHost()) return;
// update activity
Clients.UpdateClientActivity();
// find clients to deactivate
2010-03-28 18:58:01 +00:00
for (C4Network2Client *pClient = Clients.GetNextClient(NULL); pClient; pClient = Clients.GetNextClient(pClient))
if (!pClient->isLocal() && pClient->isActivated())
if (pClient->getLastActivity() + C4NetDeactivationDelay < Game.FrameCounter)
2009-06-15 22:06:37 +00:00
::Control.DoInput(CID_ClientUpdate, new C4ControlClientUpdate(pClient->getID(), CUT_Activate, false), CDT_Sync);
2009-05-08 13:28:41 +00:00
}
void C4Network2::UpdateChaseTarget()
{
// no chasing clients?
C4Network2Client *pClient;
2010-03-28 18:58:01 +00:00
for (pClient = Clients.GetNextClient(NULL); pClient; pClient = Clients.GetNextClient(pClient))
if (pClient->isChasing())
2009-05-08 13:28:41 +00:00
break;
2010-03-28 18:58:01 +00:00
if (!pClient)
2009-05-08 13:28:41 +00:00
{
iLastChaseTargetUpdate = 0;
return;
}
// not time for an update?
2010-03-28 18:58:01 +00:00
if (!iLastChaseTargetUpdate || long(iLastChaseTargetUpdate + C4NetChaseTargetUpdateInterval) > time(NULL))
2009-05-08 13:28:41 +00:00
return;
// copy status, set current tick
C4Network2Status ChaseTarget = Status;
2009-06-15 22:06:37 +00:00
ChaseTarget.SetTargetTick(::Control.ControlTick);
2009-05-08 13:28:41 +00:00
// send to everyone involved
2010-03-28 18:58:01 +00:00
for (pClient = Clients.GetNextClient(NULL); pClient; pClient = Clients.GetNextClient(pClient))
if (pClient->isChasing())
2009-05-08 13:28:41 +00:00
pClient->SendMsg(MkC4NetIOPacket(PID_Status, ChaseTarget));
iLastChaseTargetUpdate = time(NULL);
}
void C4Network2::LeagueGameEvaluate(const char *szRecordName, const BYTE *pRecordSHA)
{
// already off?
if (!pLeagueClient) return;
// already evaluated?
if (fLeagueEndSent) return;
// end
LeagueEnd(szRecordName, pRecordSHA);
}
void C4Network2::LeagueSignupDisable()
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// already off?
if (!pLeagueClient) return;
// no post-disable if league is active
if (pLeagueClient && Game.Parameters.isLeague()) return;
// signup end
LeagueEnd(); DeinitLeague();
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
bool C4Network2::LeagueSignupEnable()
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// already running?
if (pLeagueClient) return true;
// Start it!
if (InitLeague(NULL) && LeagueStart(NULL)) return true;
// Failure :'(
DeinitLeague();
return false;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
void C4Network2::InvalidateReference()
{
// Update both local and league reference as soon as possible
iLastReferenceUpdate = 0;
iLeagueUpdateDelay = C4NetMinLeagueUpdateInterval;
}
bool C4Network2::InitLeague(bool *pCancel)
{
2010-03-28 18:58:01 +00:00
if (fHost)
2009-05-08 13:28:41 +00:00
{
// Clear parameters
MasterServerAddress.Clear();
Game.Parameters.League.Clear();
Game.Parameters.LeagueAddress.Clear();
if (pLeagueClient) delete pLeagueClient; pLeagueClient = NULL;
// Not needed?
2010-03-28 18:58:01 +00:00
if (!Config.Network.MasterServerSignUp && !Config.Network.LeagueServerSignUp)
2009-05-08 13:28:41 +00:00
return true;
// Save address
MasterServerAddress = Config.Network.GetLeagueServerAddress();
if (Config.Network.LeagueServerSignUp)
{
Game.Parameters.LeagueAddress = MasterServerAddress;
// enforce some league rules
Game.Parameters.EnforceLeagueRules(&Game.C4S);
}
}
else
{
// Get league server from parameters
MasterServerAddress = Game.Parameters.LeagueAddress;
// Not needed?
2010-03-28 18:58:01 +00:00
if (!MasterServerAddress.getLength())
2009-05-08 13:28:41 +00:00
return true;
}
// Init
pLeagueClient = new C4LeagueClient();
2010-03-28 18:58:01 +00:00
if (!pLeagueClient->Init() ||
!pLeagueClient->SetServer(MasterServerAddress.getData()))
{
// Log message
StdStrBuf Message = FormatString(LoadResStr("IDS_NET_ERR_LEAGUEINIT"), pLeagueClient->GetError());
LogFatal(Message.getData());
// Clear league
delete pLeagueClient; pLeagueClient = NULL;
2010-03-28 18:58:01 +00:00
if (fHost)
Game.Parameters.LeagueAddress.Clear();
// Show message, allow abort
2009-05-08 13:28:41 +00:00
bool fResult = true;
if (!Application.isEditor)
fResult = ::pGUI->ShowMessageModal(Message.getData(), LoadResStr("IDS_NET_ERR_LEAGUE"),
2010-03-28 18:58:01 +00:00
(pCancel ? C4GUI::MessageDialog::btnOK : 0) | C4GUI::MessageDialog::btnAbort,
C4GUI::Ico_Error);
if (pCancel) *pCancel = fResult;
return false;
}
2009-05-08 13:28:41 +00:00
// Add to message loop
Application.Add(pLeagueClient);
// OK
return true;
2009-05-08 13:28:41 +00:00
}
void C4Network2::DeinitLeague()
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// league clear
MasterServerAddress.Clear();
Game.Parameters.League.Clear();
Game.Parameters.LeagueAddress.Clear();
if (pLeagueClient)
2010-03-28 18:58:01 +00:00
{
Application.Remove(pLeagueClient);
delete pLeagueClient; pLeagueClient = NULL;
2009-05-08 13:28:41 +00:00
}
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
bool C4Network2::LeagueStart(bool *pCancel)
{
// Not needed?
2010-03-28 18:58:01 +00:00
if (!pLeagueClient || !fHost)
return true;
2009-05-08 13:28:41 +00:00
// Default
2010-03-28 18:58:01 +00:00
if (pCancel) *pCancel = true;
2009-05-08 13:28:41 +00:00
// Do update
C4Network2Reference Ref;
Ref.InitLocal();
2010-03-28 18:58:01 +00:00
if (!pLeagueClient->Start(Ref))
{
// Log message
StdStrBuf Message = FormatString(LoadResStr("IDS_NET_ERR_LEAGUE_STARTGAME"), pLeagueClient->GetError());
LogFatal(Message.getData());
// Show message
if (!Application.isEditor)
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// Show option to cancel, if possible
2009-06-05 16:53:56 +00:00
bool fResult = ::pGUI->ShowMessageModal(
2010-03-28 18:58:01 +00:00
Message.getData(),
LoadResStr("IDS_NET_ERR_LEAGUE"),
pCancel ? (C4GUI::MessageDialog::btnOK | C4GUI::MessageDialog::btnAbort) : C4GUI::MessageDialog::btnOK,
C4GUI::Ico_Error);
if (pCancel)
2009-05-08 13:28:41 +00:00
*pCancel = !fResult;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
// Failed
return false;
}
2009-05-08 13:28:41 +00:00
// We have an internet connection, so let's punch the master server here in order to open an udp port
C4NetIO::addr_t PuncherAddr;
2010-03-28 18:58:01 +00:00
if (ResolveAddress(Config.Network.PuncherAddress, &PuncherAddr, C4NetStdPortPuncher))
2009-05-08 13:28:41 +00:00
NetIO.Punch(PuncherAddr);
// Let's wait for response
StdStrBuf Message = FormatString(LoadResStr("IDS_NET_LEAGUE_REGGAME"), pLeagueClient->getServerName());
Log(Message.getData());
// Set up a dialog
2009-05-08 13:28:41 +00:00
C4GUI::MessageDialog *pDlg = NULL;
if (!Application.isEditor)
2009-05-08 13:28:41 +00:00
{
// create & show
pDlg = new C4GUI::MessageDialog(Message.getData(), LoadResStr("IDS_NET_LEAGUE_STARTGAME"),
2010-03-28 18:58:01 +00:00
C4GUI::MessageDialog::btnAbort, C4GUI::Ico_NetWait, C4GUI::MessageDialog::dsRegular);
if (!pDlg || !pDlg->Show(::pGUI, true)) return false;
2009-05-08 13:28:41 +00:00
}
// Wait for response
2010-03-28 18:58:01 +00:00
while (pLeagueClient->isBusy())
{
// Execute GUI
2010-03-28 18:58:01 +00:00
if (!Application.ScheduleProcs() ||
(pDlg && pDlg->IsAborted()))
{
2010-03-28 18:58:01 +00:00
// Clear up
if (pDlg) delete pDlg;
2009-05-08 13:28:41 +00:00
return false;
}
// Check if league server has responded
2010-03-28 18:58:01 +00:00
if (!pLeagueClient->Execute(100))
break;
}
// Close dialog
if (pDlg)
2009-05-08 13:28:41 +00:00
{
pDlg->Close(true);
delete pDlg;
}
// Error?
StdStrBuf LeagueServerMessage, League, StreamingAddr;
int32_t Seed = Game.RandomSeed, MaxPlayersLeague = 0;
2010-03-28 18:58:01 +00:00
if (!pLeagueClient->isSuccess() ||
!pLeagueClient->GetStartReply(&LeagueServerMessage, &League, &StreamingAddr, &Seed, &MaxPlayersLeague))
{
const char *pError = pLeagueClient->GetError() ? pLeagueClient->GetError() :
LeagueServerMessage.getLength() ? LeagueServerMessage.getData() :
2010-03-28 18:58:01 +00:00
LoadResStr("IDS_NET_ERR_LEAGUE_EMPTYREPLY");
StdStrBuf Message = FormatString(LoadResStr("IDS_NET_ERR_LEAGUE_REGGAME"), pError);
// Log message
Log(Message.getData());
// Show message
if (!Application.isEditor)
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// Show option to cancel, if possible
2009-06-05 16:53:56 +00:00
bool fResult = ::pGUI->ShowMessageModal(
2010-03-28 18:58:01 +00:00
Message.getData(),
LoadResStr("IDS_NET_ERR_LEAGUE"),
pCancel ? (C4GUI::MessageDialog::btnOK | C4GUI::MessageDialog::btnAbort) : C4GUI::MessageDialog::btnOK,
C4GUI::Ico_Error);
if (pCancel)
2009-05-08 13:28:41 +00:00
*pCancel = !fResult;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
// Failed
return false;
}
2009-05-08 13:28:41 +00:00
// Show message
2010-03-28 18:58:01 +00:00
if (LeagueServerMessage.getLength())
2009-05-08 13:28:41 +00:00
{
StdStrBuf Message = FormatString(LoadResStr("IDS_MSG_LEAGUEGAMESIGNUP"), pLeagueClient->getServerName(), LeagueServerMessage.getData());
// Log message
Log(Message.getData());
2009-05-08 13:28:41 +00:00
// Show message
if (!Application.isEditor)
2009-05-08 13:28:41 +00:00
{
// Show option to cancel, if possible
2009-06-05 16:53:56 +00:00
bool fResult = ::pGUI->ShowMessageModal(
2010-03-28 18:58:01 +00:00
Message.getData(),
LoadResStr("IDS_NET_ERR_LEAGUE"),
pCancel ? (C4GUI::MessageDialog::btnOK | C4GUI::MessageDialog::btnAbort) : C4GUI::MessageDialog::btnOK,
C4GUI::Ico_Error);
if (pCancel)
2009-05-08 13:28:41 +00:00
*pCancel = !fResult;
2010-03-28 18:58:01 +00:00
if (!fResult)
2009-05-08 13:28:41 +00:00
{
LeagueEnd(); DeinitLeague();
return false;
}
}
}
// Set game parameters for league game
Game.Parameters.League = League;
Game.RandomSeed = Seed;
2010-03-28 18:58:01 +00:00
if (MaxPlayersLeague)
2009-05-08 13:28:41 +00:00
Game.Parameters.MaxPlayers = MaxPlayersLeague;
2010-03-28 18:58:01 +00:00
if (!League.getLength())
{
2009-05-08 13:28:41 +00:00
Game.Parameters.LeagueAddress.Clear();
Game.Parameters.StreamAddress.Clear();
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
else
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
Game.Parameters.StreamAddress = StreamingAddr;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
// All ok
2009-05-08 13:28:41 +00:00
fLeagueEndSent = false;
return true;
2009-05-08 13:28:41 +00:00
}
bool C4Network2::LeagueUpdate()
{
// Not needed?
2010-03-28 18:58:01 +00:00
if (!pLeagueClient || !fHost)
return true;
2009-05-08 13:28:41 +00:00
// League client currently busy?
2010-03-28 18:58:01 +00:00
if (pLeagueClient->isBusy())
2009-05-08 13:28:41 +00:00
return true;
// Create reference
C4Network2Reference Ref;
Ref.InitLocal();
2009-05-08 13:28:41 +00:00
// Do update
2010-03-28 18:58:01 +00:00
if (!pLeagueClient->Update(Ref))
{
// Log
LogF(LoadResStr("IDS_NET_ERR_LEAGUE_UPDATEGAME"), pLeagueClient->GetError());
return false;
}
2009-05-08 13:28:41 +00:00
// Timing
iLastLeagueUpdate = time(NULL);
2009-05-08 13:28:41 +00:00
iLeagueUpdateDelay = Config.Network.MasterReferencePeriod;
return true;
2009-05-08 13:28:41 +00:00
}
bool C4Network2::LeagueUpdateProcessReply()
{
// safety: A reply must be present
assert(pLeagueClient);
assert(fHost);
assert(!pLeagueClient->isBusy());
assert(pLeagueClient->getCurrentAction() == C4LA_Update);
// check reply success
C4ClientPlayerInfos PlayerLeagueInfos;
StdStrBuf LeagueServerMessage;
bool fSucc = pLeagueClient->isSuccess() && pLeagueClient->GetUpdateReply(&LeagueServerMessage, &PlayerLeagueInfos);
pLeagueClient->ResetCurrentAction();
2010-03-28 18:58:01 +00:00
if (!fSucc)
{
const char *pError = pLeagueClient->GetError() ? pLeagueClient->GetError() :
LeagueServerMessage.getLength() ? LeagueServerMessage.getData() :
2010-03-28 18:58:01 +00:00
LoadResStr("IDS_NET_ERR_LEAGUE_EMPTYREPLY");
StdStrBuf Message = FormatString(LoadResStr("IDS_NET_ERR_LEAGUE_UPDATEGAME"), pError);
// Show message - no dialog, because it's not really fatal and might happen in the running game
Log(Message.getData());
2009-05-08 13:28:41 +00:00
return false;
}
2009-05-08 13:28:41 +00:00
// evaluate reply: Transfer data to players
// Take round results
C4PlayerInfoList &TargetList = Game.PlayerInfos;
C4ClientPlayerInfos *pInfos; C4PlayerInfo *pInfo, *pResultInfo;
2010-03-28 18:58:01 +00:00
for (int iClient = 0; (pInfos = TargetList.GetIndexedInfo(iClient)); iClient++)
for (int iInfo = 0; (pInfo = pInfos->GetPlayerInfo(iInfo)); iInfo++)
if ((pResultInfo = PlayerLeagueInfos.GetPlayerInfoByID(pInfo->GetID())))
{
2009-05-08 13:28:41 +00:00
int32_t iLeagueProjectedGain = pResultInfo->GetLeagueProjectedGain();
if (iLeagueProjectedGain != pInfo->GetLeagueProjectedGain())
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
pInfo->SetLeagueProjectedGain(iLeagueProjectedGain);
pInfos->SetUpdated();
}
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
// transfer info update to other clients
Players.SendUpdatedPlayers();
2009-05-08 13:28:41 +00:00
// if lobby is open, notify lobby of updated players
if (pLobby) pLobby->OnPlayersChange();
// OMFG SUCCESS!
return true;
}
bool C4Network2::LeagueEnd(const char *szRecordName, const BYTE *pRecordSHA)
{
C4RoundResultsPlayers RoundResults;
StdStrBuf sResultMessage;
bool fIsError = true;
// Not needed?
2010-03-28 18:58:01 +00:00
if (!pLeagueClient || !fHost || fLeagueEndSent)
2009-05-08 13:28:41 +00:00
return true;
// Make sure league client is available
LeagueWaitNotBusy();
// Try until either aborted or successful
const int MAX_RETRIES = 10;
2010-03-28 18:58:01 +00:00
for (int iRetry = 0; iRetry < MAX_RETRIES; iRetry++)
2009-05-08 13:28:41 +00:00
{
// Do update
C4Network2Reference Ref;
2009-06-05 15:19:46 +00:00
Ref.InitLocal();
2010-03-28 18:58:01 +00:00
if (!pLeagueClient->End(Ref, szRecordName, pRecordSHA))
2009-05-08 13:28:41 +00:00
{
// Log message
sResultMessage = FormatString(LoadResStr("IDS_NET_ERR_LEAGUE_FINISHGAME"), pLeagueClient->GetError());
Log(sResultMessage.getData());
// Show message, allow retry
if (Application.isEditor) break;
2009-06-05 16:53:56 +00:00
bool fRetry = ::pGUI->ShowMessageModal(sResultMessage.getData(), LoadResStr("IDS_NET_ERR_LEAGUE"),
2010-03-28 18:58:01 +00:00
C4GUI::MessageDialog::btnRetryAbort, C4GUI::Ico_Error);
2009-05-08 13:28:41 +00:00
if (fRetry) continue;
break;
}
// Let's wait for response
StdStrBuf Message = FormatString(LoadResStr("IDS_NET_LEAGUE_SENDRESULT"), pLeagueClient->getServerName());
Log(Message.getData());
// Wait for response
2010-03-28 18:58:01 +00:00
while (pLeagueClient->isBusy())
2009-05-08 13:28:41 +00:00
{
// Check if league server has responded
2010-03-28 18:58:01 +00:00
if (!pLeagueClient->Execute(100))
break;
2009-05-08 13:28:41 +00:00
}
// Error?
StdStrBuf LeagueServerMessage;
2010-03-28 18:58:01 +00:00
if (!pLeagueClient->isSuccess() || !pLeagueClient->GetEndReply(&LeagueServerMessage, &RoundResults))
2009-05-08 13:28:41 +00:00
{
const char *pError = pLeagueClient->GetError() ? pLeagueClient->GetError() :
LeagueServerMessage.getLength() ? LeagueServerMessage.getData() :
2010-03-28 18:58:01 +00:00
LoadResStr("IDS_NET_ERR_LEAGUE_EMPTYREPLY");
2009-05-08 13:28:41 +00:00
sResultMessage.Take(FormatString(LoadResStr("IDS_NET_ERR_LEAGUE_SENDRESULT"), pError));
if (Application.isEditor) continue;
2009-05-08 13:28:41 +00:00
// Only retry if we didn't get an answer from the league server
bool fRetry = !pLeagueClient->isSuccess();
2009-06-05 16:53:56 +00:00
fRetry = ::pGUI->ShowMessageModal(sResultMessage.getData(), LoadResStr("IDS_NET_ERR_LEAGUE"),
2010-03-28 18:58:01 +00:00
fRetry ? C4GUI::MessageDialog::btnRetryAbort : C4GUI::MessageDialog::btnAbort,
C4GUI::Ico_Error);
2009-05-08 13:28:41 +00:00
if (fRetry) continue;
}
else
{
// All OK!
sResultMessage.Copy(LoadResStr(Game.Parameters.isLeague() ? "IDS_MSG_LEAGUEEVALUATIONSUCCESSFU" : "IDS_MSG_INTERNETGAMEEVALUATED"));
fIsError = false;
}
// Done
break;
}
// Show message
Log(sResultMessage.getData());
// Take round results
Game.RoundResults.EvaluateLeague(sResultMessage.getData(), !fIsError, RoundResults);
// Send round results to other clients
C4PacketLeagueRoundResults LeagueUpdatePacket(sResultMessage.getData(), !fIsError, RoundResults);
Clients.BroadcastMsgToClients(MkC4NetIOPacket(PID_LeagueRoundResults, LeagueUpdatePacket));
// All done
fLeagueEndSent = true;
return true;
}
bool C4Network2::LeaguePlrAuth(C4PlayerInfo *pInfo)
{
// Not possible?
2010-03-28 18:58:01 +00:00
if (!pLeagueClient)
return false;
2009-05-08 13:28:41 +00:00
// Make sure league client is avilable
LeagueWaitNotBusy();
// Official league?
bool fOfficialLeague = SEqual(pLeagueClient->getServerName(), "clonk.de");
// Try to auth with WebCode, if it's an official league server and we have valid registration information
bool fWebCode = fOfficialLeague && *Config.GetRegistrationData("Cuid");
StdStrBuf Account, Password;
bool fRegister = false;
2010-03-28 18:58:01 +00:00
for (;;)
2009-05-08 13:28:41 +00:00
{
StdStrBuf NewAccount, NewPassword;
// Default authentication data
if (!Account.getLength()) Account.Copy(Config.GetRegistrationData("Cuid"));
// Try first auth with local CUID and WebCode
2010-03-28 18:58:01 +00:00
if (fWebCode)
2009-05-08 13:28:41 +00:00
{
// Default authentication data
Password.Copy(Config.GetRegistrationData("WebCode"));
};
// Ask for registration information
2010-03-28 18:58:01 +00:00
if (fRegister)
2009-05-08 13:28:41 +00:00
{
// Use local nick as default
NewAccount.Copy(Config.Network.Nick);
2010-03-28 18:58:01 +00:00
if (Config.Network.Nick.getLength() == 0)
2009-05-08 13:28:41 +00:00
NewAccount.Copy(Config.GetRegistrationData("Nick"));
2010-03-28 18:58:01 +00:00
if (!C4LeagueSignupDialog::ShowModal(pInfo->GetName(), "", pLeagueClient->getServerName(), &NewAccount, &NewPassword, !fOfficialLeague, true))
2009-05-08 13:28:41 +00:00
return false;
2010-03-28 18:58:01 +00:00
if (!NewPassword.getLength())
2009-05-08 13:28:41 +00:00
NewPassword.Copy(Password);
}
else if (!fWebCode)
{
// CUID is default for account, no default password
Password.Clear();
// ask for account
2010-03-28 18:58:01 +00:00
if (!C4LeagueSignupDialog::ShowModal(pInfo->GetName(), "", pLeagueClient->getServerName(), &Account, &Password, !fOfficialLeague, false))
2009-05-08 13:28:41 +00:00
return false;
}
// safety (modal dlg may have deleted network)
if (!pLeagueClient) return false;
// Send authentication request
2010-03-28 18:58:01 +00:00
if (!pLeagueClient->Auth(*pInfo, Account.getData(), Password.getData(), NewAccount.getLength() ? NewAccount.getData() : NULL, NewPassword.getLength() ? NewPassword.getData() : NULL))
2009-05-08 13:28:41 +00:00
return false;
// safety (modal dlg may have deleted network)
if (!pLeagueClient) return false;
// Wait for a response
StdStrBuf Message = FormatString(LoadResStr("IDS_MSG_TRYLEAGUESIGNUP"), pInfo->GetName(), Account.getData(), pLeagueClient->getServerName());
Log(Message.getData());
// Set up a dialog
C4GUI::MessageDialog *pDlg = NULL;
if (!Application.isEditor)
2009-05-08 13:28:41 +00:00
{
// create & show
pDlg = new C4GUI::MessageDialog(Message.getData(), LoadResStr("IDS_DLG_LEAGUESIGNUP"), C4GUI::MessageDialog::btnAbort, C4GUI::Ico_NetWait, C4GUI::MessageDialog::dsRegular);
2010-03-28 18:58:01 +00:00
if (!pDlg || !pDlg->Show(::pGUI, true)) return false;
2009-05-08 13:28:41 +00:00
}
// Wait for response
2010-03-28 18:58:01 +00:00
while (pLeagueClient->isBusy())
2009-05-08 13:28:41 +00:00
{
// Execute GUI
2010-03-28 18:58:01 +00:00
if (!Application.ScheduleProcs() ||
(pDlg && pDlg->IsAborted()))
2009-05-08 13:28:41 +00:00
{
// Clear up
if (pDlg) delete pDlg;
2009-05-08 13:28:41 +00:00
return false;
}
// Check if league server has responded
2010-03-28 18:58:01 +00:00
if (!pLeagueClient->Execute(0))
2009-05-08 13:28:41 +00:00
break;
}
// Close dialog
if (pDlg)
2009-05-08 13:28:41 +00:00
{
pDlg->Close(true);
delete pDlg;
}
// Success?
StdStrBuf AUID, AccountMaster; bool fUnregistered = false;
2010-03-28 18:58:01 +00:00
if (pLeagueClient->GetAuthReply(&Message, &AUID, &AccountMaster, &fUnregistered))
2009-05-08 13:28:41 +00:00
{
// Set AUID
pInfo->SetAuthID(AUID.getData());
// Show welcome message, if any
bool fSuccess;
2010-03-28 18:58:01 +00:00
if (Message.getLength())
2009-06-05 16:53:56 +00:00
fSuccess = ::pGUI->ShowMessageModal(
Message.getData(), LoadResStr("IDS_DLG_LEAGUESIGNUPCONFIRM"),
2010-03-28 18:58:01 +00:00
C4GUI::MessageDialog::btnOKAbort, C4GUI::Ico_Ex_League);
else if (AccountMaster.getLength())
2009-06-05 16:53:56 +00:00
fSuccess = ::pGUI->ShowMessageModal(
2010-03-28 18:58:01 +00:00
FormatString(LoadResStr("IDS_MSG_LEAGUEPLAYERSIGNUPAS"), pInfo->GetName(), AccountMaster.getData(), pLeagueClient->getServerName()).getData(), LoadResStr("IDS_DLG_LEAGUESIGNUPCONFIRM"),
C4GUI::MessageDialog::btnOKAbort, C4GUI::Ico_Ex_League);
2009-05-08 13:28:41 +00:00
else
2009-06-05 16:53:56 +00:00
fSuccess = ::pGUI->ShowMessageModal(
2010-03-28 18:58:01 +00:00
FormatString(LoadResStr("IDS_MSG_LEAGUEPLAYERSIGNUP"), pInfo->GetName(), pLeagueClient->getServerName()).getData(), LoadResStr("IDS_DLG_LEAGUESIGNUPCONFIRM"),
C4GUI::MessageDialog::btnOKAbort, C4GUI::Ico_Ex_League);
2009-05-08 13:28:41 +00:00
// Approved?
2010-03-28 18:58:01 +00:00
if (fSuccess)
2009-05-08 13:28:41 +00:00
// Done
return true;
else
// Sign-up was cancelled by user
2009-06-05 16:53:56 +00:00
::pGUI->ShowMessageModal(FormatString(LoadResStr("IDS_MSG_LEAGUESIGNUPCANCELLED"), pInfo->GetName()).getData(), LoadResStr("IDS_DLG_LEAGUESIGNUP"), C4GUI::MessageDialog::btnOK, C4GUI::Ico_Notify);
2009-05-08 13:28:41 +00:00
}
else
{
// Error with first-time registration or manual password entry
2010-03-28 18:58:01 +00:00
if ((!fWebCode && !fUnregistered) || fRegister)
2009-05-08 13:28:41 +00:00
{
LogF(LoadResStr("IDS_MSG_LEAGUESIGNUPERROR"), Message.getData());
2009-06-05 16:53:56 +00:00
::pGUI->ShowMessageModal(FormatString(LoadResStr("IDS_MSG_LEAGUESERVERMSG"), Message.getData()).getData(), LoadResStr("IDS_DLG_LEAGUESIGNUPFAILED"), C4GUI::MessageDialog::btnOK, C4GUI::Ico_Error);
2009-05-08 13:28:41 +00:00
// after a league server error message, always fall-through to try again
}
}
// Autommatic attempt?
2010-03-28 18:58:01 +00:00
if ((fWebCode || fUnregistered) && !fRegister)
2009-05-08 13:28:41 +00:00
{
// No account yet? Try to register.
2010-03-28 18:58:01 +00:00
if (fUnregistered)
2009-05-08 13:28:41 +00:00
fRegister = true;
else
fWebCode = false;
}
// Try given account name as default next time
2010-03-28 18:58:01 +00:00
if (AccountMaster.getLength())
2009-11-25 18:38:54 +00:00
Account.Take(std::move(AccountMaster));
2009-05-08 13:28:41 +00:00
// safety (modal dlg may have deleted network)
if (!pLeagueClient) return false;
}
}
bool C4Network2::LeaguePlrAuthCheck(C4PlayerInfo *pInfo)
{
// Not possible?
2010-03-28 18:58:01 +00:00
if (!pLeagueClient)
return false;
2009-05-08 13:28:41 +00:00
// Make sure league client is available
LeagueWaitNotBusy();
// Ask league server to check the code
2010-03-28 18:58:01 +00:00
if (!pLeagueClient->AuthCheck(*pInfo))
2009-05-08 13:28:41 +00:00
return false;
// Log
StdStrBuf Message = FormatString(LoadResStr("IDS_MSG_LEAGUEJOINING"), pInfo->GetName());
Log(Message.getData());
// Wait for response
2010-03-28 18:58:01 +00:00
while (pLeagueClient->isBusy())
if (!pLeagueClient->Execute(100))
break;
2009-05-08 13:28:41 +00:00
// Check response validity
if (!pLeagueClient->isSuccess())
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
LeagueShowError(pLeagueClient->GetError());
return false;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
// Check if league server approves. pInfo will have league info if this call is successful.
2010-03-28 18:58:01 +00:00
if (!pLeagueClient->GetAuthCheckReply(&Message, Game.Parameters.League.getData(), pInfo))
{
LeagueShowError(FormatString(LoadResStr("IDS_MSG_LEAGUEJOINREFUSED"), pInfo->GetName(), Message.getData()).getData());
2009-05-08 13:28:41 +00:00
return false;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
return true;
}
void C4Network2::LeagueNotifyDisconnect(int32_t iClientID, C4LeagueDisconnectReason eReason)
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// league active?
if (!pLeagueClient || !Game.Parameters.isLeague()) return;
// only in running game
if (!Game.IsRunning || Game.GameOver) return;
// clients send notifications for their own players; host sends for the affected client players
if (!isHost()) { if (!Clients.GetLocal()) return; iClientID = Clients.GetLocal()->getID(); }
// clients only need notifications if they have players in the game
const C4ClientPlayerInfos *pInfos = Game.PlayerInfos.GetInfoByClientID(iClientID);
if (!pInfos) return;
int32_t i=0; C4PlayerInfo *pInfo;
2010-01-25 04:00:59 +00:00
while ((pInfo = pInfos->GetPlayerInfo(i++))) if (pInfo->IsJoined() && !pInfo->IsRemoved()) break;
2009-05-08 13:28:41 +00:00
if (!pInfo) return;
// Make sure league client is avilable
LeagueWaitNotBusy();
// report the disconnect!
LogF(LoadResStr("IDS_LEAGUE_LEAGUEREPORTINGUNEXPECTED"), (int) eReason);
pLeagueClient->ReportDisconnect(*pInfos, eReason);
// wait for the reply
LeagueWaitNotBusy();
// display it
const char *szMsg;
StdStrBuf sMessage;
if (pLeagueClient->GetReportDisconnectReply(&sMessage))
szMsg = LoadResStr("IDS_MSG_LEAGUEUNEXPECTEDDISCONNEC");
else
szMsg = LoadResStr("IDS_ERR_LEAGUEERRORREPORTINGUNEXP");
LogF(szMsg, sMessage.getData());
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
void C4Network2::LeagueWaitNotBusy()
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// league client busy?
if (!pLeagueClient || !pLeagueClient->isBusy()) return;
// wait for it
Log(LoadResStr("IDS_LEAGUE_WAITINGFORLASTLEAGUESERVE"));
2010-03-28 18:58:01 +00:00
while (pLeagueClient->isBusy())
if (!pLeagueClient->Execute(100))
break;
2009-05-08 13:28:41 +00:00
// if last request was an update request, process it
if (pLeagueClient->getCurrentAction() == C4LA_Update)
LeagueUpdateProcessReply();
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
void C4Network2::LeagueSurrender()
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// there's currently no functionality to surrender in the league
// just stop responding so other clients will notify the disconnect
DeinitLeague();
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
void C4Network2::LeagueShowError(const char *szMsg)
2010-03-28 18:58:01 +00:00
{
if (!Application.isEditor)
2010-03-28 18:58:01 +00:00
{
2009-06-05 16:53:56 +00:00
::pGUI->ShowErrorMessage(szMsg);
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
else
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
LogF(LoadResStr("IDS_LGA_SERVERFAILURE"), szMsg);
}
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
void C4Network2::Vote(C4ControlVoteType eType, bool fApprove, int32_t iData)
{
// Original vote?
2010-03-28 18:58:01 +00:00
if (!GetVote(C4ClientIDUnknown, eType, iData))
2009-05-08 13:28:41 +00:00
{
// Too fast?
2010-03-28 18:58:01 +00:00
if (time(NULL) < (time_t) (iLastOwnVoting + C4NetMinVotingInterval))
2009-05-08 13:28:41 +00:00
{
Log(LoadResStr("IDS_TEXT_YOUCANONLYSTARTONEVOTINGE"));
if ((eType == VT_Kick && iData == Game.Clients.getLocalID()) || eType == VT_Cancel)
2009-05-08 13:28:41 +00:00
OpenSurrenderDialog(eType, iData);
return;
}
// Save timestamp
iLastOwnVoting = time(NULL);
}
// Already voted? Ignore
2010-03-28 18:58:01 +00:00
if (GetVote(::Control.ClientID(), eType, iData))
2009-05-08 13:28:41 +00:00
return;
// Set pause mode if this is the host
2010-03-28 18:58:01 +00:00
if (isHost() && isRunning())
2009-05-08 13:28:41 +00:00
{
Pause();
fPausedForVote = true;
}
// send vote control
2009-06-15 22:06:37 +00:00
::Control.DoInput(CID_Vote, new C4ControlVote(eType, fApprove, iData), CDT_Direct);
2009-05-08 13:28:41 +00:00
}
void C4Network2::AddVote(const C4ControlVote &Vote)
{
// Save back timestamp
2010-03-28 18:58:01 +00:00
if (!Votes.firstPkt())
2009-05-08 13:28:41 +00:00
iVoteStartTime = time(NULL);
// Save vote back
Votes.Add(CID_Vote, new C4ControlVote(Vote));
// Set pause mode if this is the host
2010-03-28 18:58:01 +00:00
if (isHost() && isRunning())
2009-05-08 13:28:41 +00:00
{
Pause();
fPausedForVote = true;
}
// Check if the dialog should be opened
OpenVoteDialog();
}
C4IDPacket *C4Network2::GetVote(int32_t iClientID, C4ControlVoteType eType, int32_t iData)
{
C4ControlVote *pVote;
2010-03-28 18:58:01 +00:00
for (C4IDPacket *pPkt = Votes.firstPkt(); pPkt; pPkt = Votes.nextPkt(pPkt))
if (pPkt->getPktType() == CID_Vote)
if ((pVote = static_cast<C4ControlVote *>(pPkt->getPkt())))
if (iClientID == C4ClientIDUnknown || pVote->getByClient() == iClientID)
if (pVote->getType() == eType && pVote->getData() == iData)
2009-05-08 13:28:41 +00:00
return pPkt;
return NULL;
}
void C4Network2::EndVote(C4ControlVoteType eType, bool fApprove, int32_t iData)
{
// Remove all vote packets
C4IDPacket *pPkt; int32_t iOrigin = C4ClientIDUnknown;
2010-03-28 18:58:01 +00:00
while ((pPkt = GetVote(C4ClientIDAll, eType, iData)))
2009-05-08 13:28:41 +00:00
{
2010-03-28 18:58:01 +00:00
if (iOrigin == C4ClientIDUnknown)
2009-05-08 13:28:41 +00:00
iOrigin = static_cast<C4ControlVote *>(pPkt->getPkt())->getByClient();
Votes.Delete(pPkt);
}
// Reset timestamp
iVoteStartTime = time(NULL);
// Approved own voting? Reset voting block
2010-03-28 18:58:01 +00:00
if (fApprove && iOrigin == Game.Clients.getLocalID())
2009-05-08 13:28:41 +00:00
iLastOwnVoting = 0;
// Dialog open?
2010-03-28 18:58:01 +00:00
if (pVoteDialog)
if (pVoteDialog->getVoteType() == eType && pVoteDialog->getVoteData() == iData)
2009-05-08 13:28:41 +00:00
{
// close
delete pVoteDialog;
pVoteDialog = NULL;
}
// Did we try to kick ourself? Ask if we'd like to surrender
bool fCancelVote = (eType == VT_Kick && iData == Game.Clients.getLocalID()) || eType == VT_Cancel;
if (!fApprove && fCancelVote && iOrigin == Game.Clients.getLocalID())
2009-05-08 13:28:41 +00:00
OpenSurrenderDialog(eType, iData);
// Check if the dialog should be opened
OpenVoteDialog();
// Pause/unpause voting?
2010-03-28 18:58:01 +00:00
if (fApprove && eType == VT_Pause)
2009-05-08 13:28:41 +00:00
fPausedForVote = !iData;
// No voting left? Reset pause.
2010-03-28 18:58:01 +00:00
if (!Votes.firstPkt())
if (fPausedForVote)
{
2009-05-08 13:28:41 +00:00
Start();
fPausedForVote = false;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
}
void C4Network2::OpenVoteDialog()
{
// Dialog already open?
2010-03-28 18:58:01 +00:00
if (pVoteDialog) return;
2009-05-08 13:28:41 +00:00
// No vote available?
2010-03-28 18:58:01 +00:00
if (!Votes.firstPkt()) return;
2009-05-08 13:28:41 +00:00
// Can't vote?
C4ClientPlayerInfos *pPlayerInfos = Game.PlayerInfos.GetInfoByClientID(Game.Clients.getLocalID());
2010-03-28 18:58:01 +00:00
if (!pPlayerInfos || !pPlayerInfos->GetPlayerCount() || !pPlayerInfos->GetJoinedPlayerCount())
2009-05-08 13:28:41 +00:00
return;
// Search a voting we have to vote on
2010-03-28 18:58:01 +00:00
for (C4IDPacket *pPkt = Votes.firstPkt(); pPkt; pPkt = Votes.nextPkt(pPkt))
2009-05-08 13:28:41 +00:00
{
// Already voted on this matter?
C4ControlVote *pVote = static_cast<C4ControlVote *>(pPkt->getPkt());
2010-03-28 18:58:01 +00:00
if (!GetVote(::Control.ClientID(), pVote->getType(), pVote->getData()))
2009-05-08 13:28:41 +00:00
{
// Compose message
C4Client *pSrcClient = Game.Clients.getClientByID(pVote->getByClient());
StdStrBuf Msg; Msg.Format(LoadResStr("IDS_VOTE_WANTSTOALLOW"), pSrcClient ? pSrcClient->getName() : "???", pVote->getDesc().getData());
Msg.AppendChar('|');
Msg.Append(pVote->getDescWarning());
// Open dialog
pVoteDialog = new C4VoteDialog(Msg.getData(), pVote->getType(), pVote->getData(), false);
pVoteDialog->SetDelOnClose();
2009-06-05 16:53:56 +00:00
pVoteDialog->Show(::pGUI, true);
2009-05-08 13:28:41 +00:00
break;
}
}
}
void C4Network2::OpenSurrenderDialog(C4ControlVoteType eType, int32_t iData)
{
2010-03-28 18:58:01 +00:00
if (!pVoteDialog)
{
2009-05-08 13:28:41 +00:00
pVoteDialog = new C4VoteDialog(
2010-03-28 18:58:01 +00:00
LoadResStr("IDS_VOTE_SURRENDERWARNING"), eType, iData, true);
2009-05-08 13:28:41 +00:00
pVoteDialog->SetDelOnClose();
2009-06-05 16:53:56 +00:00
pVoteDialog->Show(::pGUI, true);
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
}
void C4Network2::OnVoteDialogClosed()
{
pVoteDialog = NULL;
}
// *** C4VoteDialog
C4VoteDialog::C4VoteDialog(const char *szText, C4ControlVoteType eVoteType, int32_t iVoteData, bool fSurrender)
2010-03-28 18:58:01 +00:00
: MessageDialog(szText, LoadResStr("IDS_DLG_VOTING"), C4GUI::MessageDialog::btnYesNo, C4GUI::Ico_Confirm, C4GUI::MessageDialog::dsRegular, NULL, true),
2010-01-25 04:00:59 +00:00
eVoteType(eVoteType), iVoteData(iVoteData), fSurrender(fSurrender)
2009-05-08 13:28:41 +00:00
{
}
void C4VoteDialog::OnClosed(bool fOK)
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
bool fAbortGame = false;
// notify that this object will be deleted shortly
2009-06-05 15:19:46 +00:00
::Network.OnVoteDialogClosed();
2009-05-08 13:28:41 +00:00
// Was league surrender dialog
if (fSurrender)
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// League surrender accepted
if (fOK)
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// set game leave reason, although round results dialog isn't showing it ATM
Game.RoundResults.EvaluateNetwork(C4RoundResults::NR_NetError, LoadResStr("IDS_ERR_YOUSURRENDEREDTHELEAGUEGA"));
// leave game
2009-06-05 15:19:46 +00:00
::Network.LeagueSurrender();
::Network.Clear();
2009-05-08 13:28:41 +00:00
// We have just league-surrendered. Abort the game - that is what we originally wanted.
// Note: as we are losing league points and this is a relevant game, it would actually be
// nice to show an evaluation dialog which tells us that we have lost and how many league
// points we have lost. But until the evaluation dialog can actually do that, it is better
// to abort completely.
// Note2: The league dialog will never know that, because the game will usually not be over yet.
// Scores are not calculated until after the game.
fAbortGame = true;
}
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
// Was normal vote dialog
else
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// Vote still active? Then vote.
2009-06-05 15:19:46 +00:00
if (::Network.GetVote(C4ClientIDUnknown, eVoteType, iVoteData))
::Network.Vote(eVoteType, fOK, iVoteData);
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
// notify base class
MessageDialog::OnClosed(fOK);
// Abort game
if (fAbortGame)
Game.Abort(true);
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
/* Lobby countdown */
void C4Network2::StartLobbyCountdown(int32_t iCountdownTime)
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// abort previous
if (pLobbyCountdown) AbortLobbyCountdown();
// start new
pLobbyCountdown = new C4GameLobby::Countdown(iCountdownTime);
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
void C4Network2::AbortLobbyCountdown()
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// aboert lobby countdown
if (pLobbyCountdown)
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
pLobbyCountdown->Abort();
delete pLobbyCountdown;
pLobbyCountdown = NULL;
}
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
/* Streaming */
bool C4Network2::StartStreaming(C4Record *pRecord)
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// Save back
fStreaming = true;
pStreamedRecord = pRecord;
iLastStreamAttempt = time(NULL);
// Initialize compressor
ZeroMem(&StreamCompressor, sizeof(StreamCompressor));
2010-03-28 18:58:01 +00:00
if (deflateInit(&StreamCompressor, 9) != Z_OK)
2009-05-08 13:28:41 +00:00
return false;
// Create stream buffer
StreamingBuf.New(C4NetStreamingMaxBlockSize);
StreamCompressor.next_out = reinterpret_cast<BYTE*>(StreamingBuf.getMData());
StreamCompressor.avail_out = C4NetStreamingMaxBlockSize;
// Initialize HTTP client
pStreamer = new C4Network2HTTPClient();
2010-03-28 18:58:01 +00:00
if (!pStreamer->Init())
2009-05-08 13:28:41 +00:00
return false;
Application.Add(pStreamer);
2009-05-08 13:28:41 +00:00
return true;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
bool C4Network2::FinishStreaming()
2010-03-28 18:58:01 +00:00
{
if (!fStreaming) return false;
2009-05-08 13:28:41 +00:00
// Stream
StreamIn(true);
// Reset record pointer
pStreamedRecord = NULL;
// Try to get rid of remaining data immediately
iLastStreamAttempt = 0;
StreamOut();
return true;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
bool C4Network2::StopStreaming()
2010-03-28 18:58:01 +00:00
{
if (!fStreaming) return false;
2009-05-08 13:28:41 +00:00
// Clear
Application.Remove(pStreamer);
2009-05-08 13:28:41 +00:00
fStreaming = false;
pStreamedRecord = NULL;
deflateEnd(&StreamCompressor);
StreamingBuf.Clear();
delete pStreamer;
pStreamer = NULL;
// ... finalization?
return true;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
bool C4Network2::StreamIn(bool fFinish)
2010-03-28 18:58:01 +00:00
{
if (!pStreamedRecord) return false;
2009-05-08 13:28:41 +00:00
// Get data from record
const StdBuf &Data = pStreamedRecord->GetStreamingBuf();
2010-03-28 18:58:01 +00:00
if (!fFinish)
if (!Data.getSize() || !StreamCompressor.avail_out)
2009-05-08 13:28:41 +00:00
return false;
do
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// Compress
StreamCompressor.next_in = const_cast<BYTE *>(getBufPtr<BYTE>(Data));
StreamCompressor.avail_in = Data.getSize();
int ret = deflate(&StreamCompressor, fFinish ? Z_FINISH : Z_NO_FLUSH);
// Anything consumed?
unsigned int iInAmount = Data.getSize() - StreamCompressor.avail_in;
2010-03-28 18:58:01 +00:00
if (iInAmount > 0)
2009-05-08 13:28:41 +00:00
pStreamedRecord->ClearStreamingBuf(iInAmount);
// Done?
2010-03-28 18:58:01 +00:00
if (!fFinish || ret == Z_STREAM_END)
2009-05-08 13:28:41 +00:00
break;
// Error while finishing?
2010-03-28 18:58:01 +00:00
if (ret != Z_OK)
2009-05-08 13:28:41 +00:00
return false;
// Enlarge buffer, if neccessary
size_t iPending = getPendingStreamData();
size_t iGrow = StreamingBuf.getSize();
StreamingBuf.Grow(iGrow);
StreamCompressor.avail_out += iGrow;
StreamCompressor.next_out = getMBufPtr<BYTE>(StreamingBuf, iPending);
2010-03-28 18:58:01 +00:00
}
while (true);
2009-05-08 13:28:41 +00:00
return true;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
bool C4Network2::StreamOut()
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// Streamer busy?
2010-03-28 18:58:01 +00:00
if (!pStreamer || pStreamer->isBusy())
2009-05-08 13:28:41 +00:00
return false;
// Streamer done?
2010-03-28 18:58:01 +00:00
if (pStreamer->isSuccess())
{
2009-05-08 13:28:41 +00:00
// Move new data to front of buffer
2010-03-28 18:58:01 +00:00
if (getPendingStreamData() != iCurrentStreamAmount)
2009-05-08 13:28:41 +00:00
StreamingBuf.Move(iCurrentStreamAmount, getPendingStreamData() - iCurrentStreamAmount);
// Free buffer space
StreamCompressor.next_out -= iCurrentStreamAmount;
StreamCompressor.avail_out += iCurrentStreamAmount;
// Advance stream
iCurrentStreamPosition += iCurrentStreamAmount;
// Get input
StreamIn(false);
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
// Clear streamer
pStreamer->Clear();
// Record is still running?
2010-03-28 18:58:01 +00:00
if (pStreamedRecord)
{
2009-05-08 13:28:41 +00:00
// Enough available to send?
2010-03-28 18:58:01 +00:00
if (getPendingStreamData() < C4NetStreamingMinBlockSize)
2009-05-08 13:28:41 +00:00
return false;
// Overflow protection
2010-03-28 18:58:01 +00:00
if (iLastStreamAttempt && iLastStreamAttempt + C4NetStreamingInterval >= time(NULL))
2009-05-08 13:28:41 +00:00
return false;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
// All data finished?
else if (!getPendingStreamData())
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// Then we're done.
StopStreaming();
return false;
2010-03-28 18:58:01 +00:00
}
2009-05-08 13:28:41 +00:00
// Set stream address
StdStrBuf StreamAddr;
StreamAddr.Copy(Game.Parameters.StreamAddress);
StreamAddr.AppendFormat("pos=%d&end=%d", iCurrentStreamPosition, !pStreamedRecord);
pStreamer->SetServer(StreamAddr.getData());
// Send data
size_t iStreamAmount = getPendingStreamData();
iCurrentStreamAmount = iStreamAmount;
iLastStreamAttempt = time(NULL);
2010-03-28 18:58:01 +00:00
return pStreamer->Query(StdBuf(StreamingBuf.getData(), iStreamAmount), false);
}
2009-05-08 13:28:41 +00:00
bool C4Network2::isStreaming() const
2010-03-28 18:58:01 +00:00
{
2009-05-08 13:28:41 +00:00
// Streaming must be active and there must still be anything to stream
return fStreaming;
2010-03-28 18:58:01 +00:00
}
2009-06-05 15:19:46 +00:00
2009-07-28 11:39:22 +00:00
//C4Network2 Network;