PageRenderTime 273ms CodeModel.GetById 18ms app.highlight 204ms RepoModel.GetById 1ms app.codeStats 2ms

/src/network/C4Network2.cpp

https://bitbucket.org/randrian/openclonk2
C++ | 2901 lines | 1977 code | 303 blank | 621 comment | 620 complexity | 7fd9b93aaacc6fedfca7a5dba0a3ac2e MD5 | raw file
Possible License(s): WTFPL, 0BSD, LGPL-2.1, CC-BY-3.0

Large files files are truncated, but you can click here to view the full file

   1/*
   2 * OpenClonk, http://www.openclonk.org
   3 *
   4 * Copyright (c) 2004-2009  Peter Wortmann
   5 * Copyright (c) 2004-2009  Sven Eberhardt
   6 * Copyright (c) 2005-2006, 2009  G�nther Brammer
   7 * Copyright (c) 2006  Florian Gro�
   8 * Copyright (c) 2007-2008  Matthes Bender
   9 * Copyright (c) 2009  Nicolas Hake
  10 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
  11 *
  12 * Portions might be copyrighted by other authors who have contributed
  13 * to OpenClonk.
  14 *
  15 * Permission to use, copy, modify, and/or distribute this software for any
  16 * purpose with or without fee is hereby granted, provided that the above
  17 * copyright notice and this permission notice appear in all copies.
  18 * See isc_license.txt for full license and disclaimer.
  19 *
  20 * "Clonk" is a registered trademark of Matthes Bender.
  21 * See clonk_trademark_license.txt for full license.
  22 */
  23#include <C4Include.h>
  24#include <C4Network2.h>
  25#include <C4Version.h>
  26
  27#ifndef BIG_C4INCLUDE
  28#include <C4Log.h>
  29#include <C4Application.h>
  30#include <C4Console.h>
  31#include <C4GameSave.h>
  32#include <C4RoundResults.h>
  33#include <C4Game.h>
  34#include <C4GraphicsSystem.h>
  35#include <C4GraphicsResource.h>
  36#include <C4GameControl.h>
  37
  38// lobby
  39#include <C4Gui.h>
  40#include <C4GameLobby.h>
  41
  42#include <C4Network2Dialogs.h>
  43#include <C4League.h>
  44#endif
  45
  46#ifdef _WIN32
  47#include <direct.h>
  48#endif
  49#ifndef HAVE_WINSOCK
  50#include <sys/socket.h>
  51#include <netinet/in.h>
  52#include <arpa/inet.h>
  53#endif
  54
  55// compile options
  56#ifdef _MSC_VER
  57#pragma warning (disable: 4355)
  58#endif
  59
  60// *** C4Network2Status
  61
  62C4Network2Status::C4Network2Status()
  63	: eState(GS_None), iTargetCtrlTick(-1)
  64{
  65
  66}
  67
  68const char *C4Network2Status::getStateName() const
  69{
  70	switch(eState)
  71	{
  72	case GS_None: return "none";
  73	case GS_Init: return "init";
  74	case GS_Lobby: return "lobby";
  75	case GS_Pause: return "pause";
  76	case GS_Go: return "go";
  77	}
  78	return "???";
  79}
  80
  81const char *C4Network2Status::getDescription() const
  82{
  83	switch(eState)
  84	{
  85	case GS_None: return LoadResStr("IDS_DESC_NOTINITED");
  86	case GS_Init: return LoadResStr("IDS_DESC_WAITFORHOST");
  87	case GS_Lobby: return LoadResStr("IDS_DESC_EXPECTING");
  88	case GS_Pause: return LoadResStr("IDS_DESC_GAMEPAUSED");
  89	case GS_Go: return LoadResStr("IDS_DESC_GAMERUNNING");
  90	}
  91	return LoadResStr("IDS_DESC_UNKNOWNGAMESTATE");
  92}
  93
  94void C4Network2Status::Set(C4NetGameState enState, int32_t inTargetTick)
  95{
  96	eState = enState; iTargetCtrlTick = inTargetTick;
  97}
  98
  99void C4Network2Status::SetCtrlMode(int32_t inCtrlMode)
 100{
 101	iCtrlMode = inCtrlMode;
 102}
 103
 104void C4Network2Status::SetTargetTick(int32_t inTargetCtrlTick)
 105{
 106	iTargetCtrlTick = inTargetCtrlTick;
 107}
 108
 109void C4Network2Status::Clear()
 110{
 111	eState = GS_None; iTargetCtrlTick = -1;
 112}
 113
 114void C4Network2Status::CompileFunc(StdCompiler *pComp)
 115{
 116	CompileFunc(pComp, false);
 117}
 118
 119void C4Network2Status::CompileFunc(StdCompiler *pComp, bool fReference)
 120{
 121	StdEnumEntry<C4NetGameState> GameStates[] =
 122	{
 123		{ "None",					GS_None			},
 124		{ "Init",					GS_Init			},
 125		{ "Lobby",				GS_Lobby		},
 126		{ "Paused",				GS_Pause		},
 127		{ "Running",			GS_Go				},
 128	};
 129  pComp->Value(mkNamingAdapt(mkEnumAdaptT<uint8_t>(eState, GameStates), "State", GS_None));
 130  pComp->Value(mkNamingAdapt(mkIntPackAdapt(iCtrlMode), "CtrlMode", -1));
 131
 132	if(!fReference)
 133		pComp->Value(mkNamingAdapt(mkIntPackAdapt(iTargetCtrlTick), "TargetTick", -1));
 134}
 135
 136// *** C4Network2
 137
 138C4Network2::C4Network2()
 139	: Clients(&NetIO),
 140		fAllowJoin(false),
 141		iDynamicTick(-1), fDynamicNeeded(false),
 142		fStatusAck(false), fStatusReached(false),
 143		fChasing(false),
 144		pLobby(NULL), fLobbyRunning(false), pLobbyCountdown(NULL),
 145		pControl(NULL),
 146		iNextClientID(0),
 147    iLastActivateRequest(0),
 148		iLastChaseTargetUpdate(0),
 149    iLastReferenceUpdate(0),
 150    iLastLeagueUpdate(0),
 151    pLeagueClient(NULL),
 152		fDelayedActivateReq(false),
 153		pVoteDialog(NULL),
 154		fPausedForVote(false),
 155		iLastOwnVoting(0),
 156		fStreaming(NULL)
 157{
 158
 159}
 160
 161C4Network2::~C4Network2()
 162{
 163	Clear();
 164}
 165
 166bool C4Network2::InitHost(bool fLobby)
 167{
 168	if(isEnabled()) Clear();
 169	// initialize everything
 170	Status.Set(fLobby ? GS_Lobby : GS_Go, ::Control.ControlTick);
 171	Status.SetCtrlMode(Config.Network.ControlMode);
 172	fHost = true;
 173	fStatusAck = fStatusReached = true;
 174	fChasing = false;
 175	fAllowJoin = false;
 176	iNextClientID = C4ClientIDStart;
 177	// initialize client list
 178	Clients.Init(&Game.Clients, true);
 179	// initialize resource list
 180	if(!ResList.Init(Game.Clients.getLocalID(), &NetIO))
 181		 { LogFatal("Network: failed to initialize resource list!"); Clear(); return false; }
 182	if(!Game.Parameters.InitNetwork(&ResList))
 183		return false;
 184	// create initial dynamic
 185	if(!CreateDynamic(true))
 186		return false;
 187	// initialize net i/o
 188	if(!InitNetIO(false, true))
 189		{ Clear(); return false; }
 190	// init network control
 191	pControl = &::Control.Network;
 192	pControl->Init(C4ClientIDHost, true, ::Control.getNextControlTick(), true, this);
 193  // init league
 194	bool fCancel = true;
 195  if(!InitLeague(&fCancel) || !LeagueStart(&fCancel))
 196	{
 197		// deinit league
 198		DeinitLeague();
 199		// user cancelled?
 200		if(fCancel)
 201			return false;
 202		// in console mode, bail out
 203#ifdef USE_CONSOLE
 204		return false;
 205#endif
 206	}
 207  // allow connect
 208  NetIO.SetAcceptMode(true);
 209	// timer
 210	Application.Add(this);
 211	// ok
 212	return true;
 213}
 214
 215C4Network2::InitResult C4Network2::InitClient(const C4Network2Reference &Ref, bool fObserver)
 216{
 217	if(isEnabled()) Clear();
 218	// Get host core
 219	const C4ClientCore &HostCore = Ref.Parameters.Clients.getHost()->getCore();
 220	// repeat if wrong password
 221	fWrongPassword = Ref.isPasswordNeeded();
 222	StdStrBuf Password;
 223	for(;;)
 224	{
 225		// ask for password (again)?
 226		if(fWrongPassword)
 227		{
 228			Password.Take(QueryClientPassword());
 229			if(!Password.getLength())
 230				return IR_Error;
 231			fWrongPassword = false;
 232		}
 233		// copy addresses
 234		C4Network2Address Addrs[C4ClientMaxAddr];
 235		for(int i = 0; i < Ref.getAddrCnt(); i++)
 236			Addrs[i] = Ref.getAddr(i);
 237		// Try to connect to host
 238		if(InitClient(Addrs, Ref.getAddrCnt(), HostCore, Password.getData()) == IR_Fatal)
 239			return IR_Fatal;
 240		// success?
 241		if(isEnabled())
 242			break;
 243		// Retry only for wrong password
 244		if(!fWrongPassword)
 245		{
 246			LogSilent("Network: Could not connect!");
 247			return IR_Error;
 248		}
 249	}
 250	// initialize ressources
 251	if(!Game.Parameters.InitNetwork(&ResList))
 252		return IR_Fatal;
 253  // init league
 254  if(!InitLeague(NULL))
 255	{
 256		// deinit league
 257		DeinitLeague();
 258		return IR_Fatal;
 259	}
 260  // allow connect
 261  NetIO.SetAcceptMode(true);
 262	// timer
 263	Application.Add(this);
 264	// ok, success
 265	return IR_Success;
 266}
 267
 268C4Network2::InitResult C4Network2::InitClient(const class C4Network2Address *pAddrs, int iAddrCount, const C4ClientCore &HostCore, const char *szPassword)
 269{
 270	// initialization
 271	Status.Set(GS_Init, -1);
 272	fHost = false;
 273	fStatusAck = fStatusReached = true;
 274	fChasing = true;
 275	fAllowJoin = false;
 276	// initialize client list
 277	Game.Clients.Init(C4ClientIDUnknown);
 278	Clients.Init(&Game.Clients, false);
 279	// initialize resource list
 280	if(!ResList.Init(Game.Clients.getLocalID(), &NetIO))
 281		{ LogFatal(LoadResStr("IDS_NET_ERR_INITRESLIST")); Clear(); return IR_Fatal; }
 282	// initialize net i/o
 283	if(!InitNetIO(true, false))
 284		{ Clear(); return IR_Fatal; }
 285	// set network control
 286	pControl = &::Control.Network;
 287	// set exclusive connection mode
 288	NetIO.SetExclusiveConnMode(true);
 289	// try to connect host
 290	StdStrBuf strAddresses; int iSuccesses = 0;
 291	for(int i = 0; i < iAddrCount; i++)
 292		if(!pAddrs[i].isIPNull())
 293			{
 294			// connection
 295			if(!NetIO.Connect(pAddrs[i].getAddr(), pAddrs[i].getProtocol(), HostCore, szPassword))
 296				continue;
 297			// format for message
 298			if(strAddresses.getLength())
 299				strAddresses.Append(", ");
 300			strAddresses.Append(pAddrs[i].toString());
 301			iSuccesses++;
 302			}
 303	// no connection attempt running?
 304	if(!iSuccesses)
 305		{ Clear(); return IR_Error; }
 306	// log
 307	StdStrBuf strMessage = FormatString(LoadResStr("IDS_NET_CONNECTHOST"), strAddresses.getData());
 308	Log(strMessage.getData());
 309	// show box
 310	C4GUI::MessageDialog *pDlg = NULL;
 311	if(::pGUI && !Console.Active)
 312	{
 313		// create & show
 314		pDlg = new C4GUI::MessageDialog(strMessage.getData(), LoadResStr("IDS_NET_JOINGAME"),
 315			C4GUI::MessageDialog::btnAbort, C4GUI::Ico_NetWait, C4GUI::MessageDialog::dsMedium);
 316		if(!pDlg->Show(::pGUI, true)) { Clear(); return IR_Fatal; }
 317	}
 318	// wait for connect / timeout / abort by user (host will change status on succesful connect)
 319	while(Status.getState() == GS_Init)
 320	{
 321		if(!Application.ScheduleProcs(100))
 322			{ if(::pGUI && pDlg) delete pDlg; return IR_Fatal;}
 323		if(pDlg && pDlg->IsAborted())
 324			{ if(::pGUI && pDlg) delete pDlg; return IR_Fatal; }
 325	}
 326	// Close dialog
 327	if(::pGUI && pDlg) delete pDlg;
 328	// error?
 329	if(!isEnabled())
 330		return IR_Error;
 331	// deactivate exclusive connection mode
 332	NetIO.SetExclusiveConnMode(false);
 333	return IR_Success;
 334}
 335
 336bool C4Network2::DoLobby()
 337{
 338	// shouldn't do lobby?
 339	if(!isEnabled() || (!isHost() && !isLobbyActive()))
 340		return true;
 341
 342	// lobby runs
 343	fLobbyRunning = true;
 344  fAllowJoin = true;
 345  Log(LoadResStr("IDS_NET_LOBBYWAITING"));
 346
 347	// client: lobby status reached, message to host
 348	if(!isHost())
 349		CheckStatusReached();
 350	// host: set lobby mode
 351	else
 352		ChangeGameStatus(GS_Lobby, 0);
 353
 354	// determine lobby type
 355	bool fFullscreenLobby = !Console.Active && (lpDDraw->GetEngine() != GFXENGN_NOGFX);
 356
 357	if(!fFullscreenLobby)
 358		{
 359		// console lobby - update console
 360		if (Console.Active) Console.UpdateMenus();
 361		// init lobby countdown if specified
 362		if (Game.iLobbyTimeout) StartLobbyCountdown(Game.iLobbyTimeout);
 363		// do console lobby
 364		while(isLobbyActive())
 365			if (!Application.ScheduleProcs())
 366				{ Clear(); return false; }
 367		}
 368	else
 369		{
 370		// fullscreen lobby
 371
 372		// init lobby dialog
 373		pLobby = new C4GameLobby::MainDlg(isHost());
 374		if (!pLobby->FadeIn(::pGUI)) { delete pLobby; pLobby = NULL; Clear(); return false; }
 375
 376		// init lobby countdown if specified
 377		if (Game.iLobbyTimeout) StartLobbyCountdown(Game.iLobbyTimeout);
 378
 379		// while state lobby: keep looping
 380		while(isLobbyActive() && ::pGUI && pLobby && pLobby->IsShown())
 381			if (!Application.ScheduleProcs())
 382				{ Clear(); return false; }
 383
 384		// check whether lobby was aborted; first checking ::pGUI
 385		// (because an external call to Game.Clear() would invalidate pLobby)
 386		if (!::pGUI) { pLobby = NULL; Clear(); return false; }
 387		if (pLobby && pLobby->IsAborted()) { delete pLobby; pLobby = NULL; Clear(); return false; }
 388
 389		// deinit lobby
 390		if (pLobby && pLobby->IsShown()) pLobby->Close(true);
 391		delete pLobby; pLobby = NULL;
 392
 393		// close any other dialogs
 394		if (::pGUI) ::pGUI->CloseAllDialogs(false);
 395		}
 396
 397	// lobby end
 398	delete pLobbyCountdown; pLobbyCountdown = NULL;
 399	fLobbyRunning = false;
 400  fAllowJoin = !Config.Network.NoRuntimeJoin;
 401
 402	// notify user that the lobby has ended (for people who tasked out)
 403	Application.NotifyUserIfInactive();
 404
 405	// notify lobby end
 406	bool fGameGo = isEnabled();
 407	if (fGameGo) Log(LoadResStr("IDS_PRC_GAMEGO"));;
 408
 409	// disabled?
 410	return fGameGo;
 411}
 412
 413bool C4Network2::Start()
 414{
 415	if(!isEnabled() || !isHost()) return false;
 416	// change mode: go
 417	ChangeGameStatus(GS_Go, ::Control.ControlTick);
 418	return true;
 419}
 420
 421bool C4Network2::Pause()
 422{
 423	if(!isEnabled() || !isHost()) return false;
 424	// change mode: pause
 425	return ChangeGameStatus(GS_Pause, ::Control.getNextControlTick());
 426}
 427
 428bool C4Network2::Sync()
 429{
 430	// host only
 431	if(!isEnabled() || !isHost()) return false;
 432	// already syncing the network?
 433	if(!fStatusAck)
 434		{
 435		// maybe we are already sync?
 436		if(fStatusReached) CheckStatusAck();
 437		return true;
 438		}
 439	// already sync?
 440	if(isFrozen()) return true;
 441	// ok, so let's do a sync: change in the same state we are already in
 442	return ChangeGameStatus(Status.getState(), ::Control.getNextControlTick());
 443}
 444
 445bool C4Network2::FinalInit()
 446{
 447	// check reach
 448	CheckStatusReached(true);
 449	// reached, waiting for ack?
 450	if(fStatusReached && !fStatusAck)
 451	{
 452		// wait for go acknowledgement
 453		Log(LoadResStr("IDS_NET_JOINREADY"));
 454
 455		// any pending keyboard commands should not be routed to cancel the wait dialog - flish the message queue!
 456		if(!Application.FlushMessages()) return false;
 457
 458		// show box
 459		C4GUI::Dialog *pDlg = NULL;
 460		if(::pGUI && !Console.Active)
 461		{
 462			// seperate dlgs for host/client
 463			if (isHost())
 464				pDlg = new C4Network2StartWaitDlg();
 465			else
 466				pDlg = new C4GUI::MessageDialog(LoadResStr("IDS_NET_WAITFORSTART"), LoadResStr("IDS_NET_CAPTION"),
 467						C4GUI::MessageDialog::btnAbort, C4GUI::Ico_NetWait, C4GUI::MessageDialog::dsSmall);
 468			// show it
 469			if(!pDlg->Show(::pGUI, true)) return false;
 470		}
 471
 472		// wait for acknowledgement
 473		while(fStatusReached && !fStatusAck)
 474		{
 475			if(pDlg)
 476			{
 477				// execute
 478				if(!pDlg->Execute()) { delete pDlg; Clear(); return false; }
 479				// aborted?
 480				if(!::pGUI) { Clear(); return false;}
 481				if(pDlg->IsAborted()) { delete pDlg; Clear(); return false; }
 482			}
 483			else if(!Application.ScheduleProcs())
 484				{ Clear(); return false; }
 485		}
 486		if(::pGUI && pDlg) delete pDlg;
 487		// log
 488		Log(LoadResStr("IDS_NET_START"));
 489	}
 490  // synchronize
 491  Game.SyncClearance();
 492  Game.Synchronize(false);
 493  // finished
 494	return isEnabled();
 495}
 496
 497
 498bool C4Network2::RetrieveScenario(char *szScenario)
 499{
 500	// client only
 501	if(isHost()) return false;
 502
 503	// wait for scenario
 504	C4Network2Res::Ref pScenario = RetrieveRes(*Game.Parameters.Scenario.getResCore(),
 505                                              C4NetResRetrieveTimeout, LoadResStr("IDS_NET_RES_SCENARIO"));
 506	if(!pScenario)
 507		return false;
 508
 509	// wait for dynamic data
 510	C4Network2Res::Ref pDynamic = RetrieveRes(ResDynamic, C4NetResRetrieveTimeout, LoadResStr("IDS_NET_RES_DYNAMIC"));
 511	if(!pDynamic)
 512		return false;
 513
 514	// create unpacked copy of scenario
 515	if(!ResList.FindTempResFileName(FormatString("Combined%d.c4s", Game.Clients.getLocalID()).getData(), szScenario) ||
 516		 !C4Group_CopyItem(pScenario->getFile(), szScenario) ||
 517		 !C4Group_UnpackDirectory(szScenario))
 518		return false;
 519
 520	// create unpacked copy of dynamic data
 521	char szTempDynamic[_MAX_PATH + 1];
 522	if(!ResList.FindTempResFileName(pDynamic->getFile(), szTempDynamic) ||
 523		 !C4Group_CopyItem(pDynamic->getFile(), szTempDynamic) ||
 524		 !C4Group_UnpackDirectory(szTempDynamic))
 525		return false;
 526
 527	// unpack Material.c4g if materials need to be merged
 528	StdStrBuf MaterialScenario, MaterialDynamic;
 529	MaterialScenario.Format("%s" DirSep  C4CFN_Material, szScenario);
 530	MaterialDynamic.Format("%s" DirSep  C4CFN_Material, szTempDynamic);
 531	if(FileExists(MaterialScenario.getData()) && FileExists(MaterialDynamic.getData()))
 532		if(!C4Group_UnpackDirectory(MaterialScenario.getData()) ||
 533		   !C4Group_UnpackDirectory(MaterialDynamic.getData()))
 534		  return false;
 535
 536	// move all dynamic files to scenario
 537	C4Group ScenGrp;
 538	if(!ScenGrp.Open(szScenario) ||
 539		 !ScenGrp.Merge(szTempDynamic))
 540		return false;
 541	ScenGrp.Close();
 542
 543	// remove dynamic temp file
 544	EraseDirectory(szTempDynamic);
 545
 546	// remove dynamic - isn't needed any more and will soon be out-of-date
 547	pDynamic->Remove();
 548
 549	return true;
 550}
 551
 552void C4Network2::OnSec1Timer()
 553{
 554	Execute();
 555}
 556
 557void C4Network2::Execute()
 558{
 559
 560	// client connections
 561	Clients.DoConnectAttempts();
 562
 563	// status reached?
 564	CheckStatusReached();
 565
 566	if(isHost())
 567	{
 568		// remove dynamic
 569		if(!ResDynamic.isNull() && ::Control.ControlTick > iDynamicTick)
 570			RemoveDynamic();
 571		// Set chase target
 572		UpdateChaseTarget();
 573    // check for inactive clients and deactivate them
 574    DeactivateInactiveClients();
 575    // reference
 576    if(!iLastReferenceUpdate || time(NULL) > (time_t) (iLastReferenceUpdate + C4NetReferenceUpdateInterval))
 577	    if (NetIO.IsReferenceNeeded())
 578	    {
 579	      // create
 580	      C4Network2Reference *pRef = new C4Network2Reference();
 581	      pRef->InitLocal();
 582	      // set
 583	      NetIO.SetReference(pRef);
 584	      iLastReferenceUpdate = time(NULL);
 585	    }
 586    // league server reference
 587    if(!iLastLeagueUpdate || time(NULL) > (time_t) (iLastLeagueUpdate + iLeagueUpdateDelay))
 588    {
 589      LeagueUpdate();
 590    }
 591		// league update reply receive
 592		if(pLeagueClient && fHost && !pLeagueClient->isBusy() && pLeagueClient->getCurrentAction() == C4LA_Update)
 593		{
 594      LeagueUpdateProcessReply();
 595		}
 596		// voting timeout
 597		if(Votes.firstPkt() && time(NULL) > (time_t) (iVoteStartTime + C4NetVotingTimeout))
 598		{
 599			C4ControlVote *pVote = static_cast<C4ControlVote *>(Votes.firstPkt()->getPkt());
 600			::Control.DoInput(
 601				CID_VoteEnd,
 602				new C4ControlVoteEnd(pVote->getType(), false, pVote->getData()),
 603				CDT_Sync);
 604			iVoteStartTime = time(NULL);
 605		}
 606		// record streaming
 607		if(fStreaming)
 608			{
 609			StreamIn(false);
 610			StreamOut();
 611			}
 612	}
 613  else
 614  {
 615    // request activate, if neccessary
 616    if(iLastActivateRequest) RequestActivate();
 617  }
 618}
 619
 620void C4Network2::Clear()
 621{
 622	// stop timer
 623	Application.Remove(this);
 624	// stop streaming
 625	StopStreaming();
 626	// clear league
 627	if(pLeagueClient)
 628		{
 629		LeagueEnd();
 630		DeinitLeague();
 631		}
 632	// stop lobby countdown
 633	delete pLobbyCountdown; pLobbyCountdown = NULL;
 634	// cancel lobby
 635	delete pLobby; pLobby = NULL;
 636	fLobbyRunning = false;
 637	// deactivate
 638	Status.Clear();
 639	fStatusAck = fStatusReached = true;
 640	// if control mode is network: change to local
 641	if(::Control.isNetwork())
 642		::Control.ChangeToLocal();
 643	// clear all player infos
 644	Players.Clear();
 645	// remove all clients
 646	Clients.Clear();
 647	// close net classes
 648	NetIO.Clear();
 649	// clear ressources
 650	ResList.Clear();
 651	// clear password
 652	sPassword.Clear();
 653	// stuff
 654	fAllowJoin = false;
 655  iDynamicTick = -1; fDynamicNeeded = false;
 656  iLastActivateRequest = iLastChaseTargetUpdate = iLastReferenceUpdate = iLastLeagueUpdate = 0;
 657	fDelayedActivateReq = false;
 658	if(::pGUI) delete pVoteDialog; pVoteDialog = NULL;
 659	fPausedForVote = false;
 660	iLastOwnVoting = 0;
 661	// don't clear fPasswordNeeded here, it's needed by InitClient
 662}
 663
 664bool C4Network2::ToggleAllowJoin()
 665{
 666	// just toggle
 667	AllowJoin(!fAllowJoin);
 668	return true; // toggled
 669}
 670
 671bool C4Network2::ToggleClientListDlg()
 672	{
 673	C4Network2ClientListDlg::Toggle();
 674	return true;
 675	}
 676
 677void C4Network2::SetPassword(const char *szToPassword)
 678	{
 679	bool fHadPassword = isPassworded();
 680	// clear password?
 681	if (!szToPassword || !*szToPassword)
 682		sPassword.Clear();
 683	else
 684		// no? then set it
 685		sPassword.Copy(szToPassword);
 686	// if the has-password-state has changed, the reference is invalidated
 687	if (fHadPassword != isPassworded()) InvalidateReference();
 688	}
 689
 690StdStrBuf C4Network2::QueryClientPassword()
 691	{
 692	// ask client for a password; return nothing if user canceled
 693	StdStrBuf sCaption; sCaption.Copy(LoadResStr("IDS_MSG_ENTERPASSWORD"));
 694	C4GUI::InputDialog *pInputDlg = new C4GUI::InputDialog(LoadResStr("IDS_MSG_ENTERPASSWORD"), sCaption.getData(), C4GUI::Ico_Ex_Locked, NULL, false);
 695	pInputDlg->SetDelOnClose(false);
 696	if (!::pGUI->ShowModalDlg(pInputDlg, false))
 697		{
 698		if (C4GUI::IsGUIValid()) delete pInputDlg;
 699		return StdStrBuf();
 700		}
 701	// copy to buffer
 702	StdStrBuf Buf; Buf.Copy(pInputDlg->GetInputText());
 703	delete pInputDlg;
 704	return Buf;
 705	}
 706
 707void C4Network2::AllowJoin(bool fAllow)
 708{
 709	if(!isHost()) return;
 710	fAllowJoin = fAllow;
 711	if (Game.IsRunning)
 712		{
 713		::GraphicsSystem.FlashMessage(LoadResStr(fAllowJoin ? "IDS_NET_RUNTIMEJOINFREE" : "IDS_NET_RUNTIMEJOINBARRED"));
 714		Config.Network.NoRuntimeJoin = !fAllowJoin;
 715		}
 716}
 717
 718void C4Network2::SetAllowObserve(bool fAllow)
 719{
 720	if(!isHost()) return;
 721  fAllowObserve = fAllow;
 722}
 723
 724void C4Network2::SetCtrlMode(int32_t iCtrlMode)
 725{
 726	if(!isHost()) return;
 727  // no change?
 728  if(iCtrlMode == Status.getCtrlMode()) return;
 729  // change game status
 730  ChangeGameStatus(Status.getState(), ::Control.ControlTick, iCtrlMode);
 731}
 732
 733void C4Network2::OnConn(C4Network2IOConnection *pConn)
 734{
 735	// Nothing to do atm... New pending connections are managed mainly by C4Network2IO
 736	// until they are accepted, see PID_Conn/PID_ConnRe handlers in HandlePacket.
 737
 738	// Note this won't get called anymore because of this (see C4Network2IO::OnConn)
 739}
 740
 741void C4Network2::OnDisconn(C4Network2IOConnection *pConn)
 742{
 743  // could not establish host connection?
 744	if(Status.getState() == GS_Init && !isHost())
 745	{
 746		if(!NetIO.getConnectionCount())
 747			Clear();
 748		return;
 749	}
 750
 751	// connection failed?
 752	if(pConn->isFailed())
 753	{
 754		// call handler
 755		OnConnectFail(pConn);
 756		return;
 757	}
 758
 759	// search client
 760	C4Network2Client *pClient = Clients.GetClient(pConn);
 761	// not found? Search by ID (not associated yet, half-accepted connection)
 762	if(!pClient) pClient = Clients.GetClientByID(pConn->getClientID());
 763	// not found? ignore
 764	if(!pClient) return;
 765	// remove connection
 766	pClient->RemoveConn(pConn);
 767
 768	// create post-mortem if needed
 769	C4PacketPostMortem PostMortem;
 770	if(pConn->CreatePostMortem(&PostMortem))
 771	{
 772		LogSilentF("Network: Sending %d packets for recovery (%d-%d)", PostMortem.getPacketCount(), pConn->getOutPacketCounter() - PostMortem.getPacketCount(), pConn->getOutPacketCounter() - 1);
 773		// This might fail because of this disconnect
 774		// (If it's the only host connection. We're toast then anyway.)
 775		if(!Clients.SendMsgToClient(pConn->getClientID(), MkC4NetIOPacket(PID_PostMortem, PostMortem)))
 776			assert(isHost() || !Clients.GetHost()->isConnected());
 777	}
 778
 779	// call handler
 780	OnDisconnect(pClient, pConn);
 781
 782}
 783
 784void C4Network2::HandlePacket(char cStatus, const C4PacketBase *pPacket, C4Network2IOConnection *pConn)
 785{
 786	// find associated client
 787	C4Network2Client *pClient = Clients.GetClient(pConn);
 788	if(!pClient) pClient = Clients.GetClientByID(pConn->getClientID());
 789
 790	// local? ignore
 791	if(pClient && pClient->isLocal()) { pConn->Close(); return; }
 792
 793	#define GETPKT(type, name) \
 794		assert(pPacket); const type &name = \
 795			/*dynamic_cast*/ static_cast<const type &>(*pPacket);
 796
 797	switch(cStatus)
 798	{
 799	case PID_Conn: // connection request
 800	{
 801		if(!pConn->isOpen()) break;
 802		GETPKT(C4PacketConn, rPkt);
 803		HandleConn(rPkt, pConn, pClient);
 804	}
 805	break;
 806
 807	case PID_ConnRe: // connection request reply
 808	{
 809		GETPKT(C4PacketConnRe, rPkt);
 810		HandleConnRe(rPkt, pConn, pClient);
 811	}
 812	break;
 813
 814  case PID_JoinData:
 815  {
 816    // host->client only
 817    if(isHost() || !pClient || !pClient->isHost()) break;
 818		if(!pConn->isOpen()) break;
 819    // handle
 820    GETPKT(C4PacketJoinData, rPkt)
 821    HandleJoinData(rPkt);
 822  }
 823  break;
 824
 825	case PID_Status: // status change
 826	{
 827		// by host only
 828		if(isHost() || !pClient || !pClient->isHost()) break;
 829		if(!pConn->isOpen()) break;
 830		// must be initialized
 831		if(Status.getState() == GS_Init) break;
 832		// handle
 833		GETPKT(C4Network2Status, rPkt);
 834		HandleStatus(rPkt);
 835	}
 836	break;
 837
 838	case PID_StatusAck: // status change acknowledgement
 839	{
 840		// host->client / client->host only
 841		if(!pClient) break;
 842		if(!isHost() && !pClient->isHost()) break;
 843		// must be initialized
 844		if(Status.getState() == GS_Init) break;
 845		// handle
 846		GETPKT(C4Network2Status, rPkt);
 847		HandleStatusAck(rPkt, pClient);
 848	}
 849	break;
 850
 851  case PID_ClientActReq: // client activation request
 852  {
 853    // client->host only
 854    if(!isHost() || !pClient || pClient->isHost()) break;
 855		// must be initialized
 856		if(Status.getState() == GS_Init) break;
 857    // handle
 858    GETPKT(C4PacketActivateReq, rPkt)
 859    HandleActivateReq(rPkt.getTick(), pClient);
 860  }
 861  break;
 862
 863	}
 864
 865	#undef GETPKT
 866}
 867
 868void C4Network2::HandleLobbyPacket(char cStatus, const C4PacketBase *pBasePkt, C4Network2IOConnection *pConn)
 869	{
 870	// find associated client
 871	C4Network2Client *pClient = Clients.GetClient(pConn);
 872	if(!pClient) pClient = Clients.GetClientByID(pConn->getClientID());
 873	// forward directly to lobby
 874	if (pLobby) pLobby->HandlePacket(cStatus, pBasePkt, pClient);
 875	}
 876
 877void C4Network2::OnGameSynchronized()
 878{
 879	// savegame needed?
 880	if(fDynamicNeeded)
 881	{
 882		// create dynamic
 883		bool fSuccess = CreateDynamic(false);
 884		// check for clients that still need join-data
 885		C4Network2Client *pClient = NULL;
 886		while(pClient = Clients.GetNextClient(pClient))
 887			if(!pClient->hasJoinData())
 888				if(fSuccess)
 889					// now we can provide join data: send it
 890					SendJoinData(pClient);
 891				else
 892					// join data could not be created: emergency kick
 893					Game.Clients.CtrlRemove(pClient->getClient(), LoadResStr("IDS_ERR_ERRORWHILECREATINGJOINDAT"));
 894	}
 895}
 896
 897void C4Network2::DrawStatus(C4TargetFacet &cgo)
 898{
 899	if(!isEnabled()) return;
 900
 901	C4Network2Client *pLocal = Clients.GetLocal();
 902
 903  StdStrBuf Stat;
 904
 905	// local client status
 906	Stat.AppendFormat("Local: %s %s %s (ID %d)",
 907		pLocal->isObserver() ? "Observing" : pLocal->isActivated() ? "Active" : "Inactive", pLocal->isHost() ? "host" : "client",
 908		pLocal->getName(), pLocal->getID());
 909
 910	// game status
 911	Stat.AppendFormat( "|Game Status: %s (tick %d)%s%s",
 912		Status.getStateName(), Status.getTargetCtrlTick(),
 913		fStatusReached ? " reached" : "", fStatusAck ? " ack" : "");
 914
 915	// available protocols
 916	C4NetIO *pMsgIO = NetIO.MsgIO(), *pDataIO = NetIO.DataIO();
 917	if(pMsgIO && pDataIO)
 918	{
 919    C4Network2IOProtocol eMsgProt = NetIO.getNetIOProt(pMsgIO),
 920                         eDataProt = NetIO.getNetIOProt(pDataIO);
 921		int32_t iMsgPort = 0, iDataPort = 0;
 922		switch(eMsgProt)
 923		{
 924		case P_TCP: iMsgPort = Config.Network.PortTCP; break;
 925		case P_UDP: iMsgPort = Config.Network.PortUDP; break;
 926		}
 927		switch(eDataProt)
 928		{
 929		case P_TCP: iDataPort = Config.Network.PortTCP; break;
 930		case P_UDP: iDataPort = Config.Network.PortUDP; break;
 931		}
 932		Stat.AppendFormat( "|Protocols: %s: %s (%d i%d o%d bc%d)",
 933      pMsgIO != pDataIO ? "Msg" : "Msg/Data",
 934			NetIO.getNetIOName(pMsgIO), iMsgPort,
 935      NetIO.getProtIRate(eMsgProt), NetIO.getProtORate(eMsgProt), NetIO.getProtBCRate(eMsgProt));
 936    if(pMsgIO != pDataIO)
 937    Stat.AppendFormat( ", Data: %s (%d i%d o%d bc%d)",
 938      NetIO.getNetIOName(pDataIO), iDataPort,
 939      NetIO.getProtIRate(eDataProt), NetIO.getProtORate(eDataProt), NetIO.getProtBCRate(eDataProt));
 940	}
 941	else
 942		Stat.Append("|Protocols: none");
 943
 944	// some control statistics
 945	Stat.AppendFormat( "|Control: %s, Tick %d, Behind %d, Rate %d, PreSend %d, ACT: %d",
 946    Status.getCtrlMode() == CNM_Decentral ? "Decentral" : Status.getCtrlMode() == CNM_Central ? "Central" : "Async",
 947		::Control.ControlTick, pControl->GetBehind(::Control.ControlTick),
 948    ::Control.ControlRate, pControl->getControlPreSend(), pControl->getAvgControlSendTime());
 949
 950	// Streaming statistics
 951	if(fStreaming)
 952		Stat.AppendFormat( "|Streaming: %d waiting, %d in, %d out, %d sent",
 953			pStreamedRecord ? pStreamedRecord->GetStreamingBuf().getSize() : 0,
 954			pStreamedRecord ? pStreamedRecord->GetStreamingPos() : 0,
 955			getPendingStreamData(),
 956			iCurrentStreamPosition);
 957
 958	// clients
 959	Stat.Append("|Clients:");
 960	for(C4Network2Client *pClient = Clients.GetNextClient(NULL); pClient; pClient = Clients.GetNextClient(pClient))
 961	{
 962		// ignore local
 963		if(pClient->isLocal()) continue;
 964		// client status
 965		const C4ClientCore &Core = pClient->getCore();
 966		const char *szClientStatus = "";
 967		switch(pClient->getStatus())
 968		{
 969		case NCS_Joining: szClientStatus = " (joining)"; break;
 970		case NCS_Chasing: szClientStatus = " (chasing)"; break;
 971		case NCS_NotReady: szClientStatus = " (!rdy)"; break;
 972		case NCS_Remove: szClientStatus = " (removed)"; break;
 973		}
 974		Stat.AppendFormat( "|- %s %s %s (ID %d) (wait %d ms, behind %d)%s%s",
 975			Core.isObserver() ? "Observing" : Core.isActivated() ? "Active" : "Inactive", Core.isHost() ? "host" : "client",
 976			Core.getName(), Core.getID(),
 977      pControl->ClientPerfStat(pClient->getID()),
 978      ::Control.ControlTick - pControl->ClientNextControl(pClient->getID()),
 979      szClientStatus,
 980			pClient->isActivated() && !pControl->ClientReady(pClient->getID(), ::Control.ControlTick) ? " (!ctrl)" : "");
 981		// connections
 982		if(pClient->isConnected())
 983		{
 984			Stat.AppendFormat( "|   Connections: %s: %s (%s:%d p%d l%d)",
 985				pClient->getMsgConn() == pClient->getDataConn() ? "Msg/Data" : "Msg",
 986				NetIO.getNetIOName(pClient->getMsgConn()->getNetClass()),
 987				inet_ntoa(pClient->getMsgConn()->getPeerAddr().sin_addr),
 988				htons(pClient->getMsgConn()->getPeerAddr().sin_port),
 989				pClient->getMsgConn()->getPingTime(),
 990        pClient->getMsgConn()->getPacketLoss());
 991			if(pClient->getMsgConn() != pClient->getDataConn())
 992			Stat.AppendFormat( ", Data: %s (%s:%d p%d l%d)",
 993				NetIO.getNetIOName(pClient->getDataConn()->getNetClass()),
 994				inet_ntoa(pClient->getDataConn()->getPeerAddr().sin_addr),
 995				htons(pClient->getDataConn()->getPeerAddr().sin_port),
 996				pClient->getDataConn()->getPingTime(),
 997        pClient->getDataConn()->getPacketLoss());
 998		}
 999		else
1000			Stat.Append("|   Not connected");
1001	}
1002	if(!Clients.GetNextClient(NULL))
1003		Stat.Append("| - none -");
1004
1005	// draw
1006	Application.DDraw->TextOut(Stat.getData(), ::GraphicsResource.FontRegular, 1.0, cgo.Surface,cgo.X + 20,cgo.Y + 50);
1007}
1008
1009bool C4Network2::InitNetIO(bool fNoClientID, bool fHost)
1010{
1011	// clear
1012	NetIO.Clear();
1013	Config.Network.CheckPortsForCollisions();
1014	// discovery: disable for client
1015	int16_t iPortDiscovery = fHost ? Config.Network.PortDiscovery : -1;
1016	int16_t iPortRefServer = fHost ? Config.Network.PortRefServer : -1;
1017	// init subclass
1018	if(!NetIO.Init(Config.Network.PortTCP, Config.Network.PortUDP, iPortDiscovery, iPortRefServer, fHost))
1019		return false;
1020	// set core (unset ID if sepecified, has to be set later)
1021	C4ClientCore Core = Game.Clients.getLocalCore();
1022	if(fNoClientID) Core.SetID(C4ClientIDUnknown);
1023	NetIO.SetLocalCCore(Core);
1024	// safe addresses of local client
1025	Clients.GetLocal()->AddLocalAddrs(
1026		NetIO.hasTCP() ? Config.Network.PortTCP : -1,
1027		NetIO.hasUDP() ? Config.Network.PortUDP : -1);
1028	// ok
1029	return true;
1030}
1031
1032void C4Network2::HandleConn(const C4PacketConn &Pkt, C4Network2IOConnection *pConn, C4Network2Client *pClient)
1033{
1034	// security
1035	if(!pConn) return;
1036
1037	// Handles a connect request (packet PID_Conn).
1038	// Check if this peer should be allowed to connect, make space for the new connection.
1039
1040	// connection is closed?
1041	if(pConn->isClosed())
1042		return;
1043
1044	// set up core
1045	const C4ClientCore &CCore = Pkt.getCCore();
1046	C4ClientCore NewCCore = CCore;
1047
1048	// accept connection?
1049	StdStrBuf reply;
1050	bool fOK = false;
1051
1052	// search client
1053	if(!pClient && Pkt.getCCore().getID() != C4ClientIDUnknown)
1054		pClient = Clients.GetClient(Pkt.getCCore());
1055
1056	// check engine version
1057	bool fWrongPassword = false;
1058	if(Pkt.getVer() != C4XVERBUILD)
1059	{
1060		reply.Format("wrong engine (%d, I have %d)", Pkt.getVer(), C4XVERBUILD);
1061		fOK = false;
1062	}
1063	else
1064	{
1065		if(pClient)
1066			if(CheckConn(NewCCore, pConn, pClient, reply.getData()))
1067			{
1068        // accept
1069				if(!reply) reply = "connection accepted";
1070				fOK = true;
1071			}
1072		// client: host connection?
1073		if(!fOK && !isHost() && Status.getState() == GS_Init && !Clients.GetHost())
1074			if(HostConnect(NewCCore, pConn, reply.getData()))
1075			{
1076        // accept
1077				if(!reply) reply = "host connection accepted";
1078				fOK = true;
1079			}
1080		// host: client join? (NewCCore will be changed by Join()!)
1081		if(!fOK && isHost() && !pClient)
1082		{
1083			// check password
1084			if(!sPassword.isNull() && !SEqual(Pkt.getPassword(), sPassword.getData()))
1085			{
1086				reply = "wrong password";
1087				fWrongPassword = true;
1088			}
1089			// registered join only
1090			else if (Game.RegJoinOnly && !SLen(NewCCore.getCUID()))
1091			{
1092				reply = "registered join only";
1093			}
1094			// accept join
1095			else if(Join(NewCCore, pConn, reply.getData()))
1096			{
1097        // save core
1098		    pConn->SetCCore(NewCCore);
1099        // accept
1100				if(!reply) reply = "join accepted";
1101				fOK = true;
1102			}
1103		}
1104	}
1105
1106	// denied? set default reason
1107	if(!fOK && !reply) reply = "connection denied";
1108
1109	// OK and already half accepted? Skip (double-checked: ok).
1110	if(fOK && pConn->isHalfAccepted())
1111		return;
1112
1113  // send answer
1114	C4PacketConnRe pcr(fOK, fWrongPassword, reply.getData());
1115  if(!pConn->Send(MkC4NetIOPacket(PID_ConnRe, pcr)))
1116		return;
1117
1118	// accepted?
1119	if(fOK)
1120	{
1121		// set status
1122		if(!pConn->isClosed())
1123			pConn->SetHalfAccepted();
1124	}
1125	// denied? close
1126	else
1127	{
1128		// log & close
1129		LogSilentF("Network: connection by %s (%s:%d) blocked: %s", CCore.getName(), inet_ntoa(pConn->getPeerAddr().sin_addr), htons(pConn->getPeerAddr().sin_port), reply.getData());
1130		pConn->Close();
1131	}
1132}
1133
1134bool C4Network2::CheckConn(const C4ClientCore &CCore, C4Network2IOConnection *pConn, C4Network2Client *pClient, const char *szReply)
1135{
1136	if(!pConn || !pClient) return false;
1137	// already connected? (shouldn't happen really)
1138	if(pClient->hasConn(pConn))
1139		{	szReply = "already connected"; return true; }
1140	// check core
1141	if(CCore.getDiffLevel(pClient->getCore()) > C4ClientCoreDL_IDMatch)
1142		{	szReply = "wrong client core"; return false; }
1143	// check address
1144	if(pClient->isConnected() && pClient->getMsgConn()->getPeerAddr().sin_addr.s_addr != pConn->getPeerAddr().sin_addr.s_addr)
1145		{ szReply = "wrong address"; return false; }
1146	// accept
1147	return true;
1148}
1149
1150bool C4Network2::HostConnect(const C4ClientCore &CCore, C4Network2IOConnection *pConn, const char *szReply)
1151{
1152	if(!pConn) return false;
1153	if(!CCore.isHost()) { szReply = "not host"; return false; }
1154	// create client class for host
1155	// (core is unofficial, see InitClient() -  will be overwritten later in HandleJoinData)
1156	C4Client *pClient = Game.Clients.Add(CCore);
1157	if(!pClient) return false;
1158	// accept
1159	return true;
1160}
1161
1162bool C4Network2::Join(C4ClientCore &CCore, C4Network2IOConnection *pConn, const char *szReply)
1163{
1164	if(!pConn) return false;
1165	// security
1166	if(!isHost()) { szReply = "not host"; return false; }
1167	if(!fAllowJoin && !fAllowObserve) { szReply = "join denied"; return false; }
1168	if(CCore.getID() != C4ClientIDUnknown) { szReply = "join with set id not allowed"; return false; }
1169	// find free client id
1170	CCore.SetID(iNextClientID++);
1171  // observer?
1172  if(!fAllowJoin) CCore.SetObserver(true);
1173  // deactivate - client will have to ask for activation.
1174  CCore.SetActivated(false);
1175	// Name already in use? Find unused one
1176	if(Clients.GetClient(CCore.getName()))
1177	{
1178		char szNameTmpl[256+1], szNewName[256+1];
1179		SCopy(CCore.getName(), szNameTmpl, 254); SAppend("%d", szNameTmpl, 256);
1180		int32_t i = 1;
1181		do
1182			sprintf(szNewName, szNameTmpl, ++i);
1183		while(Clients.GetClient(szNewName));
1184		CCore.SetName(szNewName);
1185	}
1186	// join client
1187	::Control.DoInput(CID_ClientJoin, new C4ControlClientJoin(CCore), CDT_Direct);
1188	// get client, set status
1189	C4Network2Client *pClient = Clients.GetClient(CCore);
1190	if(pClient) pClient->SetStatus(NCS_Joining);
1191	// ok, client joined.
1192	return true;
1193	// Note that the connection isn't fully accepted at this point and won't be
1194	// associated with the client. The new-created client is waiting for connect.
1195	// Somewhat ironically, the connection may still timeout (resulting in an instant
1196	// removal and maybe some funny message sequences).
1197	// The final client initialization will be done at OnClientConnect.
1198}
1199
1200void C4Network2::HandleConnRe(const C4PacketConnRe &Pkt, C4Network2IOConnection *pConn, C4Network2Client *pClient)
1201{
1202	// Handle the connection request reply. After this handling, the connection should
1203	// be either fully associated with a client (fully accepted) or closed.
1204	// Note that auto-accepted connection have to processed here once, too, as the
1205	// client must get associated with the connection. After doing so, the connection
1206	// auto-accept flag will be reset to mark the connection fully accepted.
1207
1208	// security
1209	if(!pConn) return;
1210	if(!pClient) { pConn->Close(); return; }
1211
1212	// negative reply?
1213	if(!Pkt.isOK())
1214	{
1215		// wrong password?
1216		fWrongPassword = Pkt.isPasswordWrong();
1217		// show message
1218		LogSilentF("Network: connection to %s (%s:%d) refused: %s", pClient->getName(), inet_ntoa(pConn->getPeerAddr().sin_addr), htons(pConn->getPeerAddr().sin_port), Pkt.getMsg());
1219		// close connection
1220		pConn->Close();
1221		return;
1222	}
1223
1224	// connection is closed?
1225	if(!pConn->isOpen())
1226		return;
1227
1228	// already accepted? ignore
1229	if(pConn->isAccepted() && !pConn->isAutoAccepted()) return;
1230
1231	// first connection?
1232	bool fFirstConnection = !pClient->isConnected();
1233
1234	// accept connection
1235	pConn->SetAccepted(); pConn->ResetAutoAccepted();
1236
1237	// add connection
1238	pConn->SetCCore(pClient->getCore());
1239	if(pConn->getNetClass() == NetIO.MsgIO()) pClient->SetMsgConn(pConn);
1240	if(pConn->getNetClass() == NetIO.DataIO()) pClient->SetDataConn(pConn);
1241
1242	// add peer connect address to client address list
1243	if(pConn->getConnectAddr().sin_addr.s_addr)
1244	{
1245		C4Network2Address Addr(pConn->getConnectAddr(), pConn->getProtocol());
1246		pClient->AddAddr(Addr, Status.getState() != GS_Init);
1247	}
1248
1249	// handle
1250	OnConnect(pClient, pConn, Pkt.getMsg(), fFirstConnection);
1251}
1252
1253void C4Network2::HandleStatus(const C4Network2Status &nStatus)
1254{
1255	// set
1256	Status = nStatus;
1257	// log
1258	LogSilentF("Network: going into status %s (tick %d)", Status.getStateName(), nStatus.getTargetCtrlTick());
1259	// reset flags
1260	fStatusReached = fStatusAck = false;
1261	// check: reached?
1262	CheckStatusReached();
1263}
1264
1265void C4Network2::HandleStatusAck(const C4Network2Status &nStatus, C4Network2Client *pClient)
1266{
1267	// security
1268	if(!pClient->hasJoinData() || pClient->isRemoved()) return;
1269	// status doesn't match?
1270	if(nStatus.getState() != Status.getState() || nStatus.getTargetCtrlTick() < Status.getTargetCtrlTick())
1271		return;
1272	// host: wait until all clients are ready
1273	if(isHost())
1274	{
1275		// check: target tick change?
1276		if(!fStatusAck && nStatus.getTargetCtrlTick() > Status.getTargetCtrlTick())
1277			// take the new status
1278			ChangeGameStatus(nStatus.getState(), nStatus.getTargetCtrlTick());
1279		// already acknowledged? Send another ack
1280		if(fStatusAck)
1281			pClient->SendMsg(MkC4NetIOPacket(PID_StatusAck, nStatus));
1282		// mark as ready (will clear chase-flag)
1283		pClient->SetStatus(NCS_Ready);
1284		// check: everyone ready?
1285		if(!fStatusAck && fStatusReached)
1286			CheckStatusAck();
1287	}
1288	else
1289	{
1290		// target tick doesn't match? ignore
1291		if(nStatus.getTargetCtrlTick() != Status.getTargetCtrlTick())
1292			return;
1293		// reached?
1294		// can be ignored safely otherwise - when the status is reached, we will send
1295		// status ack on which the host should generate another status ack (see above)
1296		if(fStatusReached)
1297		{
1298			// client: set flags, call handler
1299			fStatusAck = true; fChasing = false;
1300			OnStatusAck();
1301		}
1302
1303	}
1304}
1305
1306void C4Network2::HandleActivateReq(int32_t iTick, C4Network2Client *pByClient)
1307{
1308  if(!isHost()) return;
1309  // not allowed or already activated? ignore
1310  if(pByClient->isObserver() || pByClient->isActivated()) return;
1311	// not joined completely yet? ignore
1312	if(!pByClient->isWaitedFor()) return;
1313  // check behind limit
1314  if(isRunning())
1315  {
1316    // make a guess how much the client lags.
1317    int32_t iLagFrames = BoundBy(pByClient->getMsgConn()->getPingTime() * Game.FPS / 500, 0, 100);
1318    if(iTick < Game.FrameCounter - iLagFrames - C4NetMaxBehind4Activation)
1319      return;
1320  }
1321  // activate him
1322	::Control.DoInput(CID_ClientUpdate,
1323		new C4ControlClientUpdate(pByClient->getID(), CUT_Activate, true),
1324		CDT_Sync);
1325}
1326
1327void C4Network2::HandleJoinData(const C4PacketJoinData &rPkt)
1328{
1329	// init only
1330	if(Status.getState() != GS_Init)
1331		{ LogSilentF("Network: unexpected join data received!"); return; }
1332	// get client ID
1333	if(rPkt.getClientID() == C4ClientIDUnknown)
1334		{ LogSilentF("Network: host didn't set client ID!"); Clear(); return; }
1335	// set local ID
1336	ResList.SetLocalID(rPkt.getClientID());
1337	Game.Parameters.Clients.SetLocalID(rPkt.getClientID());
1338	// read and validate status
1339	HandleStatus(rPkt.getStatus());
1340	if(Status.getState() != GS_Lobby && Status.getState() != GS_Pause && Status.getState() != GS_Go)
1341		{ LogSilentF("Network: join data has bad game status: %s", Status.getStateName()); Clear(); return; }
1342	// copy parameters
1343	Game.Parameters = rPkt.Parameters;
1344	// set local client
1345	C4Client *pLocalClient = Game.Clients.getClientByID(rPkt.getClientID());
1346	if(!pLocalClient)
1347		{ LogSilentF("Network: Could not find local client in join data!"); Clear(); return; }
1348	// save back dynamic data
1349	ResDynamic = rPkt.getDynamicCore();
1350	iDynamicTick = rPkt.getStartCtrlTick();
1351	// initialize control
1352	::Control.ControlRate = rPkt.Parameters.ControlRate;
1353	pControl->Init(rPkt.getClientID(), false, rPkt.getStartCtrlTick(), pLocalClient->isActivated(), this);
1354	pControl->CopyClientList(Game.Parameters.Clients);
1355	// set local core
1356	NetIO.SetLocalCCore(pLocalClient->getCore());
1357	// add the resources to the network ressource list
1358	Game.Parameters.GameRes.InitNetwork(&ResList);
1359	// load dynamic
1360	if(!ResList.AddByCore(ResDynamic))
1361		{ LogFatal("Network: can not not retrieve dynamic!"); Clear(); return; }
1362	// load player ressources
1363	Game.Parameters.PlayerInfos.LoadResources();
1364	// send additional addresses
1365	Clients.SendAddresses(NULL);
1366}
1367
1368void C4Network2::OnConnect(C4Network2Client *pClient, C4Network2IOConnection *pConn, const char *szMsg, bool fFirstConnection)
1369{
1370	// log
1371	LogSilentF("Network: %s %s connected (%s:%d/%s) (%s)", pClient->isHost() ? "host" : "client",
1372		pClient->getName(), inet_ntoa(pConn->getPeerAddr().sin_addr), htons(pConn->getPeerAddr().sin_port),
1373		NetIO.getNetIOName(pConn->getNetClass()), szMsg ? szMsg : "");
1374
1375	// first connection for this peer? call special handler
1376	if(fFirstConnection) OnClientConnect(pClient, pConn);
1377}
1378
1379void C4Network2::OnConnectFail(C4Network2IOConnection *pConn)
1380{
1381	LogSilentF("Network: %s connection to %s:%d failed!", NetIO.getNetIOName(pConn->getNetClass()),
1382		inet_ntoa(pConn->getPeerAddr().sin_addr), htons(pConn->getPeerAddr().sin_port));
1383
1384	// maybe client connection failure
1385	// (happens if the connection is not fully accepted and the client disconnects.
1386	//  See C4Network2::Join)
1387	C4Network2Client *pClient = Clients.GetClientByID(pConn->getClientID());
1388	if(pClient && !pClient->isConnected())
1389		OnClientDisconnect(pClient);
1390}
1391
1392void C4Network2::OnDisconnect(C4Network2Client *pClient, C4Network2IOConnection *pConn)
1393{
1394	LogSilentF("Network: %s connection to %s (%s:%d) lost!", NetIO.getNetIOName(pConn->getNetClass()),
1395		pClient->getName(), inet_ntoa(pConn->getPeerAddr().sin_addr), htons(pConn->getPeerAddr().sin_port));
1396
1397	// connection lost?
1398	if(!pClient->isConnected())
1399		OnClientDisconnect(pClient);
1400}
1401
1402void C4Network2::OnClientConnect(C4Network2Client *pClient, C4Network2IOConnection *pConn)
1403{
1404	// host: new client?
1405	if(isHost())
1406	{
1407		// dynamic available?
1408		if(!pClient->hasJoinData())
1409			SendJoinData(pClient);
1410
1411		// notice lobby (doesn't do anything atm?)
1412		C4GameLobby::MainDlg *pDlg = GetLobby();
1413		if (isLobbyActive()) pDlg->OnClientConnect(pClient->getClient(), pConn);
1414
1415	}
1416
1417	// discover resources
1418	ResList.OnClientConnect(pConn);
1419
1420}
1421
1422void C4Network2::OnClientDisconnect(C4Network2Client *pClient)
1423{
1424	// league: Notify regular client disconnect within the game
1425	if (pLeagueClient && (isHost() || pClient->isHost())) LeagueNotifyDisconnect(pClient->getID(), C4LDR_ConnectionFailed);
1426	// host? Remove this client from the game.
1427	if(isHost())
1428	{
1429		// log
1430		LogSilentF(LoadResStr("IDS_NET_CLIENTDISCONNECTED"), pClient->getName()); // silent, because a duplicate message with disconnect reason will follow
1431		// remove the client
1432		Game.Clients.CtrlRemove(pClient->getClient(), LoadResStr("IDS_MSG_DISCONNECTED"));
1433		// check status ack (disconnected client might be the last that was waited for)
1434		CheckStatusAck();
1435		// unreached pause/go? retry setting the state with current control tick
1436		// (client might be the only one claiming to have the given control)
1437		if(!fStatusReached)
1438			if(Status.getState() == GS_Go || Status.getState() == GS_Pause)
1439				ChangeGameStatus(Status.getState(), ::Control.ControlTick);
1440	}
1441	// host disconnected? Clear up
1442	if(!isHost() && pClient->isHost())
1443	{
1444		StdStrBuf sMsg; sMsg.Format(LoadResStr("IDS_NET_HOSTDISCONNECTED"), pClient->getName());
1445		Log(sMsg.getData());
1446		// host connection lost: clear up everything
1447		Game.RoundResults.EvaluateNetwork(C4RoundResults::NR_NetError, sMsg.getData());
1448		Clear();
1449	}
1450}
1451
1452void C4Network2::SendJoinData(C4Network2Client *pClient)
1453{
1454	if(pClient->hasJoinData()) return;
1455	// host only, scenario must be available
1456	assert(isHost());
1457	// dynamic available?
1458	if(ResDynamic.isNull() || iDynamicTick < ::Control.ControlTick)
1459	{
1460		fDynamicNeeded = true;
1461		// add synchronization control (will callback, see C4Game::Synchronize)
1462		::Control.DoInput(CID_Synchronize, new C4ControlSynchronize(false, true), CDT_Sync);
1463		return;
1464	}
1465	// save his client ID
1466	C4PacketJoinData JoinData;
1467	JoinData.SetClientID(pClient->getID());
1468	// save status into packet
1469	JoinData.SetGameStatus(Status);
1470	// parameters
1471	JoinData.Parameters = Game.Parameters;
1472	// core join data
1473	JoinData.SetStartCtrlTick(iDynamicTick);
1474	JoinData.SetDynamicCore(ResDynamic);
1475	// send
1476	pClient->SendMsg(MkC4NetIOPacket(PID_JoinData, JoinData));
1477	// send addresses
1478	Clients.SendAddresses(pClient->getMsgConn());
1479	// flag client (he will have to accept the network status sent next)
1480	pClient->SetStatus(NCS_Chasing);
1481	if(!iLastChaseTargetUpdate) iLastChaseTargetUpdate = time(NULL);
1482}
1483
1484C4Network2Res::Ref C4Network2::RetrieveRes(const C4Network2ResCore &Core, int32_t iTimeoutLen, const char *szResName, bool fWaitForCore)
1485{
1486	C4GUI::ProgressDialog *pDlg = NULL;
1487	bool fLog = false;
1488	int32_t iProcess = -1; uint32_t iTimeout = timeGetTime() + iTimeoutLen;
1489	// wait for ressource
1490	while(isEnabled())
1491	{
1492		// find ressource
1493		C4Network2Res::Ref pRes = ResList.getRefRes(Core.getID());
1494		// res not found?
1495		if(!pRes)
1496			if(Core.isNull())
1497			{
1498				// should wait for core?
1499				if(!fWaitForCore) return NULL;
1500			}
1501			else
1502			{
1503				// start loading
1504				pRes = ResList.AddByCore(Core);
1505			}
1506		// res found and loaded completely
1507		else if(!pRes->isLoading())
1508		{
1509			// log
1510			if(fLog) LogF(LoadResStr("IDS_NET_RECEIVED"), szResName, pRes->getCore().getFileName());
1511			// return
1512			if (pDlg) delete pDlg;
1513			return pRes;
1514		}
1515
1516		// check: progress?
1517		if(pRes && pRes->getPresentPercent() != iProcess)
1518		{
1519			iProcess = pRes->getPresentPercent();
1520			iTimeout = timeGetTime() + iTimeoutLen;
1521		}
1522		else
1523		{
1524			// if not: check timeout
1525			if(timeGetTime() > iTimeout)
1526			{
1527				LogFatal(FormatString(LoadResStr("IDS_NET_ERR_RESTIMEOUT"), szResName).getData());
1528				if (pDlg) delete pDlg;
1529				return NULL;
1530			}
1531		}
1532
1533		// log
1534		if(!fLog)
1535		{
1536			LogF(LoadResStr("IDS_NET_WAITFORRES"), szResName);
1537			fLog = true;
1538		}
1539		// show progress dialog
1540		if(!pDlg && !Console.Active && ::pGUI)
1541		{
1542			// create
1543			pDlg = new C4GUI::ProgressDialog(FormatString(LoadResStr("IDS_NET_WAITFORRES"), szResName).getData(),
1544                                          LoadResStr("IDS_NET_CAPTION"), 100, 0, C4GUI::Ico_NetWait);
1545			// show dialog
1546			if(!pDlg->Show(::pGUI, true)) { delete pDlg; return NULL; }
1547		}
1548
1549		// wait
1550		if(pDlg)
1551		{
1552			// set progress bar
1553			pDlg->SetProgress(iProcess);
1554			// execute (will do message handling)
1555			if(!pDlg->Execute())
1556				{ if (pDlg) delete pDlg; return NULL; }
1557			// aborted?
1558			if(!::pGUI) return NULL;
1559			if(pDlg->IsAborted()) break;
1560		}
1561		else
1562		{
1563			if(!Application.ScheduleProcs(iTimeout - timeGetTime()))
1564				{ return NULL; }
1565		}
1566
1567	}
1568	// aborted
1569	if(!::pGUI) return NULL;
1570	delete pDlg;
1571	return NULL;
1572}
1573
1574
1575bool C4Network2::CreateDynamic(bool fInit)
1576{
1577	if(!isHost()) return false;
1578	// remove all existing dynamic data
1579	RemoveDynamic();
1580	// log
1581	Log(LoadResStr("IDS_NET_SAVING"));
1582	// compose file name
1583	char szDynamicBase[_MAX_PATH+1], szDynamicFilename[_MAX_PATH+1];
1584	sprintf(szDynamicBase, Config.AtNetworkPath("Dyn%s"), GetFilename(Game.ScenarioFilename), _MAX_PATH);
1585	if(!ResList.FindTempResFileName(szDynamicBase, szDynamicFilename))
1586		LogF(LoadResStr("IDS_NET_SAVE_ERR_CREATEDYNFILE"));
1587	// save dynamic data
1588	C4GameSaveNetwork SaveGame(fInit);
1589	if (!SaveGame.Save(szDynamicFilename) || !SaveGame.Close())
1590		{ Log(LoadResStr("IDS_NET_SAVE_ERR_SAVEDYNFILE")); return false; }
1591	// add ressource
1592	C4Network2Res::Ref pRes = ResList.AddByFile(szDynamicFilename, true, NRT_Dynamic);
1593	if(!pRes) { Log(LoadResStr("IDS_NET_SAVE_ERR_ADDDYNDATARES")); return false; }
1594	// save
1595	ResDynamic = pRes->getCore();
1596	iDynamicTick = ::Control.getNextControlTick();
1597	fDynamicNeeded = false;
1598	// ok
1599	return true;
1600}
1601
1602void C4Network2::RemoveDynamic()
1603{
1604	C4Network2Res::Ref pRes = ResList.getRefRes(ResDynamic.getID());
1605	if(pRes) pRes->Remove();
1606	ResDynamic.Clear();
1607	iDynamicTick = -1;
1608}
1609
1610bool C4Network2::isFrozen() const
1611{
1612  // "frozen" means all clients are garantueed to be in the same tick.
1613  // This is only the case if the game is not started yet (lobby) or the
1614  // tick has been ensured (pause) and acknowledged by all joined clients.
1615  // Note unjoined clients must be ignored here - they can't be faster than
1616  // the host, anyway.
1617	if(Status.getState() == GS_Lobby) return true;
1618	if(Status.getState() == GS_Pause && fStatusAck) return true;
1619	return false;
1620}
1621
1622bool C4Network2::ChangeGameStatus(C4NetGameState enState, int32_t iTargetCtrlTick, int32_t iCtrlMode)
1623{
1624	// change game status, announce. Can only be done by host.
1625	if(!isHost()) return false;
1626	// set status
1627	Status.Set(enState, iTargetCtrlTick);
1628	// update reference
1629	InvalidateReference();
1630  // control mode change?
1631  if(iCtrlMode >= 0) Status.SetCtrlMode(iCtrlMode);
1632	// log
1633  LogSilentF("Network: going into status %s (tick %d)", Status.getStateName(), iTargetCtrlTick);
1634	// set flags
1635	Clients.ResetReady();
1636	fStatusReached = fStatusAck = false;
1637	// send new status to all clients
1638	Clients.BroadcastMsgToClients(MkC4NetIOPacket(PID_Status, Status));
1639	// check reach/ack
1640	CheckStatusReached();
1641	// ok
1642	return true;
1643}
1644
1645void C4Network2::CheckStatusReached(bool fFromFinalInit)
1646{
1647	// already reached?
1648	if(fStatusReached) return;
1649	if(Status.getState() == GS_Lobby)
1650		fStatusReached = fLobbyRunning;
1651	// game go / pause: control must be initialized and target tick reached
1652	else if(Status.getState() == GS_Go || Status.getState() == GS_Pause)
1653	{
1654		if(Game.IsRunning || fFromFinalInit)
1655    {
1656			// Make sure we have reached the tick and the control queue is empty (except for chasing)
1657			if(::Control.CtrlTickReached(Status.getTargetCtrlTick()) &&
1658				 (fChasing || !pControl->CtrlReady(::Control.ControlTick)))
1659				fStatusReached = true;
1660			else
1661      {
1662				// run ctrl so the tick can be reached
1663				pControl->SetRunning(true, Status.getTargetCtrlTick());
1664				Game.HaltCount = 0;
1665        Console.UpdateHaltCtrls(!! Game.HaltCount);
1666      }
1667    }
1668	}
1669	if(!fStatusReached) return;
1670	// call handler
1671	OnStatusReached();
1672	// host?
1673	if(isHost())
1674		// all clients ready?
1675		CheckStatusAck();
1676	else
1677	{
1678		Status.SetTargetTick(::Control.ControlTick);
1679		// send response to host
1680		Clients.SendMsgToHost(MkC4NetIOPacket(PID_StatusAck, Status));
1681		// do delayed activation request
1682		if(fDelayedActivateReq)
1683			{
1684			fDelayedActivateReq = false;
1685			RequestActivate();
1686			}
1687	}

Large files files are truncated, but you can click here to view the full file