OpenClonk
C4GameLobby.cpp
Go to the documentation of this file.
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"
20 #include "gui/C4GameLobby.h"
21 
22 #include "c4group/C4Components.h"
23 #include "control/C4GameControl.h"
24 #include "game/C4Application.h"
26 #include "gui/C4ChatDlg.h"
27 #include "gui/C4GameOptions.h"
28 #include "gui/C4MessageInput.h"
30 #include "network/C4Network2.h"
32 
33 namespace C4GameLobby
34 {
35 
36  bool UserAbort = false;
37 
38 // ----------- C4PacketCountdown ---------------------------------------------
39 
41  {
42  pComp->Value(mkNamingAdapt(iCountdown, "Countdown", 0));
43  }
44 
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 
56  {
58  pComp->Value(mkNamingAdapt(Value, "Value", 0));
59  }
60 
61 
62 // ----------- ScenDescs ---------------------------------------------
63 
64  ScenDesc::ScenDesc(const C4Rect &rcBounds, bool fActive) : C4GUI::Window(), fDescFinished(false)
65  {
66  // build components
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 
75  void ScenDesc::Update()
76  {
77  // scenario present?
79  if (!pRes) return; // something's wrong
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;
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 
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 
122  {
123  // release timer if set
124  Application.Remove(this);
125  }
126 
127 // ----------- MainDlg -----------------------------------------------------------------------------
128 
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
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);
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);
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"));
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);
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
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 
288  {
289  Application.Remove(this);
290  delete pKeyHistoryUp;
291  delete pKeyHistoryDown;
292  }
293 
295  {
296  // abort dlg
297  Close(false);
298  }
299 
301  {
302  bool rIsOn = static_cast<C4GUI::CheckBox *>(pCheckBox)->GetChecked();
304  }
305 
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
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 
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 
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 
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 
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
404  }
405 
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  {
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"),
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 
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 
474  void MainDlg::OnClientJoin(C4Client *pNewClient)
475  {
476  // update list
477  UpdatePlayerList();
478  }
479 
481  {
482  }
483 
484  void MainDlg::OnClientPart(C4Client *pPartClient)
485  {
486  // update list
487  UpdatePlayerList();
488  }
489 
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  {
505  // do countdown
506  OnCountdownPacket(Pkt);
507  }
508  break;
509  case PID_SetScenarioParameter: // set a scenario parameter value
510  {
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 
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 
537  {
538  // show that someone played a sound
539  if (pOfClient && pPlayerList)
540  {
541  pPlayerList->SetClientSoundIcon(pOfClient->getID());
542  }
543  }
544 
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 
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 
565  {
566  UpdatePlayerList();
567  }
568 
569  void MainDlg::UpdatePlayerList()
570  {
571  // this updates ping label texts and teams
572  if (pPlayerList) pPlayerList->Update();
573  }
574 
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,
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,
590  }
591  pMenu->AddItem(pResSheet->GetTitle(), pResSheet->GetToolTip(), C4GUI::Ico_Resource,
593  pMenu->AddItem(pOptionsSheet->GetTitle(), pOptionsSheet->GetToolTip(), C4GUI::Ico_Options,
595  // open it
596  return pMenu;
597  }
598 
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!
615  }
616 
618  {
619  if (pPlayerList) pPlayerList->SetMode(C4PlayerInfoListBox::PILBM_LobbyClientSort);
620  if (pRightTab)
621  {
622  pRightTab->SelectSheet(SheetIdx_PlayerList, true);
623  UpdateRightTab();
624  }
625  }
626 
628  {
629  if (pPlayerList) pPlayerList->SetMode(C4PlayerInfoListBox::PILBM_LobbyTeamSort);
630  if (pRightTab)
631  {
632  pRightTab->SelectSheet(SheetIdx_PlayerList, true);
633  UpdateRightTab();
634  }
635  }
636 
638  {
639  if (pRightTab)
640  {
641  pRightTab->SelectSheet(SheetIdx_Res, true);
642  UpdateRightTab();
643  }
644  }
645 
647  {
648  if (pRightTab)
649  {
650  pRightTab->SelectSheet(SheetIdx_Options, true);
651  UpdateRightTab();
652  }
653  }
654 
656  {
657  if (pRightTab)
658  {
659  pRightTab->SelectSheet(SheetIdx_Scenario, true);
660  UpdateRightTab();
661  }
662  }
663 
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 
684  {
685  // open chat dialog
687  }
688 
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 
706  {
707  // if the password setting has changed, make sure the buttons reflect this change
708  pGameOptionButtons->UpdatePasswordBtn();
709  }
710 
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 
721  {
722  pChatBox->ClearText(true);
723  }
724 
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 
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);
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 
760  {
761  // release timer
762  Application.Remove(this);
763  }
764 
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);
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
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 
805  {
806  // host sends packets
807  if (!::Network.isHost()) return;
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 
#define C4CFN_ScenarioDesc
Definition: C4Components.h:88
C4Config Config
Definition: C4Config.cpp:930
@ CUT_SetReady
Definition: C4Control.h:367
C4GameControl Control
@ CDT_Direct
Definition: C4GameControl.h:36
#define GETPKT(type, name)
C4Game Game
Definition: C4Globals.cpp:52
C4Application Application
Definition: C4Globals.cpp:44
C4Network2 Network
Definition: C4Globals.cpp:53
C4GraphicsResource GraphicsResource
#define C4GUI_MessageFontAlpha
Definition: C4Gui.h:44
#define C4GUI_CaptionFontClr
Definition: C4Gui.h:37
#define C4GUI_ErrorFontClr
Definition: C4Gui.h:53
#define C4GUI_LogFontClr2
Definition: C4Gui.h:52
#define C4GUI_MessageFontClr
Definition: C4Gui.h:43
#define C4GUI_IconExHgt
Definition: C4Gui.h:96
@ KEYSCOPE_Gui
const char * LoadResStr(const char *id)
Definition: C4Language.h:83
bool LogSilent(const char *szMessage, bool fConsole)
Definition: C4Log.cpp:126
bool Log(const char *szMessage)
Definition: C4Log.cpp:204
C4MessageInput MessageInput
C4NetIOPacket MkC4NetIOPacket(char cStatus, const class C4PacketBase &Pkt, const C4NetIO::addr_t &addr=C4NetIO::addr_t())
Definition: C4PacketBase.h:40
@ PID_SetScenarioParameter
Definition: C4PacketBase.h:126
@ CID_ClientUpdate
Definition: C4PacketBase.h:145
@ PID_LobbyCountdown
Definition: C4PacketBase.h:125
void StopSoundEffect(const char *szSndName, C4Object *pObj)
C4SoundInstance * StartSoundEffect(const char *szSndName, bool fLoop, int32_t iVolume, C4Object *pObj, int32_t iCustomFalloffDistance, int32_t iPitch, C4SoundModifier *modifier)
const int ALeft
Definition: C4Surface.h:41
uint32_t DWORD
StdParameterAdapt< T, P > mkParAdapt(T &&rObj, P &&rPar)
Definition: StdAdaptors.h:490
StdNamingAdapt< T > mkNamingAdapt(T &&rValue, const char *szName)
Definition: StdAdaptors.h:92
StdStrBuf FormatString(const char *szFmt,...)
Definition: StdBuf.cpp:270
bool ItemExists(const char *szItemName)
Definition: StdFile.h:75
bool fHost
Definition: TstC4NetIO.cpp:31
void NotifyUserIfInactive()
Definition: C4App.cpp:89
void Quit() override
static C4ChatDlg * ShowChat()
Definition: C4ChatDlg.cpp:966
static bool IsChatActive()
Definition: C4ChatDlg.cpp:996
int32_t getID() const
Definition: C4Client.h:105
bool IsIgnored() const
Definition: C4Client.h:114
int32_t getLocalID() const
Definition: C4Client.h:171
C4PlayerInfo * GetPlayerInfo(int32_t iIndex) const
const char * GetData() const
char LanguageEx[CFG_MaxString+1]
Definition: C4Config.h:38
C4ConfigGeneral General
Definition: C4Config.h:255
C4ConfigStartup Startup
Definition: C4Config.h:264
const char * AtRelativePath(const char *filename)
Definition: C4Config.cpp:741
C4ConfigLobby Lobby
Definition: C4Config.h:260
int32_t CountdownTime
Definition: C4Config.h:201
int32_t HideMsgPlrNoTakeOver
Definition: C4Config.h:190
void SetOnChecked(BaseCallbackHandler *pCB)
bool GetFromLeft(int32_t iWdt, int32_t iHgt, C4Rect &rcOut)
Definition: C4Gui.cpp:1076
bool GetCentered(int32_t iWdt, int32_t iHgt, C4Rect &rcOut)
Definition: C4Gui.cpp:1133
int32_t GetHeight() const
Definition: C4Gui.h:2804
int32_t GetInnerWidth() const
Definition: C4Gui.h:2808
bool GetFromRight(int32_t iWdt, int32_t iHgt, C4Rect &rcOut)
Definition: C4Gui.cpp:1093
void ExpandTop(int32_t iByHgt)
Definition: C4Gui.h:2811
bool GetFromTop(int32_t iHgt, int32_t iWdt, C4Rect &rcOut)
Definition: C4Gui.cpp:1059
void GetAll(C4Rect &rcOut)
Definition: C4Gui.cpp:1125
bool GetFromBottom(int32_t iHgt, int32_t iWdt, C4Rect &rcOut)
Definition: C4Gui.cpp:1109
void AddElement(Element *pChild)
void AddItem(const char *szText, const char *szToolTip=nullptr, Icons icoIcon=Ico_None, MenuHandler *pMenuHandler=nullptr, ContextHandler *pSubmenuHandler=nullptr)
Definition: C4Gui.h:1874
void SetFocus(Control *pCtrl, bool fByMouse)
void Close(bool fOK)
bool fOK
Definition: C4Gui.h:2083
bool IsFocused(Control *pCtrl) override
Definition: C4Gui.h:2114
static int32_t GetDefaultEditHeight()
Definition: C4GuiEdit.cpp:113
const char * GetText()
Definition: C4Gui.h:1339
bool InsertText(const char *szText, bool fUser)
Definition: C4GuiEdit.cpp:163
void SelectAll()
Definition: C4GuiEdit.cpp:641
void DeleteSelection()
Definition: C4GuiEdit.cpp:149
C4Rect rcBounds
Definition: C4Gui.h:385
C4Rect GetToprightCornerRect(int32_t iWidth=16, int32_t iHeight=16, int32_t iHIndent=4, int32_t iVIndent=4, int32_t iIndexX=0)
Definition: C4Gui.cpp:399
virtual Screen * GetScreen()
Definition: C4Gui.cpp:289
C4Rect GetContainedClientRect()
Definition: C4Gui.h:448
void SetBounds(const C4Rect &rcNewBound)
Definition: C4Gui.h:446
void SetToolTip(const char *szNewTooltip, bool is_immediate=false)
Definition: C4Gui.cpp:409
const char * GetToolTip()
Definition: C4Gui.cpp:423
void SetContextHandler(ContextHandler *pNewHd)
Definition: C4Gui.h:465
void SetText(const char *szToText, bool fAllowHotkey=true)
Definition: C4GuiLabels.cpp:74
void SetClickFocusControl(Control *pToCtrl)
Definition: C4Gui.h:504
bool ShowMessageModal(const char *szMessage, const char *szCaption, DWORD dwButtons, Icons icoIcon, int32_t *piConfigDontShowAgainSetting=nullptr)
const char * GetTitle()
Definition: C4Gui.h:1625
Sheet * AddSheet(const char *szTitle, int32_t icoTitle=Ico_None)
void SelectSheet(int32_t iIndex, bool fByUser)
Sheet * GetActiveSheet()
Definition: C4Gui.h:1708
int32_t GetActiveSheetIndex()
Sheet * GetSheet(int32_t iIndex)
Definition: C4Gui.h:1707
void SetDecoration(bool fDrawBG, bool fDrawFrame, ScrollBarFacets *pToGfx, bool fAutoScroll)
Definition: C4Gui.h:1753
void ScrollToBottom()
Definition: C4Gui.h:1743
void ClearText(bool fDoUpdate)
Definition: C4Gui.h:1745
void AddTextLine(const char *szText, CStdFont *pFont, DWORD dwClr, bool fDoUpdate, bool fMakeReadableOnBlack, CStdFont *pCaptionFont=nullptr)
Definition: C4Gui.h:1741
void UpdateHeight()
Definition: C4Gui.h:1751
C4Rect & GetClientRect() override
Definition: C4Gui.h:864
static int32_t GetDefaultHeight(CStdFont *pUseFont=nullptr)
void DoInput(C4PacketType eCtrlType, C4ControlPacket *pPkt, C4ControlDeliveryType eDelivery)
C4TeamList & Teams
Definition: C4Game.h:70
C4Scenario C4S
Definition: C4Game.h:74
C4ClientList & Clients
Definition: C4Game.h:69
C4PlayerInfoList & PlayerInfos
Definition: C4Game.h:71
C4PlayerInfoList & RestorePlayerInfos
Definition: C4Game.h:72
C4GameParameters & Parameters
Definition: C4Game.h:67
StdCopyStrBuf ScenarioTitle
Definition: C4Game.h:103
StdStrBuf GetCountdownMsg(bool fInitialMsg=false) const
Definition: C4GameLobby.cpp:45
void CompileFunc(StdCompiler *pComp) override
Definition: C4GameLobby.cpp:40
int32_t GetCountdown() const
Definition: C4GameLobby.h:44
void CompileFunc(StdCompiler *pComp) override
Definition: C4GameLobby.cpp:55
void OnSec1Timer() override
Countdown(int32_t iStartTimer)
void OnTabOptions(C4GUI::Control *btn)
bool KeyHistoryUpDown(bool fUp)
void Start(int32_t iCountdownTime)
void OnClientPart(C4Client *pPartClient)
void OnBtnChat(C4GUI::Control *btn)
void OnCtxTabOptions(C4GUI::Element *pListItem)
Definition: C4GameLobby.h:122
void OnTabRes(C4GUI::Control *btn)
void OnTabPlayers(C4GUI::Control *btn)
void OnClientJoin(C4Client *pNewClient)
void OnCtxTabRes(C4GUI::Element *pListItem)
Definition: C4GameLobby.h:120
void OnReadyCheck(C4GUI::Element *pCheckBox)
void OnSec1Timer() override
void OnTabTeams(C4GUI::Control *btn)
void OnClientSound(C4Client *pOfClient)
void OnTabScenario(C4GUI::Control *btn)
bool OnMessage(C4Client *pOfClient, const char *szMessage)
void OnCtxTabTeams(C4GUI::Element *pListItem)
Definition: C4GameLobby.h:118
C4GUI::ContextMenu * OnRightTabContext(C4GUI::Element *pLabel, int32_t iX, int32_t iY)
void OnError(const char *szErrMsg)
void OnClientAddPlayer(const char *szFilename, int32_t idClient)
void HandlePacket(char cStatus, const C4PacketBase *pBasePkt, C4Network2Client *pClient)
void OnClientConnect(C4Client *pClient, C4Network2IOConnection *pConn)
void OnClosed(bool fOK) override
C4GUI::Edit::InputResult OnChatInput(C4GUI::Edit *edt, bool fPasting, bool fPastingMore)
void OnExitBtn(C4GUI::Control *btn)
void OnRunBtn(C4GUI::Control *btn)
void OnCountdownPacket(const C4PacketCountdown &Pkt)
void OnLog(const char *szLogMsg, DWORD dwClr=C4GUI_LogFontClr)
void OnCtxTabPlayers(C4GUI::Element *pListItem)
Definition: C4GameLobby.h:116
ScenDesc(const C4Rect &rcBounds, bool fActive)
Definition: C4GameLobby.cpp:64
void SetCountdown(bool fToVal)
bool isLeague() const
C4ScenarioParameters ScenarioParameters
C4Network2Res::Ref getNetRes() const
bool Open(const char *group_name, bool do_create=false)
Definition: C4Group.cpp:660
static bool LoadComponentHost(C4ComponentHost *host, C4Group &hGroup, const char *szFilename, const char *szLanguage)
Definition: C4Language.cpp:232
bool ProcessInput(const char *szText)
void StoreBackBuffer(const char *szMessage)
const char * GetBackBuffer(int32_t iIndex)
bool BroadcastMsgToClients(const C4NetIOPacket &rPkt)
C4Network2Players Players
Definition: C4Network2.h:119
void AbortLobbyCountdown()
class C4GameLobby::MainDlg * GetLobby() const
Definition: C4Network2.h:216
bool isHost() const
Definition: C4Network2.h:209
C4Network2ClientList Clients
Definition: C4Network2.h:116
bool Start()
Definition: C4Network2.cpp:506
void StartLobbyCountdown(int32_t iCountdownTime)
DWORD GetClientChatColor(int idForClient, bool fLobby) const
bool JoinLocalPlayer(const char *szLocalPlayerFilename, bool initial=false)
const char * getFile() const
int32_t getPresentPercent() const
bool isComplete() const
int32_t GetAssociatedSavegamePlayerID() const
Definition: C4PlayerInfo.h:126
void SetClientSoundIcon(int32_t iForClientID)
void SetMode(Mode eNewMode)
C4ClientPlayerInfos * GetIndexedInfo(int32_t iIndex) const
Definition: C4PlayerInfo.h:358
C4PlayerInfo * FindUnassociatedRestoreInfo(const C4PlayerInfoList &rRestoreInfoList)
int32_t GetPlayerCount() const
Definition: C4Rect.h:28
int32_t Wdt
Definition: C4Rect.h:30
bool SaveGame
Definition: C4Scenario.h:71
int32_t GetMinPlayer()
Definition: C4Scenario.cpp:141
C4SHead Head
Definition: C4Scenario.h:232
void SetValue(const char *id, int32_t value, bool only_if_larger)
bool IsMultiTeams() const
Definition: C4Teams.h:162
void Value(const T &rStruct)
Definition: StdCompiler.h:161
void Remove(StdSchedulerProc *pProc)
void Add(StdSchedulerProc *pProc)
void Ref(const char *pnData)
Definition: StdBuf.h:455
const char * getData() const
Definition: StdBuf.h:442
@ Ico_Options
Definition: C4Gui.h:656
@ Ico_Gfx
Definition: C4Gui.h:666
@ Ico_Team
Definition: C4Gui.h:662
@ Ico_Resource
Definition: C4Gui.h:651
@ Ico_Player
Definition: C4Gui.h:650
@ Ico_Ex_Chat
Definition: C4Gui.h:713
@ Ico_Error
Definition: C4Gui.h:652
@ Ico_SavegamePlayer
Definition: C4Gui.h:653
void GUISound(const char *szSound)
Definition: C4Gui.cpp:1175
const int32_t AlmostStartCountdownTime
Definition: C4GameLobby.h:27
void LobbyError(const char *szErrorMsg)