1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 // the ingame-lobby
17 
18 #include "C4Include.h"
19 #include "C4ForbidLibraryCompilation.h"
20 #include "gui/C4GameLobby.h"
21 
22 #include "c4group/C4Components.h"
23 #include "control/C4GameControl.h"
24 #include "game/C4Application.h"
25 #include "graphics/C4GraphicsResource.h"
26 #include "gui/C4ChatDlg.h"
27 #include "gui/C4GameOptions.h"
28 #include "gui/C4MessageInput.h"
29 #include "gui/C4PlayerInfoListBox.h"
30 #include "network/C4Network2.h"
31 #include "network/C4Network2Dialogs.h"
32 
33 namespace C4GameLobby
34 {
35 
36 	bool UserAbort = false;
37 
38 // ----------- C4PacketCountdown ---------------------------------------------
39 
CompileFunc(StdCompiler * pComp)40 	void C4PacketCountdown::CompileFunc(StdCompiler *pComp)
41 	{
42 		pComp->Value(mkNamingAdapt(iCountdown, "Countdown", 0));
43 	}
44 
GetCountdownMsg(bool fInitialMsg) const45 	StdStrBuf C4PacketCountdown::GetCountdownMsg(bool fInitialMsg) const
46 	{
47 		const char *szCountdownMsg;
48 		if (iCountdown < AlmostStartCountdownTime && !fInitialMsg) szCountdownMsg = "%d..."; else szCountdownMsg = LoadResStr("IDS_PRC_COUNTDOWN");
49 		return FormatString(szCountdownMsg, (int)iCountdown);
50 	}
51 
52 
53 // ----------- C4PacketSetScenarioParameter ---------------------------------------------
54 
CompileFunc(StdCompiler * pComp)55 	void C4PacketSetScenarioParameter::CompileFunc(StdCompiler *pComp)
56 	{
57 		pComp->Value(mkNamingAdapt(mkParAdapt(ID, StdCompiler::RCT_Idtf), "ID", StdCopyStrBuf()));
58 		pComp->Value(mkNamingAdapt(Value, "Value", 0));
59 	}
60 
61 
62 // ----------- ScenDescs ---------------------------------------------
63 
ScenDesc(const C4Rect & rcBounds,bool fActive)64 	ScenDesc::ScenDesc(const C4Rect &rcBounds, bool fActive) : C4GUI::Window(), fDescFinished(false)
65 	{
66 		// build components
67 		SetBounds(rcBounds);
68 		C4GUI::ComponentAligner caMain(GetClientRect(), 0,0, true);
69 		AddElement(pDescBox = new C4GUI::TextWindow(caMain.GetAll(), 0, 0, 0, 100, 4096, "", true));
70 		pDescBox->SetDecoration(false, false, nullptr, true);
71 		// initial update to set current data
72 		if (fActive) Activate();
73 	}
74 
Update()75 	void ScenDesc::Update()
76 	{
77 		// scenario present?
78 		C4Network2Res *pRes = Game.Parameters.Scenario.getNetRes();
79 		if (!pRes) return; // something's wrong
80 		CStdFont &rTitleFont = ::GraphicsResource.CaptionFont;
81 		CStdFont &rTextFont = ::GraphicsResource.TextFont;
82 		pDescBox->ClearText(false);
83 		if (pRes->isComplete())
84 		{
85 			C4Group ScenarioFile;
86 			if (!ScenarioFile.Open(pRes->getFile()))
87 			{
88 				pDescBox->AddTextLine("scenario file load error", &rTextFont, C4GUI_MessageFontClr, false, true);
89 			}
90 			else
91 			{
92 				// load desc
93 				C4ComponentHost DefDesc;
94 				if (C4Language::LoadComponentHost(&DefDesc, ScenarioFile, C4CFN_ScenarioDesc, Config.General.LanguageEx))
95 					pDescBox->AddTextLine(DefDesc.GetData(), &rTextFont, C4GUI_MessageFontClr, false, true, &rTitleFont);
96 				else
97 					pDescBox->AddTextLine(Game.ScenarioTitle.getData(), &rTitleFont, C4GUI_CaptionFontClr, false, true);
98 			}
99 			// okay, done loading. No more updates.
100 			fDescFinished = true;
101 			Deactivate();
102 		}
103 		else
104 		{
105 			pDescBox->AddTextLine(FormatString(LoadResStr("IDS_MSG_SCENARIODESC_LOADING"), (int) pRes->getPresentPercent()).getData(),
106 			                      &rTextFont, C4GUI_MessageFontClr, false, true);
107 		}
108 		pDescBox->UpdateHeight();
109 	}
110 
Activate()111 	void ScenDesc::Activate()
112 	{
113 		// final desc set? no update then
114 		if (fDescFinished) return;
115 		// register timer if necessary
116 		Application.Add(this);
117 		// force an update
118 		Update();
119 	}
120 
Deactivate()121 	void ScenDesc::Deactivate()
122 	{
123 		// release timer if set
124 		Application.Remove(this);
125 	}
126 
127 // ----------- MainDlg -----------------------------------------------------------------------------
128 
MainDlg(bool fHost)129 	MainDlg::MainDlg(bool fHost)
130 			: C4GUI::FullscreenDialog(!Game.ScenarioTitle ?
131 			                          (const char *) LoadResStr("IDS_DLG_LOBBY"):
132 			                          FormatString("%s - %s", Game.ScenarioTitle.getData(), LoadResStr("IDS_DLG_LOBBY")).getData(),
133 			                          Game.ScenarioTitle.getData()),
134 			pPlayerList(nullptr), pResList(nullptr), pChatBox(nullptr), pRightTabLbl(nullptr), pRightTab(nullptr),
135 			pEdt(nullptr), btnRun(nullptr), btnPlayers(nullptr), btnResources(nullptr), btnTeams(nullptr), btnChat(nullptr)
136 	{
137 		// key bindings
138 		pKeyHistoryUp  = new C4KeyBinding(C4KeyCodeEx(K_UP  ), "LobbyChatHistoryUp"  , KEYSCOPE_Gui, new C4GUI::DlgKeyCBEx<MainDlg, bool>(*this, true , &MainDlg::KeyHistoryUpDown), C4CustomKey::PRIO_CtrlOverride);
139 		pKeyHistoryDown= new C4KeyBinding(C4KeyCodeEx(K_DOWN), "LobbyChatHistoryDown", KEYSCOPE_Gui, new C4GUI::DlgKeyCBEx<MainDlg, bool>(*this, false, &MainDlg::KeyHistoryUpDown), C4CustomKey::PRIO_CtrlOverride);
140 		// timer
141 		Application.Add(this);
142 		// indents / sizes
143 		int32_t iDefBtnHeight = 32;
144 		int32_t iIndentX1, iIndentX2, iIndentX3;
145 		int32_t iIndentY1, iIndentY2, iIndentY3, iIndentY4;
146 		int32_t iClientListWdt;
147 		if (GetClientRect().Wdt > 500)
148 		{
149 			// normal dlg
150 			iIndentX1 = 10;   // lower button area
151 			iIndentX2 = 20;   // center area (chat)
152 			iIndentX3 = 5;    // client/player list
153 			iClientListWdt = GetClientRect().Wdt / 3;
154 		}
155 		else
156 		{
157 			// small dlg
158 			iIndentX1 = 2;   // lower button area
159 			iIndentX2 = 2;   // center area (chat)
160 			iIndentX3 = 1;   // client/player list
161 			iClientListWdt = GetClientRect().Wdt / 2;
162 		}
163 		if (GetClientRect().Hgt > 320)
164 		{
165 			// normal dlg
166 			iIndentY1 = 16;    // lower button area
167 			iIndentY2 = 20;    // status bar offset
168 			iIndentY3 = 8;     // center area (chat)
169 			iIndentY4 = 8;     // client/player list
170 		}
171 		else
172 		{
173 			// small dlg
174 			iIndentY1 = 2;     // lower button area
175 			iIndentY2 = 2;     // status bar offset
176 			iIndentY3 = 1;     // center area (chat)
177 			iIndentY4 = 1;     // client/player list
178 		}
179 		// set subtitle ToolTip
180 		if (pSubTitle)
181 			pSubTitle->SetToolTip(LoadResStr("IDS_DLG_SCENARIOTITLE"));
182 		C4GUI::Label *pLbl;
183 		// main screen components
184 		C4GUI::ComponentAligner caMain(GetClientRect(), 0,0,true);
185 		caMain.GetFromBottom(iIndentY2);
186 		// lower button-area
187 		C4GUI::ComponentAligner caBottom(caMain.GetFromBottom(iDefBtnHeight+iIndentY1*2), iIndentX1,iIndentY1);
188 		// add buttons
189 		C4GUI::CallbackButton<MainDlg> *btnExit;
190 		btnExit = new C4GUI::CallbackButton<MainDlg>(LoadResStr("IDS_DLG_EXIT"), caBottom.GetFromLeft(100), &MainDlg::OnExitBtn);
191 		if (fHost)
192 		{
193 			btnRun = new C4GUI::CallbackButton<MainDlg>(LoadResStr("IDS_DLG_GAMEGO"), caBottom.GetFromRight(100), &MainDlg::OnRunBtn);
194 			checkReady = nullptr;
195 		}
196 		else
197 		{
198 			checkReady = new C4GUI::CheckBox(caBottom.GetFromRight(90), LoadResStr("IDS_DLG_READY"), false);
199 			checkReady->SetOnChecked(new C4GUI::CallbackHandler<MainDlg>(this, &MainDlg::OnReadyCheck));
200 			caBottom.GetFromRight(90);
201 		}
202 		pGameOptionButtons = new C4GameOptionButtons(caBottom.GetCentered(caBottom.GetInnerWidth(), std::min<int32_t>(C4GUI_IconExHgt, caBottom.GetHeight())), true, fHost, true);
203 
204 		// players / resources sidebar
205 		C4GUI::ComponentAligner caRight(caMain.GetFromRight(iClientListWdt), iIndentX3,iIndentY4);
206 		pRightTabLbl = new C4GUI::WoodenLabel("", caRight.GetFromTop(C4GUI::WoodenLabel::GetDefaultHeight(&(::GraphicsResource.TextFont))), C4GUI_CaptionFontClr, &::GraphicsResource.TextFont, ALeft);
207 		caRight.ExpandTop(iIndentY4*2 + 1); // undo margin, so client list is located directly under label
208 		pRightTab = new C4GUI::Tabular(caRight.GetAll(), C4GUI::Tabular::tbNone);
209 		C4GUI::Tabular::Sheet *pPlayerSheet = pRightTab->AddSheet(LoadResStr("IDS_DLG_PLAYERS"));
210 		C4GUI::Tabular::Sheet *pResSheet = pRightTab->AddSheet(LoadResStr("IDS_DLG_RESOURCES"));
211 		C4GUI::Tabular::Sheet *pOptionsSheet = pRightTab->AddSheet(LoadResStr("IDS_DLG_OPTIONS"));
212 		C4GUI::Tabular::Sheet *pScenarioSheet = pRightTab->AddSheet(LoadResStr("IDS_DLG_SCENARIO"));
213 		pPlayerList = new C4PlayerInfoListBox(pPlayerSheet->GetContainedClientRect(), C4PlayerInfoListBox::PILBM_LobbyClientSort);
214 		pPlayerSheet->AddElement(pPlayerList);
215 		pResList = new C4Network2ResDlg(pResSheet->GetContainedClientRect(), false);
216 		pResSheet->AddElement(pResList);
217 		pOptionsList = new C4GameOptionsList(pResSheet->GetContainedClientRect(), false, C4GameOptionsList::GOLS_Lobby);
218 		pOptionsSheet->AddElement(pOptionsList);
219 		pScenarioInfo = new ScenDesc(pResSheet->GetContainedClientRect(), false);
220 		pScenarioSheet->AddElement(pScenarioInfo);
221 		pRightTabLbl->SetContextHandler(new C4GUI::CBContextHandler<C4GameLobby::MainDlg>(this, &MainDlg::OnRightTabContext));
222 		pRightTabLbl->SetClickFocusControl(pPlayerList);
223 
224 		bool fHasTeams = Game.Teams.IsMultiTeams();
225 		bool fHasChat = C4ChatDlg::IsChatActive();
226 		int32_t iBtnNum = 4+fHasTeams+fHasChat;
227 		if (fHasTeams)
228 		{
229 			btnTeams = new C4GUI::CallbackButton<MainDlg, C4GUI::IconButton>(C4GUI::Ico_Team, pRightTabLbl->GetToprightCornerRect(16, 16, 4, 4, --iBtnNum), LoadResStr("IDS_DLG_PLAYERSBYTEAM"), &MainDlg::OnTabTeams);
230 		}
231 		btnPlayers = new C4GUI::CallbackButton<MainDlg, C4GUI::IconButton>(C4GUI::Ico_Player, pRightTabLbl->GetToprightCornerRect(16,16,4,4,--iBtnNum), LoadResStr("IDS_DLG_PLAYERS"), &MainDlg::OnTabPlayers);
232 		btnResources = new C4GUI::CallbackButton<MainDlg, C4GUI::IconButton>(C4GUI::Ico_Resource, pRightTabLbl->GetToprightCornerRect(16,16,4,4,--iBtnNum), LoadResStr("IDS_DLG_RESOURCES"), &MainDlg::OnTabRes);
233 		btnOptions = new C4GUI::CallbackButton<MainDlg, C4GUI::IconButton>(C4GUI::Ico_Options, pRightTabLbl->GetToprightCornerRect(16,16,4,4,--iBtnNum), LoadResStr("IDS_DLG_OPTIONS"), &MainDlg::OnTabOptions);
234 		btnScenario = new C4GUI::CallbackButton<MainDlg, C4GUI::IconButton>(C4GUI::Ico_Gfx, pRightTabLbl->GetToprightCornerRect(16,16,4,4,--iBtnNum), LoadResStr("IDS_DLG_SCENARIO"), &MainDlg::OnTabScenario);
235 		if (fHasChat)
236 			btnChat = new C4GUI::CallbackButton<MainDlg, C4GUI::IconButton>(C4GUI::Ico_Ex_Chat, pRightTabLbl->GetToprightCornerRect(16,16,4,4,--iBtnNum), LoadResStr("IDS_CTL_CHAT"), &MainDlg::OnBtnChat);
237 
238 		// update labels and tooltips for player list
239 		UpdateRightTab();
240 
241 		// chat area
242 		C4GUI::ComponentAligner caCenter(caMain.GetAll(), iIndentX2, iIndentY3);
243 		// chat input box
244 		C4GUI::ComponentAligner caChat(caCenter.GetFromBottom(C4GUI::Edit::GetDefaultEditHeight()), 0,0);
245 		pLbl = new C4GUI::WoodenLabel(LoadResStr("IDS_CTL_CHAT"), caChat.GetFromLeft(40), C4GUI_CaptionFontClr, &::GraphicsResource.TextFont);
246 		pEdt = new C4GUI::CallbackEdit<MainDlg>(caChat.GetAll(), this, &MainDlg::OnChatInput);
247 		pEdt->SetToolTip(LoadResStr("IDS_DLGTIP_CHAT")); pLbl->SetToolTip(LoadResStr("IDS_DLGTIP_CHAT"));
248 		pLbl->SetClickFocusControl(pEdt);
249 		// log box
250 		pChatBox = new C4GUI::TextWindow(caCenter.GetAll());
251 		// add components in tab-order
252 		AddElement(pChatBox);
253 		AddElement(pLbl); AddElement(pEdt); // chat
254 
255 		AddElement(pRightTabLbl);
256 		if (btnTeams) AddElement(btnTeams);
257 		AddElement(btnPlayers);
258 		AddElement(btnResources);
259 		AddElement(btnOptions);
260 		AddElement(btnScenario);
261 		if (btnChat) AddElement(btnChat);
262 
263 		AddElement(pRightTab);
264 		AddElement(btnExit); btnExit->SetToolTip(LoadResStr("IDS_DLGTIP_EXIT"));
265 		AddElement(pGameOptionButtons);
266 		if (fHost)
267 		{
268 			AddElement(btnRun);
269 			btnRun->SetToolTip(LoadResStr("IDS_DLGTIP_GAMEGO"));
270 		}
271 		else
272 		{
273 			AddElement(checkReady);
274 			checkReady->SetToolTip(LoadResStr("IDS_DLGTIP_READY"));
275 		}
276 		// set initial focus
277 		SetFocus(pEdt, false);
278 
279 		// stuff
280 		eCountdownState = CDS_None;
281 		iBackBufferIndex = -1;
282 
283 		// initial player list update
284 		UpdatePlayerList();
285 	}
286 
~MainDlg()287 	MainDlg::~MainDlg()
288 	{
289 		Application.Remove(this);
290 		delete pKeyHistoryUp;
291 		delete pKeyHistoryDown;
292 	}
293 
OnExitBtn(C4GUI::Control * btn)294 	void MainDlg::OnExitBtn(C4GUI::Control *btn)
295 	{
296 		// abort dlg
297 		Close(false);
298 	}
299 
OnReadyCheck(C4GUI::Element * pCheckBox)300 	void MainDlg::OnReadyCheck(C4GUI::Element *pCheckBox)
301 	{
302 		bool rIsOn = static_cast<C4GUI::CheckBox *>(pCheckBox)->GetChecked();
303 		::Control.DoInput(CID_ClientUpdate, new C4ControlClientUpdate(::Game.Clients.getLocalID(), CUT_SetReady, rIsOn), CDT_Direct);
304 	}
305 
SetCountdownState(CountdownState eToState,int32_t iTimer)306 	void MainDlg::SetCountdownState(CountdownState eToState, int32_t iTimer)
307 	{
308 		// no change?
309 		if (eToState == eCountdownState) return;
310 		// changing away from countdown?
311 		if (eCountdownState == CDS_Countdown)
312 		{
313 			StopSoundEffect("Structures::Elevator::Moving", nullptr);
314 			if (eToState != CDS_Start) StartSoundEffect("Liquids::Pshshsh");
315 		}
316 		// change to game start?
317 		if (eToState == CDS_Start)
318 		{
319 			// announce it!
320 			StartSoundEffect("Fire::Blast3");
321 		}
322 		else if (eToState == CDS_Countdown)
323 		{
324 			StartSoundEffect("Fire::Fuse");
325 		}
326 		if (eToState == CDS_Countdown || eToState == CDS_LongCountdown)
327 		{
328 			// game start notify
329 			Application.NotifyUserIfInactive();
330 			if (!eCountdownState)
331 			{
332 				// host update start button to be abort button
333 				if (btnRun) btnRun->SetText(LoadResStr("IDS_DLG_CANCEL"));
334 			}
335 		}
336 		// countdown abort?
337 		if (!eToState)
338 		{
339 			// host update start button to be start button again
340 			if (btnRun) btnRun->SetText(LoadResStr("IDS_DLG_GAMEGO"));
341 			// countdown abort message
342 			OnLog(LoadResStr("IDS_PRC_STARTABORTED"), C4GUI_LogFontClr2);
343 		}
344 		// set new state
345 		eCountdownState = eToState;
346 		// update stuff (makes team sel and fair crew btn available)
347 		pGameOptionButtons->SetCountdown(IsCountdown());
348 		UpdatePlayerList();
349 	}
350 
OnCountdownPacket(const C4PacketCountdown & Pkt)351 	void MainDlg::OnCountdownPacket(const C4PacketCountdown &Pkt)
352 	{
353 		// determine new countdown state
354 		int32_t iTimer = 0;
355 		CountdownState eNewState;
356 		if (Pkt.IsAbort())
357 			eNewState = CDS_None;
358 		else
359 		{
360 			iTimer = Pkt.GetCountdown();
361 			if (!iTimer)
362 				eNewState = CDS_Start; // game is about to be started (boom)
363 			else if (iTimer <= AlmostStartCountdownTime)
364 				eNewState = CDS_Countdown; // eToState
365 			else
366 				eNewState = CDS_LongCountdown;
367 		}
368 		bool fWasCountdown = !!eCountdownState;
369 		SetCountdownState(eNewState, iTimer);
370 		// display countdown (except last, which ends the lobby anyway)
371 		if (iTimer)
372 		{
373 			// first countdown message
374 			OnLog(Pkt.GetCountdownMsg(!fWasCountdown).getData(), C4GUI_LogFontClr2);
375 			StartSoundEffect("UI::Tick");
376 		}
377 	}
378 
IsCountdown()379 	bool MainDlg::IsCountdown()
380 	{
381 		// flag as countdown if countdown running or game is about to start
382 		//   so team choice, etc. will not become available in the last split-second
383 		return eCountdownState >= CDS_Countdown;
384 	}
385 
OnClosed(bool fOK)386 	void MainDlg::OnClosed(bool fOK)
387 	{
388 		// lobby aborted by user: remember not to display error log
389 		if (!fOK)
390 			C4GameLobby::UserAbort = true;
391 		// finish countdown if running
392 		// (may not be finished if status change packet from host is faster than the countdown-initiate)
393 		if (eCountdownState) SetCountdownState(fOK ? CDS_Start : CDS_None, 0);
394 	}
395 
OnRunBtn(C4GUI::Control * btn)396 	void MainDlg::OnRunBtn(C4GUI::Control *btn)
397 	{
398 		// only for host
399 		if (!::Network.isHost()) return;
400 		// already started? then abort
401 		if (eCountdownState) { ::Network.AbortLobbyCountdown(); return; }
402 		// otherwise start, utilizing correct countdown time
403 		Start(Config.Lobby.CountdownTime);
404 	}
405 
Start(int32_t iCountdownTime)406 	void MainDlg::Start(int32_t iCountdownTime)
407 	{
408 		// network savegame resumes: Warn if not all players have been associated
409 		if (Game.C4S.Head.SaveGame)
410 		{
411 			if (Game.PlayerInfos.FindUnassociatedRestoreInfo(Game.RestorePlayerInfos))
412 			{
413 				StdStrBuf sMsg; sMsg.Ref(LoadResStr("IDS_MSG_NOTALLSAVEGAMEPLAYERSHAVE"));
414 				if (!GetScreen()->ShowMessageModal(sMsg.getData(), LoadResStr("IDS_MSG_FREESAVEGAMEPLRS"), C4GUI::MessageDialog::btnYesNo, C4GUI::Ico_SavegamePlayer, &Config.Startup.HideMsgPlrNoTakeOver))
415 					return;
416 			}
417 
418 			// warning about desync bug #1965
419 			int i=0; C4ClientPlayerInfos *pkInfo;
420 			while ((pkInfo = Game.PlayerInfos.GetIndexedInfo(i++))) {
421 				C4PlayerInfo *pPlrInfo; int32_t iInfo=0;
422 				while ((pPlrInfo = pkInfo->GetPlayerInfo(iInfo++)))
423 					if (!pPlrInfo->GetAssociatedSavegamePlayerID())
424 					{
425 						bool ignore = GetScreen()->ShowMessageModal(
426 							LoadResStr("IDS_DLG_NETRESUME"),
427 							LoadResStr("IDS_MSG_FREESAVEGAMEPLRS"),
428 							C4GUI::MessageDialog::btnYesNo,
429 							C4GUI::Ico_Error
430 						);
431 						if (ignore)
432 							break;
433 						else
434 							return;
435 					}
436 			}
437 		}
438 		// validate countdown time
439 		iCountdownTime = ValidatedCountdownTime(iCountdownTime);
440 		// either direct start...
441 		if (!iCountdownTime)
442 			::Network.Start();
443 		else
444 			// ...or countdown
445 			::Network.StartLobbyCountdown(iCountdownTime);
446 	}
447 
OnChatInput(C4GUI::Edit * edt,bool fPasting,bool fPastingMore)448 	C4GUI::Edit::InputResult MainDlg::OnChatInput(C4GUI::Edit *edt, bool fPasting, bool fPastingMore)
449 	{
450 		// get edit text
451 		C4GUI::Edit *pEdt = reinterpret_cast<C4GUI::Edit *>(edt);
452 		const char *szInputText = pEdt->GetText();
453 		// no input?
454 		if (!szInputText || !*szInputText)
455 		{
456 			// do some error sound then
457 			C4GUI::GUISound("UI::Error");
458 		}
459 		else
460 		{
461 			// store input in backbuffer before processing commands
462 			// because those might kill the edit field
463 			::MessageInput.StoreBackBuffer(szInputText);
464 			::MessageInput.ProcessInput(szInputText);
465 		}
466 		// clear edit field after text has been processed
467 		pEdt->SelectAll(); pEdt->DeleteSelection();
468 		// reset backbuffer-index of chat history
469 		iBackBufferIndex = -1;
470 		// OK, on we go. Leave edit intact
471 		return C4GUI::Edit::IR_None;
472 	}
473 
OnClientJoin(C4Client * pNewClient)474 	void MainDlg::OnClientJoin(C4Client *pNewClient)
475 	{
476 		// update list
477 		UpdatePlayerList();
478 	}
479 
OnClientConnect(C4Client * pClient,C4Network2IOConnection * pConn)480 	void MainDlg::OnClientConnect(C4Client *pClient, C4Network2IOConnection *pConn)
481 	{
482 	}
483 
OnClientPart(C4Client * pPartClient)484 	void MainDlg::OnClientPart(C4Client *pPartClient)
485 	{
486 		// update list
487 		UpdatePlayerList();
488 	}
489 
HandlePacket(char cStatus,const C4PacketBase * pPacket,C4Network2Client * pClient)490 	void MainDlg::HandlePacket(char cStatus, const C4PacketBase *pPacket, C4Network2Client *pClient)
491 	{
492 		// note that player info update packets are not handled by this function,
493 		// but by player info list and then forwarded to MainDlg::OnPlayerUpdate
494 		// this is necessary because there might be changes (e.g. duplicate colors)
495 		// done by player info list
496 		// besides, this releases the lobby from doing any host/client-specializations
497 #define GETPKT(type, name) \
498     assert(pPacket); const type &name = \
499       static_cast<const type &>(*pPacket);
500 		switch (cStatus)
501 		{
502 		case PID_LobbyCountdown: // initiate or abort countdown
503 		{
504 			GETPKT(C4PacketCountdown, Pkt);
505 			// do countdown
506 			OnCountdownPacket(Pkt);
507 		}
508 		break;
509 		case PID_SetScenarioParameter: // set a scenario parameter value
510 			{
511 				GETPKT(C4PacketSetScenarioParameter, Pkt);
512 				::Game.Parameters.ScenarioParameters.SetValue(Pkt.GetID(), Pkt.GetValue(), false);
513 				// reflect updated value immediately on clients
514 				if (pRightTab->GetActiveSheetIndex() == SheetIdx_Options) if (pOptionsList) pOptionsList->Update();
515 			}
516 		};
517 #undef GETPKT
518 	}
519 
OnMessage(C4Client * pOfClient,const char * szMessage)520 	bool MainDlg::OnMessage(C4Client *pOfClient, const char *szMessage)
521 	{
522 		// output message should be prefixed with client already
523 		const char *szMsgBuf = szMessage;
524 		// 2do: log with player colors?
525 		if (pChatBox && !pOfClient->IsIgnored())
526 		{
527 			pChatBox->AddTextLine(szMsgBuf, &::GraphicsResource.TextFont, ::Network.Players.GetClientChatColor(pOfClient ? pOfClient->getID() : Game.Clients.getLocalID(), true) | C4GUI_MessageFontAlpha, true, true);
528 			pChatBox->ScrollToBottom();
529 		}
530 		// log it
531 		LogSilent(szMsgBuf);
532 		// done, success
533 		return true;
534 	}
535 
OnClientSound(C4Client * pOfClient)536 	void MainDlg::OnClientSound(C4Client *pOfClient)
537 	{
538 		// show that someone played a sound
539 		if (pOfClient && pPlayerList)
540 		{
541 			pPlayerList->SetClientSoundIcon(pOfClient->getID());
542 		}
543 	}
544 
OnLog(const char * szLogMsg,DWORD dwClr)545 	void MainDlg::OnLog(const char *szLogMsg, DWORD dwClr)
546 	{
547 		if (pChatBox)
548 		{
549 			pChatBox->AddTextLine(szLogMsg, &::GraphicsResource.TextFont, dwClr, true, true);
550 			pChatBox->ScrollToBottom();
551 		}
552 	}
553 
OnError(const char * szErrMsg)554 	void MainDlg::OnError(const char *szErrMsg)
555 	{
556 		if (pChatBox)
557 		{
558 			StartSoundEffect("UI::Error");
559 			pChatBox->AddTextLine(szErrMsg, &::GraphicsResource.TextFont, C4GUI_ErrorFontClr, true, true);
560 			pChatBox->ScrollToBottom();
561 		}
562 	}
563 
OnSec1Timer()564 	void MainDlg::OnSec1Timer()
565 	{
566 		UpdatePlayerList();
567 	}
568 
UpdatePlayerList()569 	void MainDlg::UpdatePlayerList()
570 	{
571 		// this updates ping label texts and teams
572 		if (pPlayerList) pPlayerList->Update();
573 	}
574 
OnRightTabContext(C4GUI::Element * pLabel,int32_t iX,int32_t iY)575 	C4GUI::ContextMenu *MainDlg::OnRightTabContext(C4GUI::Element *pLabel, int32_t iX, int32_t iY)
576 	{
577 		// create context menu
578 		C4GUI::ContextMenu *pMenu = new C4GUI::ContextMenu();
579 		// players/resources
580 		C4GUI::Tabular::Sheet *pPlayerSheet = pRightTab->GetSheet(0);
581 		C4GUI::Tabular::Sheet *pResSheet = pRightTab->GetSheet(1);
582 		C4GUI::Tabular::Sheet *pOptionsSheet = pRightTab->GetSheet(2);
583 		pMenu->AddItem(pPlayerSheet->GetTitle(), pPlayerSheet->GetToolTip(), C4GUI::Ico_Player,
584 		               new C4GUI::CBMenuHandler<MainDlg>(this, &MainDlg::OnCtxTabPlayers));
585 		if (Game.Teams.IsMultiTeams())
586 		{
587 			StdCopyStrBuf strShowTeamsDesc(LoadResStr("IDS_MSG_SHOWTEAMS_DESC"));
588 			pMenu->AddItem(LoadResStr("IDS_MSG_SHOWTEAMS"), strShowTeamsDesc.getData(), C4GUI::Ico_Team,
589 			               new C4GUI::CBMenuHandler<MainDlg>(this, &MainDlg::OnCtxTabTeams));
590 		}
591 		pMenu->AddItem(pResSheet->GetTitle(), pResSheet->GetToolTip(), C4GUI::Ico_Resource,
592 		               new C4GUI::CBMenuHandler<MainDlg>(this, &MainDlg::OnCtxTabRes));
593 		pMenu->AddItem(pOptionsSheet->GetTitle(), pOptionsSheet->GetToolTip(), C4GUI::Ico_Options,
594 		               new C4GUI::CBMenuHandler<MainDlg>(this, &MainDlg::OnCtxTabOptions));
595 		// open it
596 		return pMenu;
597 	}
598 
OnClientAddPlayer(const char * szFilename,int32_t idClient)599 	void MainDlg::OnClientAddPlayer(const char *szFilename, int32_t idClient)
600 	{
601 		// check client number
602 		if (idClient != Game.Clients.getLocalID())
603 		{
604 			LobbyError(FormatString(LoadResStr("IDS_ERR_JOINPLR_NOLOCALCLIENT"), szFilename, idClient).getData());
605 			return;
606 		}
607 		// player join - check filename
608 		if (!ItemExists(szFilename))
609 		{
610 			LobbyError(FormatString(LoadResStr("IDS_ERR_JOINPLR_NOFILE"), szFilename).getData());
611 			return;
612 		}
613 		// join!
614 		::Network.Players.JoinLocalPlayer(Config.AtRelativePath(szFilename));
615 	}
616 
OnTabPlayers(C4GUI::Control * btn)617 	void MainDlg::OnTabPlayers(C4GUI::Control *btn)
618 	{
619 		if (pPlayerList) pPlayerList->SetMode(C4PlayerInfoListBox::PILBM_LobbyClientSort);
620 		if (pRightTab)
621 		{
622 			pRightTab->SelectSheet(SheetIdx_PlayerList, true);
623 			UpdateRightTab();
624 		}
625 	}
626 
OnTabTeams(C4GUI::Control * btn)627 	void MainDlg::OnTabTeams(C4GUI::Control *btn)
628 	{
629 		if (pPlayerList) pPlayerList->SetMode(C4PlayerInfoListBox::PILBM_LobbyTeamSort);
630 		if (pRightTab)
631 		{
632 			pRightTab->SelectSheet(SheetIdx_PlayerList, true);
633 			UpdateRightTab();
634 		}
635 	}
636 
OnTabRes(C4GUI::Control * btn)637 	void MainDlg::OnTabRes(C4GUI::Control *btn)
638 	{
639 		if (pRightTab)
640 		{
641 			pRightTab->SelectSheet(SheetIdx_Res, true);
642 			UpdateRightTab();
643 		}
644 	}
645 
OnTabOptions(C4GUI::Control * btn)646 	void MainDlg::OnTabOptions(C4GUI::Control *btn)
647 	{
648 		if (pRightTab)
649 		{
650 			pRightTab->SelectSheet(SheetIdx_Options, true);
651 			UpdateRightTab();
652 		}
653 	}
654 
OnTabScenario(C4GUI::Control * btn)655 	void MainDlg::OnTabScenario(C4GUI::Control *btn)
656 	{
657 		if (pRightTab)
658 		{
659 			pRightTab->SelectSheet(SheetIdx_Scenario, true);
660 			UpdateRightTab();
661 		}
662 	}
663 
UpdateRightTab()664 	void MainDlg::UpdateRightTab()
665 	{
666 		if (!pRightTabLbl || !pRightTab || !pRightTab->GetActiveSheet() || !pPlayerList) return;
667 		// copy active sheet data to label
668 		pRightTabLbl->SetText(pRightTab->GetActiveSheet()->GetTitle());
669 		pRightTabLbl->SetToolTip(pRightTab->GetActiveSheet()->GetToolTip());
670 		// update
671 		if (pRightTab->GetActiveSheetIndex() == SheetIdx_PlayerList) UpdatePlayerList();
672 		if (pRightTab->GetActiveSheetIndex() == SheetIdx_Res) pResList->Activate(); else pResList->Deactivate();
673 		if (pRightTab->GetActiveSheetIndex() == SheetIdx_Options) pOptionsList->Activate(); else pOptionsList->Deactivate();
674 		if (pRightTab->GetActiveSheetIndex() == SheetIdx_Scenario) pScenarioInfo->Activate(); else pScenarioInfo->Deactivate();
675 		// update selection buttons
676 		if (btnPlayers) btnPlayers->SetHighlight(pRightTab->GetActiveSheetIndex() == SheetIdx_PlayerList && pPlayerList->GetMode() == C4PlayerInfoListBox::PILBM_LobbyClientSort);
677 		if (btnResources) btnResources->SetHighlight(pRightTab->GetActiveSheetIndex() == SheetIdx_Res);
678 		if (btnTeams) btnTeams->SetHighlight(pRightTab->GetActiveSheetIndex() == SheetIdx_PlayerList && pPlayerList->GetMode() == C4PlayerInfoListBox::PILBM_LobbyTeamSort);
679 		if (btnOptions) btnOptions->SetHighlight(pRightTab->GetActiveSheetIndex() == SheetIdx_Options);
680 		if (btnScenario) btnScenario->SetHighlight(pRightTab->GetActiveSheetIndex() == SheetIdx_Scenario);
681 	}
682 
OnBtnChat(C4GUI::Control * btn)683 	void MainDlg::OnBtnChat(C4GUI::Control *btn)
684 	{
685 		// open chat dialog
686 		C4ChatDlg::ShowChat();
687 	}
688 
KeyHistoryUpDown(bool fUp)689 	bool MainDlg::KeyHistoryUpDown(bool fUp)
690 	{
691 		// chat input only
692 		if (!IsFocused(pEdt)) return false;
693 		pEdt->SelectAll(); pEdt->DeleteSelection();
694 		const char *szPrevInput = ::MessageInput.GetBackBuffer(fUp ? (++iBackBufferIndex) : (--iBackBufferIndex));
695 		if (!szPrevInput || !*szPrevInput)
696 			iBackBufferIndex = -1;
697 		else
698 		{
699 			pEdt->InsertText(szPrevInput, true);
700 			pEdt->SelectAll();
701 		}
702 		return true;
703 	}
704 
UpdatePassword()705 	void MainDlg::UpdatePassword()
706 	{
707 		// if the password setting has changed, make sure the buttons reflect this change
708 		pGameOptionButtons->UpdatePasswordBtn();
709 	}
710 
ValidatedCountdownTime(int32_t iTimeout)711 	int32_t MainDlg::ValidatedCountdownTime(int32_t iTimeout)
712 	{
713 		// no negative timeouts
714 		if (iTimeout < 0) iTimeout = 5;
715 		// in leage mode, there must be at least five seconds timeout
716 		if (Game.Parameters.isLeague() && iTimeout < 5) iTimeout = 5;
717 		return iTimeout;
718 	}
719 
ClearLog()720 	void MainDlg::ClearLog()
721 	{
722 		pChatBox->ClearText(true);
723 	}
724 
LobbyError(const char * szErrorMsg)725 	void LobbyError(const char *szErrorMsg)
726 	{
727 		// get lobby
728 		MainDlg *pLobby = ::Network.GetLobby();
729 		if (pLobby) pLobby->OnError(szErrorMsg);
730 		else Log(szErrorMsg);
731 	}
732 
733 
734 	/* Countdown */
735 
Countdown(int32_t iStartTimer)736 	Countdown::Countdown(int32_t iStartTimer) : iStartTimer(iStartTimer)
737 	{
738 		// only on network hosts
739 		assert(::Network.isHost());
740 		// ctor: Init; sends initial countdown packet
741 		C4PacketCountdown pck(iStartTimer);
742 		::Network.Clients.BroadcastMsgToClients(MkC4NetIOPacket(PID_LobbyCountdown, pck));
743 		// also process on host
744 		MainDlg *pLobby = ::Network.GetLobby();
745 		if (pLobby)
746 		{
747 			pLobby->OnCountdownPacket(pck);
748 		}
749 		else
750 		{
751 			// no lobby: Message to log for dedicated/console hosts
752 			Log(pck.GetCountdownMsg().getData());
753 		}
754 
755 		// init timer callback
756 		Application.Add(this);
757 	}
758 
~Countdown()759 	Countdown::~Countdown()
760 	{
761 		// release timer
762 		Application.Remove(this);
763 	}
764 
OnSec1Timer()765 	void Countdown::OnSec1Timer()
766 	{
767 		// count down
768 		iStartTimer = std::max<int32_t>(iStartTimer - 1, 0);
769 		// only send "important" start timer numbers to all clients
770 		if (iStartTimer <= AlmostStartCountdownTime || // last seconds
771 		    (iStartTimer <= 600 && !(iStartTimer % 10)) || // last minute: 10s interval
772 		    !(iStartTimer % 60)) // otherwise, minute interval
773 		{
774 			C4PacketCountdown pck(iStartTimer);
775 			::Network.Clients.BroadcastMsgToClients(MkC4NetIOPacket(PID_LobbyCountdown, pck));
776 			// also process on host
777 			MainDlg *pLobby = ::Network.GetLobby();
778 			if (pLobby)
779 				pLobby->OnCountdownPacket(pck);
780 			else if (iStartTimer)
781 			{
782 				// no lobby: Message to log for dedicated/console hosts
783 				Log(pck.GetCountdownMsg().getData());
784 			}
785 		}
786 		// countdown done
787 		if (!iStartTimer)
788 		{
789 #ifdef USE_CONSOLE
790 			// Dedicated server: if there are not enough players for this game, abort and quit the application
791 			if (Game.PlayerInfos.GetPlayerCount() < Game.C4S.GetMinPlayer()
792 				|| ::Network.Clients.Count() <= 2)
793 			{
794 				Log(LoadResStr("IDS_MSG_NOTENOUGHPLAYERSFORTHISRO")); // it would also be nice to send this message to all clients...
795 				Application.Quit();
796 			}
797 			// Start the game
798 			else
799 #endif // USE_CONSOLE
800 				::Network.Start();
801 		}
802 	}
803 
Abort()804 	void Countdown::Abort()
805 	{
806 		// host sends packets
807 		if (!::Network.isHost()) return;
808 		C4PacketCountdown pck(C4PacketCountdown::Abort);
809 		::Network.Clients.BroadcastMsgToClients(MkC4NetIOPacket(PID_LobbyCountdown, pck));
810 		// also process on host
811 		MainDlg *pLobby = ::Network.GetLobby();
812 		if (pLobby)
813 		{
814 			pLobby->OnCountdownPacket(pck);
815 		}
816 		else
817 		{
818 			// no lobby: Message to log for dedicated/console hosts
819 			Log(LoadResStr("IDS_PRC_STARTABORTED"));
820 		}
821 	}
822 
823 } // end of namespace
824 
825 
826