/src/network/C4Network2.cpp
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