/src/network/C4Network2.cpp
https://bitbucket.org/randrian/openclonk2 · C++ · 2901 lines · 1977 code · 303 blank · 621 comment · 640 complexity · 7fd9b93aaacc6fedfca7a5dba0a3ac2e MD5 · raw file
Large files are truncated click here to view the full file
- /*
- * OpenClonk, http://www.openclonk.org
- *
- * Copyright (c) 2004-2009 Peter Wortmann
- * Copyright (c) 2004-2009 Sven Eberhardt
- * Copyright (c) 2005-2006, 2009 Günther Brammer
- * Copyright (c) 2006 Florian Groß
- * Copyright (c) 2007-2008 Matthes Bender
- * Copyright (c) 2009 Nicolas Hake
- * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
- *
- * Portions might be copyrighted by other authors who have contributed
- * to OpenClonk.
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- * See isc_license.txt for full license and disclaimer.
- *
- * "Clonk" is a registered trademark of Matthes Bender.
- * See clonk_trademark_license.txt for full license.
- */
- #include <C4Include.h>
- #include <C4Network2.h>
- #include <C4Version.h>
- #ifndef BIG_C4INCLUDE
- #include <C4Log.h>
- #include <C4Application.h>
- #include <C4Console.h>
- #include <C4GameSave.h>
- #include <C4RoundResults.h>
- #include <C4Game.h>
- #include <C4GraphicsSystem.h>
- #include <C4GraphicsResource.h>
- #include <C4GameControl.h>
- // lobby
- #include <C4Gui.h>
- #include <C4GameLobby.h>
- #include <C4Network2Dialogs.h>
- #include <C4League.h>
- #endif
- #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()
- : eState(GS_None), iTargetCtrlTick(-1)
- {
- }
- const char *C4Network2Status::getStateName() const
- {
- switch(eState)
- {
- 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
- {
- switch(eState)
- {
- 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[] =
- {
- { "None", GS_None },
- { "Init", GS_Init },
- { "Lobby", GS_Lobby },
- { "Paused", GS_Pause },
- { "Running", GS_Go },
- };
- pComp->Value(mkNamingAdapt(mkEnumAdaptT<uint8_t>(eState, GameStates), "State", GS_None));
- pComp->Value(mkNamingAdapt(mkIntPackAdapt(iCtrlMode), "CtrlMode", -1));
- if(!fReference)
- pComp->Value(mkNamingAdapt(mkIntPackAdapt(iTargetCtrlTick), "TargetTick", -1));
- }
- // *** C4Network2
- C4Network2::C4Network2()
- : Clients(&NetIO),
- fAllowJoin(false),
- iDynamicTick(-1), fDynamicNeeded(false),
- fStatusAck(false), fStatusReached(false),
- fChasing(false),
- pLobby(NULL), fLobbyRunning(false), pLobbyCountdown(NULL),
- pControl(NULL),
- iNextClientID(0),
- iLastActivateRequest(0),
- iLastChaseTargetUpdate(0),
- iLastReferenceUpdate(0),
- iLastLeagueUpdate(0),
- pLeagueClient(NULL),
- fDelayedActivateReq(false),
- pVoteDialog(NULL),
- fPausedForVote(false),
- iLastOwnVoting(0),
- fStreaming(NULL)
- {
- }
- C4Network2::~C4Network2()
- {
- Clear();
- }
- bool C4Network2::InitHost(bool fLobby)
- {
- if(isEnabled()) Clear();
- // initialize everything
- Status.Set(fLobby ? GS_Lobby : GS_Go, ::Control.ControlTick);
- 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
- if(!ResList.Init(Game.Clients.getLocalID(), &NetIO))
- { LogFatal("Network: failed to initialize resource list!"); Clear(); return false; }
- if(!Game.Parameters.InitNetwork(&ResList))
- return false;
- // create initial dynamic
- if(!CreateDynamic(true))
- return false;
- // initialize net i/o
- if(!InitNetIO(false, true))
- { Clear(); return false; }
- // init network control
- pControl = &::Control.Network;
- pControl->Init(C4ClientIDHost, true, ::Control.getNextControlTick(), true, this);
- // init league
- bool fCancel = true;
- if(!InitLeague(&fCancel) || !LeagueStart(&fCancel))
- {
- // deinit league
- DeinitLeague();
- // user cancelled?
- if(fCancel)
- return false;
- // in console mode, bail out
- #ifdef USE_CONSOLE
- return false;
- #endif
- }
- // allow connect
- NetIO.SetAcceptMode(true);
- // timer
- Application.Add(this);
- // ok
- return true;
- }
- C4Network2::InitResult C4Network2::InitClient(const C4Network2Reference &Ref, bool fObserver)
- {
- if(isEnabled()) Clear();
- // Get host core
- const C4ClientCore &HostCore = Ref.Parameters.Clients.getHost()->getCore();
- // repeat if wrong password
- fWrongPassword = Ref.isPasswordNeeded();
- StdStrBuf Password;
- for(;;)
- {
- // ask for password (again)?
- if(fWrongPassword)
- {
- Password.Take(QueryClientPassword());
- if(!Password.getLength())
- return IR_Error;
- fWrongPassword = false;
- }
- // copy addresses
- C4Network2Address Addrs[C4ClientMaxAddr];
- for(int i = 0; i < Ref.getAddrCnt(); i++)
- Addrs[i] = Ref.getAddr(i);
- // Try to connect to host
- if(InitClient(Addrs, Ref.getAddrCnt(), HostCore, Password.getData()) == IR_Fatal)
- return IR_Fatal;
- // success?
- if(isEnabled())
- break;
- // Retry only for wrong password
- if(!fWrongPassword)
- {
- LogSilent("Network: Could not connect!");
- return IR_Error;
- }
- }
- // initialize ressources
- if(!Game.Parameters.InitNetwork(&ResList))
- return IR_Fatal;
- // init league
- if(!InitLeague(NULL))
- {
- // deinit league
- DeinitLeague();
- return IR_Fatal;
- }
- // allow connect
- NetIO.SetAcceptMode(true);
- // 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
- if(!ResList.Init(Game.Clients.getLocalID(), &NetIO))
- { LogFatal(LoadResStr("IDS_NET_ERR_INITRESLIST")); Clear(); return IR_Fatal; }
- // initialize net i/o
- if(!InitNetIO(true, false))
- { Clear(); return IR_Fatal; }
- // set network control
- pControl = &::Control.Network;
- // set exclusive connection mode
- NetIO.SetExclusiveConnMode(true);
- // try to connect host
- StdStrBuf strAddresses; int iSuccesses = 0;
- for(int i = 0; i < iAddrCount; i++)
- if(!pAddrs[i].isIPNull())
- {
- // connection
- if(!NetIO.Connect(pAddrs[i].getAddr(), pAddrs[i].getProtocol(), HostCore, szPassword))
- continue;
- // format for message
- if(strAddresses.getLength())
- strAddresses.Append(", ");
- strAddresses.Append(pAddrs[i].toString());
- iSuccesses++;
- }
- // no connection attempt running?
- if(!iSuccesses)
- { Clear(); return IR_Error; }
- // log
- StdStrBuf strMessage = FormatString(LoadResStr("IDS_NET_CONNECTHOST"), strAddresses.getData());
- Log(strMessage.getData());
- // show box
- C4GUI::MessageDialog *pDlg = NULL;
- if(::pGUI && !Console.Active)
- {
- // create & show
- pDlg = new C4GUI::MessageDialog(strMessage.getData(), LoadResStr("IDS_NET_JOINGAME"),
- C4GUI::MessageDialog::btnAbort, C4GUI::Ico_NetWait, C4GUI::MessageDialog::dsMedium);
- if(!pDlg->Show(::pGUI, true)) { Clear(); return IR_Fatal; }
- }
- // wait for connect / timeout / abort by user (host will change status on succesful connect)
- while(Status.getState() == GS_Init)
- {
- if(!Application.ScheduleProcs(100))
- { if(::pGUI && pDlg) delete pDlg; return IR_Fatal;}
- if(pDlg && pDlg->IsAborted())
- { if(::pGUI && pDlg) delete pDlg; return IR_Fatal; }
- }
- // Close dialog
- if(::pGUI && pDlg) delete pDlg;
- // error?
- if(!isEnabled())
- return IR_Error;
- // deactivate exclusive connection mode
- NetIO.SetExclusiveConnMode(false);
- return IR_Success;
- }
- bool C4Network2::DoLobby()
- {
- // shouldn't do lobby?
- if(!isEnabled() || (!isHost() && !isLobbyActive()))
- return true;
- // lobby runs
- fLobbyRunning = true;
- fAllowJoin = true;
- Log(LoadResStr("IDS_NET_LOBBYWAITING"));
- // client: lobby status reached, message to host
- if(!isHost())
- CheckStatusReached();
- // host: set lobby mode
- else
- ChangeGameStatus(GS_Lobby, 0);
- // determine lobby type
- bool fFullscreenLobby = !Console.Active && (lpDDraw->GetEngine() != GFXENGN_NOGFX);
- if(!fFullscreenLobby)
- {
- // console lobby - update console
- if (Console.Active) Console.UpdateMenus();
- // init lobby countdown if specified
- if (Game.iLobbyTimeout) StartLobbyCountdown(Game.iLobbyTimeout);
- // do console lobby
- while(isLobbyActive())
- if (!Application.ScheduleProcs())
- { Clear(); return false; }
- }
- else
- {
- // fullscreen lobby
- // init lobby dialog
- pLobby = new C4GameLobby::MainDlg(isHost());
- if (!pLobby->FadeIn(::pGUI)) { delete pLobby; pLobby = NULL; Clear(); return false; }
- // init lobby countdown if specified
- if (Game.iLobbyTimeout) StartLobbyCountdown(Game.iLobbyTimeout);
- // while state lobby: keep looping
- while(isLobbyActive() && ::pGUI && pLobby && pLobby->IsShown())
- if (!Application.ScheduleProcs())
- { Clear(); return false; }
- // check whether lobby was aborted; first checking ::pGUI
- // (because an external call to Game.Clear() would invalidate pLobby)
- if (!::pGUI) { pLobby = NULL; Clear(); return false; }
- 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
- if (::pGUI) ::pGUI->CloseAllDialogs(false);
- }
- // lobby end
- delete pLobbyCountdown; pLobbyCountdown = NULL;
- fLobbyRunning = false;
- fAllowJoin = !Config.Network.NoRuntimeJoin;
- // 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()
- {
- if(!isEnabled() || !isHost()) return false;
- // change mode: go
- ChangeGameStatus(GS_Go, ::Control.ControlTick);
- return true;
- }
- bool C4Network2::Pause()
- {
- if(!isEnabled() || !isHost()) return false;
- // change mode: pause
- return ChangeGameStatus(GS_Pause, ::Control.getNextControlTick());
- }
- bool C4Network2::Sync()
- {
- // host only
- if(!isEnabled() || !isHost()) return false;
- // already syncing the network?
- if(!fStatusAck)
- {
- // maybe we are already sync?
- if(fStatusReached) CheckStatusAck();
- return true;
- }
- // already sync?
- if(isFrozen()) return true;
- // ok, so let's do a sync: change in the same state we are already in
- return ChangeGameStatus(Status.getState(), ::Control.getNextControlTick());
- }
- bool C4Network2::FinalInit()
- {
- // check reach
- CheckStatusReached(true);
- // reached, waiting for ack?
- if(fStatusReached && !fStatusAck)
- {
- // wait for go acknowledgement
- Log(LoadResStr("IDS_NET_JOINREADY"));
- // any pending keyboard commands should not be routed to cancel the wait dialog - flish the message queue!
- if(!Application.FlushMessages()) return false;
- // show box
- C4GUI::Dialog *pDlg = NULL;
- if(::pGUI && !Console.Active)
- {
- // seperate dlgs for host/client
- if (isHost())
- pDlg = new C4Network2StartWaitDlg();
- else
- pDlg = new C4GUI::MessageDialog(LoadResStr("IDS_NET_WAITFORSTART"), LoadResStr("IDS_NET_CAPTION"),
- C4GUI::MessageDialog::btnAbort, C4GUI::Ico_NetWait, C4GUI::MessageDialog::dsSmall);
- // show it
- if(!pDlg->Show(::pGUI, true)) return false;
- }
- // wait for acknowledgement
- while(fStatusReached && !fStatusAck)
- {
- if(pDlg)
- {
- // execute
- if(!pDlg->Execute()) { delete pDlg; Clear(); return false; }
- // aborted?
- if(!::pGUI) { Clear(); return false;}
- if(pDlg->IsAborted()) { delete pDlg; Clear(); return false; }
- }
- else if(!Application.ScheduleProcs())
- { Clear(); return false; }
- }
- if(::pGUI && pDlg) delete pDlg;
- // log
- Log(LoadResStr("IDS_NET_START"));
- }
- // synchronize
- Game.SyncClearance();
- Game.Synchronize(false);
- // finished
- return isEnabled();
- }
- bool C4Network2::RetrieveScenario(char *szScenario)
- {
- // client only
- if(isHost()) return false;
- // wait for scenario
- C4Network2Res::Ref pScenario = RetrieveRes(*Game.Parameters.Scenario.getResCore(),
- C4NetResRetrieveTimeout, LoadResStr("IDS_NET_RES_SCENARIO"));
- if(!pScenario)
- return false;
- // wait for dynamic data
- C4Network2Res::Ref pDynamic = RetrieveRes(ResDynamic, C4NetResRetrieveTimeout, LoadResStr("IDS_NET_RES_DYNAMIC"));
- if(!pDynamic)
- return false;
- // create unpacked copy of scenario
- if(!ResList.FindTempResFileName(FormatString("Combined%d.c4s", Game.Clients.getLocalID()).getData(), szScenario) ||
- !C4Group_CopyItem(pScenario->getFile(), szScenario) ||
- !C4Group_UnpackDirectory(szScenario))
- return false;
- // create unpacked copy of dynamic data
- char szTempDynamic[_MAX_PATH + 1];
- if(!ResList.FindTempResFileName(pDynamic->getFile(), szTempDynamic) ||
- !C4Group_CopyItem(pDynamic->getFile(), szTempDynamic) ||
- !C4Group_UnpackDirectory(szTempDynamic))
- return false;
- // unpack Material.c4g if materials need to be merged
- StdStrBuf MaterialScenario, MaterialDynamic;
- MaterialScenario.Format("%s" DirSep C4CFN_Material, szScenario);
- MaterialDynamic.Format("%s" DirSep C4CFN_Material, szTempDynamic);
- if(FileExists(MaterialScenario.getData()) && FileExists(MaterialDynamic.getData()))
- if(!C4Group_UnpackDirectory(MaterialScenario.getData()) ||
- !C4Group_UnpackDirectory(MaterialDynamic.getData()))
- return false;
- // move all dynamic files to scenario
- C4Group ScenGrp;
- if(!ScenGrp.Open(szScenario) ||
- !ScenGrp.Merge(szTempDynamic))
- 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();
- if(isHost())
- {
- // remove dynamic
- if(!ResDynamic.isNull() && ::Control.ControlTick > iDynamicTick)
- RemoveDynamic();
- // Set chase target
- UpdateChaseTarget();
- // check for inactive clients and deactivate them
- DeactivateInactiveClients();
- // reference
- 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
- if(!iLastLeagueUpdate || time(NULL) > (time_t) (iLastLeagueUpdate + iLeagueUpdateDelay))
- {
- LeagueUpdate();
- }
- // league update reply receive
- if(pLeagueClient && fHost && !pLeagueClient->isBusy() && pLeagueClient->getCurrentAction() == C4LA_Update)
- {
- LeagueUpdateProcessReply();
- }
- // voting timeout
- if(Votes.firstPkt() && time(NULL) > (time_t) (iVoteStartTime + C4NetVotingTimeout))
- {
- C4ControlVote *pVote = static_cast<C4ControlVote *>(Votes.firstPkt()->getPkt());
- ::Control.DoInput(
- CID_VoteEnd,
- new C4ControlVoteEnd(pVote->getType(), false, pVote->getData()),
- CDT_Sync);
- iVoteStartTime = time(NULL);
- }
- // record streaming
- if(fStreaming)
- {
- StreamIn(false);
- StreamOut();
- }
- }
- else
- {
- // request activate, if neccessary
- if(iLastActivateRequest) RequestActivate();
- }
- }
- void C4Network2::Clear()
- {
- // stop timer
- Application.Remove(this);
- // stop streaming
- StopStreaming();
- // clear league
- if(pLeagueClient)
- {
- LeagueEnd();
- DeinitLeague();
- }
- // 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
- if(::Control.isNetwork())
- ::Control.ChangeToLocal();
- // clear all player infos
- Players.Clear();
- // remove all clients
- Clients.Clear();
- // close net classes
- NetIO.Clear();
- // clear ressources
- ResList.Clear();
- // clear password
- sPassword.Clear();
- // stuff
- fAllowJoin = false;
- iDynamicTick = -1; fDynamicNeeded = false;
- iLastActivateRequest = iLastChaseTargetUpdate = iLastReferenceUpdate = iLastLeagueUpdate = 0;
- fDelayedActivateReq = false;
- if(::pGUI) delete pVoteDialog; pVoteDialog = NULL;
- fPausedForVote = false;
- iLastOwnVoting = 0;
- // don't clear fPasswordNeeded here, it's needed by InitClient
- }
- bool C4Network2::ToggleAllowJoin()
- {
- // just toggle
- AllowJoin(!fAllowJoin);
- return true; // toggled
- }
- bool C4Network2::ToggleClientListDlg()
- {
- C4Network2ClientListDlg::Toggle();
- return true;
- }
- void C4Network2::SetPassword(const char *szToPassword)
- {
- 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();
- }
- StdStrBuf C4Network2::QueryClientPassword()
- {
- // 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);
- if (!::pGUI->ShowModalDlg(pInputDlg, false))
- {
- if (C4GUI::IsGUIValid()) delete pInputDlg;
- return StdStrBuf();
- }
- // copy to buffer
- StdStrBuf Buf; Buf.Copy(pInputDlg->GetInputText());
- delete pInputDlg;
- return Buf;
- }
- void C4Network2::AllowJoin(bool fAllow)
- {
- if(!isHost()) return;
- fAllowJoin = fAllow;
- if (Game.IsRunning)
- {
- ::GraphicsSystem.FlashMessage(LoadResStr(fAllowJoin ? "IDS_NET_RUNTIMEJOINFREE" : "IDS_NET_RUNTIMEJOINBARRED"));
- Config.Network.NoRuntimeJoin = !fAllowJoin;
- }
- }
- void C4Network2::SetAllowObserve(bool fAllow)
- {
- if(!isHost()) return;
- fAllowObserve = fAllow;
- }
- void C4Network2::SetCtrlMode(int32_t iCtrlMode)
- {
- if(!isHost()) return;
- // no change?
- if(iCtrlMode == Status.getCtrlMode()) return;
- // change game status
- ChangeGameStatus(Status.getState(), ::Control.ControlTick, iCtrlMode);
- }
- 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?
- if(Status.getState() == GS_Init && !isHost())
- {
- if(!NetIO.getConnectionCount())
- Clear();
- return;
- }
- // connection failed?
- if(pConn->isFailed())
- {
- // call handler
- OnConnectFail(pConn);
- return;
- }
- // search client
- C4Network2Client *pClient = Clients.GetClient(pConn);
- // not found? Search by ID (not associated yet, half-accepted connection)
- if(!pClient) pClient = Clients.GetClientByID(pConn->getClientID());
- // not found? ignore
- if(!pClient) return;
- // remove connection
- pClient->RemoveConn(pConn);
- // create post-mortem if needed
- C4PacketPostMortem PostMortem;
- if(pConn->CreatePostMortem(&PostMortem))
- {
- 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.)
- if(!Clients.SendMsgToClient(pConn->getClientID(), MkC4NetIOPacket(PID_PostMortem, PostMortem)))
- 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);
- if(!pClient) pClient = Clients.GetClientByID(pConn->getClientID());
- // local? ignore
- if(pClient && pClient->isLocal()) { pConn->Close(); return; }
- #define GETPKT(type, name) \
- assert(pPacket); const type &name = \
- /*dynamic_cast*/ static_cast<const type &>(*pPacket);
- switch(cStatus)
- {
- case PID_Conn: // connection request
- {
- if(!pConn->isOpen()) break;
- 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
- if(isHost() || !pClient || !pClient->isHost()) break;
- if(!pConn->isOpen()) break;
- // handle
- GETPKT(C4PacketJoinData, rPkt)
- HandleJoinData(rPkt);
- }
- break;
- case PID_Status: // status change
- {
- // by host only
- if(isHost() || !pClient || !pClient->isHost()) break;
- if(!pConn->isOpen()) break;
- // must be initialized
- if(Status.getState() == GS_Init) break;
- // handle
- GETPKT(C4Network2Status, rPkt);
- HandleStatus(rPkt);
- }
- break;
- case PID_StatusAck: // status change acknowledgement
- {
- // host->client / client->host only
- if(!pClient) break;
- if(!isHost() && !pClient->isHost()) break;
- // must be initialized
- if(Status.getState() == GS_Init) break;
- // handle
- GETPKT(C4Network2Status, rPkt);
- HandleStatusAck(rPkt, pClient);
- }
- break;
- case PID_ClientActReq: // client activation request
- {
- // client->host only
- if(!isHost() || !pClient || pClient->isHost()) break;
- // must be initialized
- if(Status.getState() == GS_Init) break;
- // handle
- GETPKT(C4PacketActivateReq, rPkt)
- HandleActivateReq(rPkt.getTick(), pClient);
- }
- break;
- }
- #undef GETPKT
- }
- void C4Network2::HandleLobbyPacket(char cStatus, const C4PacketBase *pBasePkt, C4Network2IOConnection *pConn)
- {
- // find associated client
- C4Network2Client *pClient = Clients.GetClient(pConn);
- if(!pClient) pClient = Clients.GetClientByID(pConn->getClientID());
- // forward directly to lobby
- if (pLobby) pLobby->HandlePacket(cStatus, pBasePkt, pClient);
- }
- void C4Network2::OnGameSynchronized()
- {
- // savegame needed?
- if(fDynamicNeeded)
- {
- // create dynamic
- bool fSuccess = CreateDynamic(false);
- // check for clients that still need join-data
- C4Network2Client *pClient = NULL;
- while(pClient = Clients.GetNextClient(pClient))
- if(!pClient->hasJoinData())
- if(fSuccess)
- // 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"));
- }
- }
- void C4Network2::DrawStatus(C4TargetFacet &cgo)
- {
- if(!isEnabled()) return;
- C4Network2Client *pLocal = Clients.GetLocal();
- StdStrBuf Stat;
- // local client status
- Stat.AppendFormat("Local: %s %s %s (ID %d)",
- pLocal->isObserver() ? "Observing" : pLocal->isActivated() ? "Active" : "Inactive", pLocal->isHost() ? "host" : "client",
- pLocal->getName(), pLocal->getID());
- // game status
- Stat.AppendFormat( "|Game Status: %s (tick %d)%s%s",
- Status.getStateName(), Status.getTargetCtrlTick(),
- fStatusReached ? " reached" : "", fStatusAck ? " ack" : "");
- // available protocols
- C4NetIO *pMsgIO = NetIO.MsgIO(), *pDataIO = NetIO.DataIO();
- if(pMsgIO && pDataIO)
- {
- C4Network2IOProtocol eMsgProt = NetIO.getNetIOProt(pMsgIO),
- eDataProt = NetIO.getNetIOProt(pDataIO);
- int32_t iMsgPort = 0, iDataPort = 0;
- switch(eMsgProt)
- {
- case P_TCP: iMsgPort = Config.Network.PortTCP; break;
- case P_UDP: iMsgPort = Config.Network.PortUDP; break;
- }
- switch(eDataProt)
- {
- case P_TCP: iDataPort = Config.Network.PortTCP; break;
- case P_UDP: iDataPort = Config.Network.PortUDP; break;
- }
- Stat.AppendFormat( "|Protocols: %s: %s (%d i%d o%d bc%d)",
- 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));
- }
- else
- Stat.Append("|Protocols: none");
- // some control statistics
- Stat.AppendFormat( "|Control: %s, Tick %d, Behind %d, Rate %d, PreSend %d, ACT: %d",
- Status.getCtrlMode() == CNM_Decentral ? "Decentral" : Status.getCtrlMode() == CNM_Central ? "Central" : "Async",
- ::Control.ControlTick, pControl->GetBehind(::Control.ControlTick),
- ::Control.ControlRate, pControl->getControlPreSend(), pControl->getAvgControlSendTime());
- // Streaming statistics
- if(fStreaming)
- Stat.AppendFormat( "|Streaming: %d waiting, %d in, %d out, %d sent",
- pStreamedRecord ? pStreamedRecord->GetStreamingBuf().getSize() : 0,
- pStreamedRecord ? pStreamedRecord->GetStreamingPos() : 0,
- getPendingStreamData(),
- iCurrentStreamPosition);
- // clients
- Stat.Append("|Clients:");
- for(C4Network2Client *pClient = Clients.GetNextClient(NULL); pClient; pClient = Clients.GetNextClient(pClient))
- {
- // ignore local
- if(pClient->isLocal()) continue;
- // client status
- const C4ClientCore &Core = pClient->getCore();
- const char *szClientStatus = "";
- switch(pClient->getStatus())
- {
- case NCS_Joining: szClientStatus = " (joining)"; break;
- case NCS_Chasing: szClientStatus = " (chasing)"; break;
- case NCS_NotReady: szClientStatus = " (!rdy)"; break;
- case NCS_Remove: szClientStatus = " (removed)"; break;
- }
- Stat.AppendFormat( "|- %s %s %s (ID %d) (wait %d ms, behind %d)%s%s",
- 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)" : "");
- // connections
- if(pClient->isConnected())
- {
- Stat.AppendFormat( "| Connections: %s: %s (%s:%d p%d l%d)",
- 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());
- }
- else
- Stat.Append("| Not connected");
- }
- if(!Clients.GetNextClient(NULL))
- Stat.Append("| - none -");
- // draw
- Application.DDraw->TextOut(Stat.getData(), ::GraphicsResource.FontRegular, 1.0, cgo.Surface,cgo.X + 20,cgo.Y + 50);
- }
- bool C4Network2::InitNetIO(bool fNoClientID, bool fHost)
- {
- // clear
- NetIO.Clear();
- Config.Network.CheckPortsForCollisions();
- // discovery: disable for client
- int16_t iPortDiscovery = fHost ? Config.Network.PortDiscovery : -1;
- int16_t iPortRefServer = fHost ? Config.Network.PortRefServer : -1;
- // init subclass
- if(!NetIO.Init(Config.Network.PortTCP, Config.Network.PortUDP, iPortDiscovery, iPortRefServer, fHost))
- return false;
- // set core (unset ID if sepecified, has to be set later)
- C4ClientCore Core = Game.Clients.getLocalCore();
- if(fNoClientID) Core.SetID(C4ClientIDUnknown);
- NetIO.SetLocalCCore(Core);
- // safe addresses of local client
- Clients.GetLocal()->AddLocalAddrs(
- NetIO.hasTCP() ? Config.Network.PortTCP : -1,
- NetIO.hasUDP() ? Config.Network.PortUDP : -1);
- // ok
- return true;
- }
- void C4Network2::HandleConn(const C4PacketConn &Pkt, C4Network2IOConnection *pConn, C4Network2Client *pClient)
- {
- // security
- if(!pConn) return;
- // 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?
- if(pConn->isClosed())
- return;
- // set up core
- const C4ClientCore &CCore = Pkt.getCCore();
- C4ClientCore NewCCore = CCore;
- // accept connection?
- StdStrBuf reply;
- bool fOK = false;
- // search client
- if(!pClient && Pkt.getCCore().getID() != C4ClientIDUnknown)
- pClient = Clients.GetClient(Pkt.getCCore());
- // check engine version
- bool fWrongPassword = false;
- if(Pkt.getVer() != C4XVERBUILD)
- {
- reply.Format("wrong engine (%d, I have %d)", Pkt.getVer(), C4XVERBUILD);
- fOK = false;
- }
- else
- {
- if(pClient)
- if(CheckConn(NewCCore, pConn, pClient, reply.getData()))
- {
- // accept
- if(!reply) reply = "connection accepted";
- fOK = true;
- }
- // client: host connection?
- if(!fOK && !isHost() && Status.getState() == GS_Init && !Clients.GetHost())
- if(HostConnect(NewCCore, pConn, reply.getData()))
- {
- // accept
- if(!reply) reply = "host connection accepted";
- fOK = true;
- }
- // host: client join? (NewCCore will be changed by Join()!)
- if(!fOK && isHost() && !pClient)
- {
- // check password
- if(!sPassword.isNull() && !SEqual(Pkt.getPassword(), sPassword.getData()))
- {
- reply = "wrong password";
- fWrongPassword = true;
- }
- // registered join only
- else if (Game.RegJoinOnly && !SLen(NewCCore.getCUID()))
- {
- reply = "registered join only";
- }
- // accept join
- else if(Join(NewCCore, pConn, reply.getData()))
- {
- // save core
- pConn->SetCCore(NewCCore);
- // accept
- if(!reply) reply = "join accepted";
- fOK = true;
- }
- }
- }
- // denied? set default reason
- if(!fOK && !reply) reply = "connection denied";
- // OK and already half accepted? Skip (double-checked: ok).
- if(fOK && pConn->isHalfAccepted())
- return;
- // send answer
- C4PacketConnRe pcr(fOK, fWrongPassword, reply.getData());
- if(!pConn->Send(MkC4NetIOPacket(PID_ConnRe, pcr)))
- return;
- // accepted?
- if(fOK)
- {
- // set status
- if(!pConn->isClosed())
- pConn->SetHalfAccepted();
- }
- // denied? close
- else
- {
- // log & close
- LogSilentF("Network: connection by %s (%s:%d) blocked: %s", CCore.getName(), inet_ntoa(pConn->getPeerAddr().sin_addr), htons(pConn->getPeerAddr().sin_port), reply.getData());
- pConn->Close();
- }
- }
- bool C4Network2::CheckConn(const C4ClientCore &CCore, C4Network2IOConnection *pConn, C4Network2Client *pClient, const char *szReply)
- {
- if(!pConn || !pClient) return false;
- // already connected? (shouldn't happen really)
- if(pClient->hasConn(pConn))
- { szReply = "already connected"; return true; }
- // check core
- if(CCore.getDiffLevel(pClient->getCore()) > C4ClientCoreDL_IDMatch)
- { szReply = "wrong client core"; return false; }
- // check address
- if(pClient->isConnected() && pClient->getMsgConn()->getPeerAddr().sin_addr.s_addr != pConn->getPeerAddr().sin_addr.s_addr)
- { szReply = "wrong address"; return false; }
- // accept
- return true;
- }
- bool C4Network2::HostConnect(const C4ClientCore &CCore, C4Network2IOConnection *pConn, const char *szReply)
- {
- if(!pConn) return false;
- if(!CCore.isHost()) { szReply = "not host"; return false; }
- // create client class for host
- // (core is unofficial, see InitClient() - will be overwritten later in HandleJoinData)
- C4Client *pClient = Game.Clients.Add(CCore);
- if(!pClient) return false;
- // accept
- return true;
- }
- bool C4Network2::Join(C4ClientCore &CCore, C4Network2IOConnection *pConn, const char *szReply)
- {
- if(!pConn) return false;
- // 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; }
- // find free client id
- CCore.SetID(iNextClientID++);
- // observer?
- if(!fAllowJoin) CCore.SetObserver(true);
- // deactivate - client will have to ask for activation.
- CCore.SetActivated(false);
- // Name already in use? Find unused one
- if(Clients.GetClient(CCore.getName()))
- {
- 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);
- while(Clients.GetClient(szNewName));
- CCore.SetName(szNewName);
- }
- // join client
- ::Control.DoInput(CID_ClientJoin, new C4ControlClientJoin(CCore), CDT_Direct);
- // get client, set status
- C4Network2Client *pClient = Clients.GetClient(CCore);
- if(pClient) pClient->SetStatus(NCS_Joining);
- // 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
- if(!pConn) return;
- if(!pClient) { pConn->Close(); return; }
- // negative reply?
- if(!Pkt.isOK())
- {
- // 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?
- if(!pConn->isOpen())
- return;
- // already accepted? ignore
- if(pConn->isAccepted() && !pConn->isAutoAccepted()) return;
- // first connection?
- bool fFirstConnection = !pClient->isConnected();
- // accept connection
- pConn->SetAccepted(); pConn->ResetAutoAccepted();
- // add connection
- pConn->SetCCore(pClient->getCore());
- if(pConn->getNetClass() == NetIO.MsgIO()) pClient->SetMsgConn(pConn);
- if(pConn->getNetClass() == NetIO.DataIO()) pClient->SetDataConn(pConn);
- // add peer connect address to client address list
- if(pConn->getConnectAddr().sin_addr.s_addr)
- {
- 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
- if(!pClient->hasJoinData() || pClient->isRemoved()) return;
- // status doesn't match?
- if(nStatus.getState() != Status.getState() || nStatus.getTargetCtrlTick() < Status.getTargetCtrlTick())
- return;
- // host: wait until all clients are ready
- if(isHost())
- {
- // check: target tick change?
- if(!fStatusAck && nStatus.getTargetCtrlTick() > Status.getTargetCtrlTick())
- // take the new status
- ChangeGameStatus(nStatus.getState(), nStatus.getTargetCtrlTick());
- // already acknowledged? Send another ack
- if(fStatusAck)
- pClient->SendMsg(MkC4NetIOPacket(PID_StatusAck, nStatus));
- // mark as ready (will clear chase-flag)
- pClient->SetStatus(NCS_Ready);
- // check: everyone ready?
- if(!fStatusAck && fStatusReached)
- CheckStatusAck();
- }
- else
- {
- // target tick doesn't match? ignore
- if(nStatus.getTargetCtrlTick() != Status.getTargetCtrlTick())
- 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)
- if(fStatusReached)
- {
- // client: set flags, call handler
- fStatusAck = true; fChasing = false;
- OnStatusAck();
- }
- }
- }
- void C4Network2::HandleActivateReq(int32_t iTick, C4Network2Client *pByClient)
- {
- if(!isHost()) return;
- // not allowed or already activated? ignore
- if(pByClient->isObserver() || pByClient->isActivated()) return;
- // not joined completely yet? ignore
- if(!pByClient->isWaitedFor()) return;
- // check behind limit
- if(isRunning())
- {
- // make a guess how much the client lags.
- int32_t iLagFrames = BoundBy(pByClient->getMsgConn()->getPingTime() * Game.FPS / 500, 0, 100);
- if(iTick < Game.FrameCounter - iLagFrames - C4NetMaxBehind4Activation)
- return;
- }
- // activate him
- ::Control.DoInput(CID_ClientUpdate,
- new C4ControlClientUpdate(pByClient->getID(), CUT_Activate, true),
- CDT_Sync);
- }
- void C4Network2::HandleJoinData(const C4PacketJoinData &rPkt)
- {
- // init only
- if(Status.getState() != GS_Init)
- { LogSilentF("Network: unexpected join data received!"); return; }
- // get client ID
- if(rPkt.getClientID() == C4ClientIDUnknown)
- { 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());
- if(Status.getState() != GS_Lobby && Status.getState() != GS_Pause && Status.getState() != GS_Go)
- { 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());
- if(!pLocalClient)
- { LogSilentF("Network: Could not find local client in join data!"); Clear(); return; }
- // save back dynamic data
- ResDynamic = rPkt.getDynamicCore();
- iDynamicTick = rPkt.getStartCtrlTick();
- // initialize control
- ::Control.ControlRate = rPkt.Parameters.ControlRate;
- 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 ressource list
- Game.Parameters.GameRes.InitNetwork(&ResList);
- // load dynamic
- if(!ResList.AddByCore(ResDynamic))
- { LogFatal("Network: can not not retrieve dynamic!"); Clear(); return; }
- // load player ressources
- 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",
- pClient->getName(), inet_ntoa(pConn->getPeerAddr().sin_addr), htons(pConn->getPeerAddr().sin_port),
- NetIO.getNetIOName(pConn->getNetClass()), szMsg ? szMsg : "");
- // first connection for this peer? call special handler
- if(fFirstConnection) OnClientConnect(pClient, pConn);
- }
- void C4Network2::OnConnectFail(C4Network2IOConnection *pConn)
- {
- LogSilentF("Network: %s connection to %s:%d failed!", NetIO.getNetIOName(pConn->getNetClass()),
- inet_ntoa(pConn->getPeerAddr().sin_addr), htons(pConn->getPeerAddr().sin_port));
- // 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());
- if(pClient && !pClient->isConnected())
- OnClientDisconnect(pClient);
- }
- void C4Network2::OnDisconnect(C4Network2Client *pClient, C4Network2IOConnection *pConn)
- {
- LogSilentF("Network: %s connection to %s (%s:%d) lost!", NetIO.getNetIOName(pConn->getNetClass()),
- pClient->getName(), inet_ntoa(pConn->getPeerAddr().sin_addr), htons(pConn->getPeerAddr().sin_port));
- // connection lost?
- if(!pClient->isConnected())
- OnClientDisconnect(pClient);
- }
- void C4Network2::OnClientConnect(C4Network2Client *pClient, C4Network2IOConnection *pConn)
- {
- // host: new client?
- if(isHost())
- {
- // dynamic available?
- if(!pClient->hasJoinData())
- 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.
- if(isHost())
- {
- // 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)
- if(!fStatusReached)
- if(Status.getState() == GS_Go || Status.getState() == GS_Pause)
- ChangeGameStatus(Status.getState(), ::Control.ControlTick);
- }
- // host disconnected? Clear up
- if(!isHost() && pClient->isHost())
- {
- 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)
- {
- if(pClient->hasJoinData()) return;
- // host only, scenario must be available
- assert(isHost());
- // dynamic available?
- if(ResDynamic.isNull() || iDynamicTick < ::Control.ControlTick)
- {
- fDynamicNeeded = true;
- // add synchronization control (will callback, see C4Game::Synchronize)
- ::Control.DoInput(CID_Synchronize, new C4ControlSynchronize(false, true), CDT_Sync);
- 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);
- if(!iLastChaseTargetUpdate) iLastChaseTargetUpdate = time(NULL);
- }
- 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; uint32_t iTimeout = timeGetTime() + iTimeoutLen;
- // wait for ressource
- while(isEnabled())
- {
- // find ressource
- C4Network2Res::Ref pRes = ResList.getRefRes(Core.getID());
- // res not found?
- if(!pRes)
- if(Core.isNull())
- {
- // should wait for core?
- if(!fWaitForCore) return NULL;
- }
- else
- {
- // start loading
- pRes = ResList.AddByCore(Core);
- }
- // res found and loaded completely
- else if(!pRes->isLoading())
- {
- // log
- if(fLog) LogF(LoadResStr("IDS_NET_RECEIVED"), szResName, pRes->getCore().getFileName());
- // return
- if (pDlg) delete pDlg;
- return pRes;
- }
- // check: progress?
- if(pRes && pRes->getPresentPercent() != iProcess)
- {
- iProcess = pRes->getPresentPercent();
- iTimeout = timeGetTime() + iTimeoutLen;
- }
- else
- {
- // if not: check timeout
- if(timeGetTime() > iTimeout)
- {
- LogFatal(FormatString(LoadResStr("IDS_NET_ERR_RESTIMEOUT"), szResName).getData());
- if (pDlg) delete pDlg;
- return NULL;
- }
- }
- // log
- if(!fLog)
- {
- LogF(LoadResStr("IDS_NET_WAITFORRES"), szResName);
- fLog = true;
- }
- // show progress dialog
- if(!pDlg && !Console.Active && ::pGUI)
- {
- // create
- pDlg = new C4GUI::ProgressDialog(FormatString(LoadResStr("IDS_NET_WAITFORRES"), szResName).getData(),
- LoadResStr("IDS_NET_CAPTION"), 100, 0, C4GUI::Ico_NetWait);
- // show dialog
- if(!pDlg->Show(::pGUI, true)) { delete pDlg; return NULL; }
- }
- // wait
- if(pDlg)
- {
- // set progress bar
- pDlg->SetProgress(iProcess);
- // execute (will do message handling)
- if(!pDlg->Execute())
- { if (pDlg) delete pDlg; return NULL; }
- // aborted?
- if(!::pGUI) return NULL;
- if(pDlg->IsAborted()) break;
- }
- else
- {
- if(!Application.ScheduleProcs(iTimeout - timeGetTime()))
- { return NULL; }
- }
- }
- // aborted
- if(!::pGUI) return NULL;
- delete pDlg;
- return NULL;
- }
- bool C4Network2::CreateDynamic(bool fInit)
- {
- if(!isHost()) return false;
- // 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);
- if(!ResList.FindTempResFileName(szDynamicBase, szDynamicFilename))
- LogF(LoadResStr("IDS_NET_SAVE_ERR_CREATEDYNFILE"));
- // save dynamic data
- C4GameSaveNetwork SaveGame(fInit);
- if (!SaveGame.Save(szDynamicFilename) || !SaveGame.Close())
- { Log(LoadResStr("IDS_NET_SAVE_ERR_SAVEDYNFILE")); return false; }
- // add ressource
- C4Network2Res::Ref pRes = ResList.AddByFile(szDynamicFilename, true, NRT_Dynamic);
- if(!pRes) { Log(LoadResStr("IDS_NET_SAVE_ERR_ADDDYNDATARES")); return false; }
- // save
- ResDynamic = pRes->getCore();
- iDynamicTick = ::Control.getNextControlTick();
- fDynamicNeeded = false;
- // ok
- return true;
- }
- void C4Network2::RemoveDynamic()
- {
- C4Network2Res::Ref pRes = ResList.getRefRes(ResDynamic.getID());
- if(pRes) pRes->Remove();
- 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.
- if(Status.getState() == GS_Lobby) return true;
- if(Status.getState() == GS_Pause && fStatusAck) return true;
- return false;
- }
- bool C4Network2::ChangeGameStatus(C4NetGameState enState, int32_t iTargetCtrlTick, int32_t iCtrlMode)
- {
- // change game status, announce. Can only be done by host.
- if(!isHost()) return false;
- // set status
- Status.Set(enState, iTargetCtrlTick);
- // update reference
- InvalidateReference();
- // control mode change?
- if(iCtrlMode >= 0) Status.SetCtrlMode(iCtrlMode);
- // log
- LogSilentF("Network: going into status %s (tick %d)", Status.getStateName(), iTargetCtrlTick);
- // 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?
- if(fStatusReached) return;
- if(Status.getState() == GS_Lobby)
- fStatusReached = fLobbyRunning;
- // game go / pause: control must be initialized and target tick reached
- else if(Status.getState() == GS_Go || Status.getState() == GS_Pause)
- {
- if(Game.IsRunning || fFromFinalInit)
- {
- // Make sure we have reached the tick and the control queue is empty (except for chasing)
- if(::Control.CtrlTickReached(Status.getTargetCtrlTick()) &&
- (fChasing || !pControl->CtrlReady(::Control.ControlTick)))
- fStatusReached = true;
- else
- {
- // run ctrl so the tick can be reached
- pControl->SetRunning(true, Status.getTargetCtrlTick());
- Game.HaltCount = 0;
- Console.UpdateHaltCtrls(!! Game.HaltCount);
- }
- }
- }
- if(!fStatusReached) return;
- // call handler
- OnStatusReached();
- // host?
- if(isHost())
- // all clients ready?
- CheckStatusAck();
- else
- {
- Status.SetTargetTick(::Control.ControlTick);
- // send response to host
- Clients.SendMsgToHost(MkC4NetIOPacket(PID_StatusAck, Status));
- // do delayed activation request
- if(fDelayedActivateReq)
- {
- fDelayedActivateReq = false;
- RequestActivate();
- }
- }…