OpenClonk
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
C4MessageInput.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2005-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 // handles input dialogs, last-message-buffer, MessageBoard-commands
17 
18 #include "C4Include.h"
19 #include "gui/C4MessageInput.h"
20 
21 #include "control/C4GameControl.h"
22 #include "editor/C4Console.h"
23 #include "game/C4Application.h"
24 #include "game/C4GraphicsSystem.h"
26 #include "gui/C4Gui.h"
27 #include "gui/C4GameLobby.h"
28 #include "object/C4Object.h"
29 #include "player/C4Player.h"
30 #include "player/C4PlayerList.h"
31 
32 // --------------------------------------------------
33 // C4ChatInputDialog
34 
35 // singleton
36 C4ChatInputDialog *C4ChatInputDialog::pInstance = nullptr;
37 
38 // helper func: Determine whether input text is good for a chat-style-layout dialog
39 bool IsSmallInputQuery(const char *szInputQuery)
40 {
41  if (!szInputQuery) return true;
42  int32_t w,h;
43  if (SCharCount('|', szInputQuery)) return false;
44  if (!::GraphicsResource.TextFont.GetTextExtent(szInputQuery, w,h, true))
45  return false; // ???
46  return w<C4GUI::GetScreenWdt()/5;
47 }
48 
49 C4ChatInputDialog::C4ChatInputDialog(bool fObjInput, C4Object *pScriptTarget, bool fUppercase, bool fTeam, int32_t iPlr, const StdStrBuf &rsInputQuery)
50  : C4GUI::InputDialog(fObjInput ? rsInputQuery.getData() : LoadResStrNoAmp("IDS_CTL_CHAT"), nullptr, C4GUI::Ico_None, nullptr, !fObjInput || IsSmallInputQuery(rsInputQuery.getData())),
51  fObjInput(fObjInput), fUppercase(fUppercase), pTarget(pScriptTarget), iPlr(iPlr), BackIndex(-1), fProcessed(false)
52 {
53  // singleton-var
54  pInstance = this;
55  // set custom edit control
57  // key bindings
58  pKeyHistoryUp = new C4KeyBinding(C4KeyCodeEx(K_UP ), "ChatHistoryUp" , KEYSCOPE_Gui, new C4GUI::DlgKeyCBEx<C4ChatInputDialog, bool>(*this, true , &C4ChatInputDialog::KeyHistoryUpDown), C4CustomKey::PRIO_CtrlOverride);
59  pKeyHistoryDown= new C4KeyBinding(C4KeyCodeEx(K_DOWN), "ChatHistoryDown", KEYSCOPE_Gui, new C4GUI::DlgKeyCBEx<C4ChatInputDialog, bool>(*this, false, &C4ChatInputDialog::KeyHistoryUpDown), C4CustomKey::PRIO_CtrlOverride);
61  pKeyNickComplete = new C4KeyBinding(C4KeyCodeEx(K_TAB), "ChatNickComplete", KEYSCOPE_Gui, new C4GUI::DlgKeyCB<C4ChatInputDialog>(*this, &C4ChatInputDialog::KeyCompleteNick), C4CustomKey::PRIO_CtrlOverride);
62  pKeyPlrControl = new C4KeyBinding(C4KeyCodeEx(KEY_Any, KEYS_Control), "ChatForwardPlrCtrl", KEYSCOPE_Gui, new C4GUI::DlgKeyCBPassKey<C4ChatInputDialog>(*this, &C4ChatInputDialog::KeyPlrControl), C4CustomKey::PRIO_Dlg);
63  pKeyGamepadControl = new C4KeyBinding(C4KeyCodeEx(KEY_Any), "ChatForwardGamepadCtrl", KEYSCOPE_Gui, new C4GUI::DlgKeyCBPassKey<C4ChatInputDialog>(*this, &C4ChatInputDialog::KeyGamepadControlDown, &C4ChatInputDialog::KeyGamepadControlUp, &C4ChatInputDialog::KeyGamepadControlPressed), C4CustomKey::PRIO_PlrControl);
64  pKeyBackClose = new C4KeyBinding(C4KeyCodeEx(K_BACK), "ChatBackspaceClose", KEYSCOPE_Gui, new C4GUI::DlgKeyCB<C4ChatInputDialog>(*this, &C4ChatInputDialog::KeyBackspaceClose), C4CustomKey::PRIO_CtrlOverride);
65  // free when closed...
66  SetDelOnClose();
67  // initial team text
68  if (fTeam) pEdit->InsertText("/team ", true);
69 }
70 
72 {
73  delete pKeyHistoryUp;
74  delete pKeyHistoryDown;
75  delete pKeyAbort;
76  delete pKeyNickComplete;
77  delete pKeyPlrControl;
78  delete pKeyGamepadControl;
79  delete pKeyBackClose;
80  if (this==pInstance) pInstance=nullptr;
81 }
82 
84 {
85  // abort chat: Make sure msg board query is aborted
86  fProcessed = true;
87  if (fObjInput)
88  {
89  // check if the target input is still valid
90  C4Player *pPlr = ::Players.Get(iPlr);
91  if (!pPlr) return;
92  if (pPlr->MarkMessageBoardQueryAnswered(pTarget))
93  {
94  // there was an associated query - it must be removed on all clients synchronized via queue
95  ::Control.DoInput(CID_MsgBoardReply, new C4ControlMsgBoardReply(nullptr, pTarget ? pTarget->Number : 0, iPlr), CDT_Decide);
96  }
97  }
98 }
99 
101 {
102  // make sure chat input is processed, even if closed by other means than Enter on edit
103  if (!fProcessed)
104  if (fOK)
105  OnChatInput(pEdit, false, false); else OnChatCancel();
106  else
107  OnChatCancel();
108  typedef C4GUI::InputDialog BaseDlg;
109  BaseDlg::OnClosed(fOK);
110 }
111 
113 {
114  // no double processing
115  if (fProcessed) return C4GUI::Edit::IR_CloseDlg;
116  // get edit text
117  auto *pEdt = reinterpret_cast<C4GUI::Edit *>(edt);
118  auto *szInputText = const_cast<char *>(pEdt->GetText());
119  // Store to back buffer
120  ::MessageInput.StoreBackBuffer(szInputText);
121  // script queried input?
122  if (fObjInput)
123  {
124  fProcessed = true;
125  // check if the target input is still valid
126  C4Player *pPlr = ::Players.Get(iPlr);
127  if (!pPlr) return C4GUI::Edit::IR_CloseDlg;
128  if (!pPlr->MarkMessageBoardQueryAnswered(pTarget))
129  {
130  // there was no associated query!
132  }
133  // then do a script callback, incorporating the input into the answer
134  if (fUppercase) SCapitalize(szInputText);
135  ::Control.DoInput(CID_MsgBoardReply, new C4ControlMsgBoardReply(szInputText, pTarget ? pTarget->Number : 0, iPlr), CDT_Decide);
137  }
138  else
139  // reroute to message input class
140  ::MessageInput.ProcessInput(szInputText);
141  // safety: message board commands may do strange things
142  if (this!=pInstance) return C4GUI::Edit::IR_Abort;
143  // select all text to be removed with next keypress
144  // just for pasting mode; usually the dlg will be closed now anyway
145  pEdt->SelectAll();
146  // avoid dlg close, if more content is to be pasted
147  if (fPastingMore) return C4GUI::Edit::IR_None;
148  fProcessed = true;
150 }
151 
152 bool C4ChatInputDialog::KeyHistoryUpDown(bool fUp)
153 {
154  // browse chat history
156  const char *szPrevInput = ::MessageInput.GetBackBuffer(fUp ? (++BackIndex) : (--BackIndex));
157  if (!szPrevInput || !*szPrevInput)
158  BackIndex = -1;
159  else
160  {
161  pEdit->InsertText(szPrevInput, true);
162  pEdit->SelectAll();
163  }
164  return true;
165 }
166 
167 bool C4ChatInputDialog::KeyPlrControl(const C4KeyCodeEx &key)
168 {
169  // Control pressed while doing this key: Reroute this key as a player-control
170  Game.DoKeyboardInput(WORD(key.Key), KEYEV_Down, !!(key.dwShift & KEYS_Alt), false, !!(key.dwShift & KEYS_Shift), key.IsRepeated(), nullptr, true);
171  // mark as processed, so it won't get any double processing
172  return true;
173 }
174 
175 bool C4ChatInputDialog::KeyGamepadControlDown(const C4KeyCodeEx &key)
176 {
177  // filter gamepad control
178  if (!Key_IsGamepad(key.Key)) return false;
179  // forward it
180  Game.DoKeyboardInput(key.Key, KEYEV_Down, false, false, false, key.IsRepeated(), nullptr, true);
181  return true;
182 }
183 
184 bool C4ChatInputDialog::KeyGamepadControlUp(const C4KeyCodeEx &key)
185 {
186  // filter gamepad control
187  if (!Key_IsGamepad(key.Key)) return false;
188  // forward it
189  Game.DoKeyboardInput(key.Key, KEYEV_Up, false, false, false, key.IsRepeated(), nullptr, true);
190  return true;
191 }
192 
193 bool C4ChatInputDialog::KeyGamepadControlPressed(const C4KeyCodeEx &key)
194 {
195  // filter gamepad control
196  if (!Key_IsGamepad(key.Key)) return false;
197  // forward it
198  Game.DoKeyboardInput(key.Key, KEYEV_Pressed, false, false, false, key.IsRepeated(), nullptr, true);
199  return true;
200 }
201 
202 bool C4ChatInputDialog::KeyBackspaceClose()
203 {
204  // close if chat text box is empty (on backspace)
205  if (pEdit->GetText() && *pEdit->GetText()) return false;
206  Close(false);
207  return true;
208 }
209 
210 bool C4ChatInputDialog::KeyCompleteNick()
211 {
212  if (!pEdit) return false;
213  char IncompleteNick[256+1];
214  // get current word in edit
215  if (!pEdit->GetCurrentWord(IncompleteNick, 256)) return false;
216  if (!*IncompleteNick) return false;
217  C4Player *plr = ::Players.First;
218  while (plr)
219  {
220  // Compare name and input
221  if (SEqualNoCase(plr->GetName(), IncompleteNick, SLen(IncompleteNick)))
222  {
223  pEdit->InsertText(plr->GetName() + SLen(IncompleteNick), true);
224  return true;
225  }
226  else
227  plr = plr->Next;
228  }
229  // no match found
230  return false;
231 }
232 
233 
234 // --------------------------------------------------
235 // C4MessageInput
236 
238 {
239  // add default commands
240  if (!pCommands)
241  {
242  AddCommand("speed", "SetGameSpeed(%d)");
243  }
244  return true;
245 }
246 
248 {
249  // clear backlog
250  for (auto & cnt : BackBuffer) cnt[0]=0;
251 }
252 
254 {
255  // close any dialog
256  CloseTypeIn();
257  // free messageboard-commands
258  C4MessageBoardCommand *pCmd;
259  while ((pCmd = pCommands))
260  {
261  pCommands = pCmd->Next;
262  delete pCmd;
263  }
264 }
265 
267 {
268  // close dialog if present and valid
269  C4ChatInputDialog *pDlg = GetTypeIn();
270  if (!pDlg) return false;
271  pDlg->Close(false);
272  return true;
273 }
274 
275 bool C4MessageInput::StartTypeIn(bool fObjInput, C4Object *pObj, bool fUpperCase, bool fTeam, int32_t iPlr, const StdStrBuf &rsInputQuery)
276 {
277  // close any previous
278  if (IsTypeIn()) CloseTypeIn();
279  // start new
280  return ::pGUI->ShowRemoveDlg(new C4ChatInputDialog(fObjInput, pObj, fUpperCase, fTeam, iPlr, rsInputQuery));
281 }
282 
284 {
285  // fullscreen only
286  if (Application.isEditor) return false;
287  // OK, start typing
288  return StartTypeIn(false, nullptr, false, fTeam);
289 }
290 
292 {
293  // toggle off?
294  if (IsTypeIn())
295  {
296  // no accidental close of script queried dlgs by chat request
297  if (GetTypeIn()->IsScriptQueried()) return false;
298  return CloseTypeIn();
299  }
300  else
301  // toggle on!
302  return StartTypeIn();
303 }
304 
306 {
307  // check GUI and dialog
309 }
310 
311 bool C4MessageInput::ProcessInput(const char *szText)
312 {
313  // helper variables
314  char OSTR[402]; // cba
315  C4ControlMessageType eMsgType;
316  const char *szMsg = nullptr;
317  int32_t iToPlayer = -1;
318 
319  // Starts with '^', "team:" or "/team ": Team message
320  if (szText[0] == '^' || SEqual2NoCase(szText, "team:") || SEqual2NoCase(szText, "/team "))
321  {
322  if (!Game.Teams.IsTeamVisible())
323  {
324  // team not known; can't send!
325  Log(LoadResStr("IDS_MSG_CANTSENDTEAMMESSAGETEAMSN"));
326  return false;
327  }
328  else
329  {
330  eMsgType = C4CMT_Team;
331  szMsg = szText[0] == '^' ? szText+1 :
332  szText[0] == '/' ? szText+6 : szText+5;
333  }
334  }
335  // Starts with "/private ": Private message (running game only)
336  else if (Game.IsRunning && SEqual2NoCase(szText, "/private "))
337  {
338  // get target name
339  char szTargetPlr[C4MaxName + 1];
340  SCopyUntil(szText + 9, szTargetPlr, ' ', C4MaxName);
341  // search player
342  C4Player *pToPlr = ::Players.GetByName(szTargetPlr);
343  if (!pToPlr) return false;
344  // set
345  eMsgType = C4CMT_Private;
346  iToPlayer = pToPlr->Number;
347  szMsg = szText + 10 + SLen(szText);
348  if (szMsg > szText + SLen(szText)) return false;
349  }
350  // Starts with "/me ": Me-Message
351  else if (SEqual2NoCase(szText, "/me "))
352  {
353  eMsgType = C4CMT_Me;
354  szMsg = szText+4;
355  }
356  // Starts with "/sound ": Sound-Message
357  else if (SEqual2NoCase(szText, "/sound "))
358  {
359  eMsgType = C4CMT_Sound;
360  szMsg = szText+7;
361  }
362  // Disabled due to spamming
363  // Starts with "/alert": Taskbar flash (message optional)
364  else if (SEqual2NoCase(szText, "/alert ") || SEqualNoCase(szText, "/alert"))
365  {
366  eMsgType = C4CMT_Alert;
367  szMsg = szText+6;
368  if (*szMsg) ++szMsg;
369  }
370  // Starts with '"': Let the clonk say it
371  else if (Game.IsRunning && szText[0] == '"')
372  {
373  eMsgType = C4CMT_Say;
374  // Append '"', if neccessary
375  StdStrBuf text(szText);
376  SCopy(szText, OSTR, 400);
377  char *pEnd = OSTR + SLen(OSTR) - 1;
378  if (*pEnd != '"') { *++pEnd = '"'; *++pEnd = 0; }
379  szMsg = OSTR;
380  }
381  // Starts with '/': Command
382  else if (szText[0] == '/')
383  return ProcessCommand(szText);
384  // Regular message
385  else
386  {
387  eMsgType = C4CMT_Normal;
388  szMsg = szText;
389  }
390 
391  // message?
392  if (szMsg)
393  {
394  char szMessage[C4MaxMessage + 1];
395  // go over whitespaces, check empty message
396  while (IsWhiteSpace(*szMsg)) szMsg++;
397  if (!*szMsg)
398  {
399  if (eMsgType != C4CMT_Alert) return true;
400  *szMessage = '\0';
401  }
402  else
403  {
404  // trim right
405  const char *szEnd = szMsg + SLen(szMsg) - 1;
406  while (IsWhiteSpace(*szEnd) && szEnd >= szMsg) szEnd--;
407  // Say: Strip quotation marks in cinematic film mode
409  {
410  if (eMsgType == C4CMT_Say) { ++szMsg; szEnd--; }
411  }
412  // get message
413  SCopy(szMsg, szMessage, std::min<ptrdiff_t>(C4MaxMessage, szEnd - szMsg + 1));
414  }
415  // get sending player (if any)
416  C4Player *pPlr = Game.IsRunning ? ::Players.GetLocalByIndex(0) : nullptr;
417  // send
419  new C4ControlMessage(eMsgType, szMessage, pPlr ? pPlr->Number : -1, iToPlayer),
420  CDT_Private);
421  }
422 
423  return true;
424 }
425 
426 bool C4MessageInput::ProcessCommand(const char *szCommand)
427 {
429  // command
430  // must be 1 char longer than the longest command only. If given commands are longer, they will be truncated, and such a command won't exist anyway
431  const int32_t MaxCommandLen = 20;
432  char szCmdName[MaxCommandLen + 1];
433  SCopyUntil(szCommand + 1, szCmdName, ' ', MaxCommandLen);
434  // parameter
435  const char *pCmdPar = SSearch(szCommand, " ");
436  if (!pCmdPar) pCmdPar = "";
437 
438  // CAUTION when implementing special commands (like /quit) here:
439  // those must not be executed when text is pasted, because that could crash the GUI system
440  // when there are additional lines to paste, but the edit field is destructed by the command
441 
442  // lobby-only commands
443  if (!Game.IsRunning && SEqualNoCase(szCmdName, "joinplr"))
444  {
445  // compose path from given filename
446  StdStrBuf plrPath;
447  plrPath.Format("%s%s", Config.General.UserDataPath, pCmdPar);
448  // player join - check filename
449  if (!ItemExists(plrPath.getData()))
450  {
451  C4GameLobby::LobbyError(FormatString(LoadResStr("IDS_MSG_CMD_JOINPLR_NOFILE"), plrPath.getData()).getData());
452  }
453  else
455  return true;
456  }
457  if (!Game.IsRunning && SEqualNoCase(szCmdName, "plrclr"))
458  {
459  // get player name from input text
460  int iSepPos = SCharPos(' ', pCmdPar, 0);
461  C4PlayerInfo *pNfo=nullptr;
462  int32_t idLocalClient = -1;
463  if (::Network.Clients.GetLocal()) idLocalClient = ::Network.Clients.GetLocal()->getID();
464  if (iSepPos>0)
465  {
466  // a player name is given: Parse it
467  StdStrBuf sPlrName;
468  sPlrName.Copy(pCmdPar, iSepPos);
469  pCmdPar += iSepPos+1; int32_t id=0;
470  while ((pNfo = Game.PlayerInfos.GetNextPlayerInfoByID(id)))
471  {
472  id = pNfo->GetID();
473  if (WildcardMatch(sPlrName.getData(), pNfo->GetName())) break;
474  }
475  }
476  else
477  // no player name: Set local player
478  pNfo = Game.PlayerInfos.GetPrimaryInfoByClientID(idLocalClient);
479  C4ClientPlayerInfos *pCltNfo=nullptr;
480  if (pNfo) pCltNfo = Game.PlayerInfos.GetClientInfoByPlayerID(pNfo->GetID());
481  if (!pCltNfo)
482  {
483  C4GameLobby::LobbyError(LoadResStr("IDS_MSG_CMD_PLRCLR_NOPLAYER"));
484  }
485  else
486  {
487  // may color of this client be set?
488  if (pCltNfo->GetClientID() != idLocalClient && !::Network.isHost())
489  {
490  C4GameLobby::LobbyError(LoadResStr("IDS_MSG_CMD_PLRCLR_NOACCESS"));
491  }
492  else
493  {
494  // get color to set
495  uint32_t dwNewClr;
496  if (sscanf(pCmdPar, "%x", &dwNewClr) != 1)
497  {
498  C4GameLobby::LobbyError(LoadResStr("IDS_MSG_CMD_PLRCLR_USAGE"));
499  }
500  else
501  {
502  // color validation
503  dwNewClr |= 0xff000000;
504  if (!dwNewClr) ++dwNewClr;
505  // request a color change to this color
506  C4ClientPlayerInfos LocalInfoRequest = *pCltNfo;
507  C4PlayerInfo *pPlrInfo = LocalInfoRequest.GetPlayerInfoByID(pNfo->GetID());
508  assert(pPlrInfo);
509  if (pPlrInfo)
510  {
511  pPlrInfo->SetOriginalColor(dwNewClr); // set this as a new color wish
512  ::Network.Players.RequestPlayerInfoUpdate(LocalInfoRequest);
513  }
514  }
515  }
516  }
517  return true;
518  }
519  if (!Game.IsRunning && SEqualNoCase(szCmdName, "start"))
520  {
521  // timeout given?
522  int32_t iTimeout = Config.Lobby.CountdownTime;
523  if (!::Network.isHost())
524  C4GameLobby::LobbyError(LoadResStr("IDS_MSG_CMD_HOSTONLY"));
525  else if (pCmdPar && *pCmdPar && (!sscanf(pCmdPar, "%d", &iTimeout) || iTimeout<0))
526  C4GameLobby::LobbyError(LoadResStr("IDS_MSG_CMD_START_USAGE"));
527  else if (pLobby)
528  {
529  // abort previous countdown
531  // start new countdown (aborts previous if necessary)
532  pLobby->Start(iTimeout);
533  }
534  else
535  {
536  if (iTimeout)
537  ::Network.StartLobbyCountdown(iTimeout);
538  else
539  ::Network.Start();
540  }
541  return true;
542  }
543  if (!Game.IsRunning && SEqualNoCase(szCmdName, "abort"))
544  {
545  if (!::Network.isHost())
546  C4GameLobby::LobbyError(LoadResStr("IDS_MSG_CMD_HOSTONLY"));
547  else if (::Network.isLobbyCountDown())
549  else
550  C4GameLobby::LobbyError(LoadResStr("IDS_MSG_CMD_ABORT_NOCOUNTDOWN"));
551  return true;
552  }
553 
554  if (SEqual(szCmdName, "help"))
555  {
556  Log(LoadResStr(pLobby ? "IDS_TEXT_COMMANDSAVAILABLEDURINGLO" : "IDS_TEXT_COMMANDSAVAILABLEDURINGGA"));
557  if (!Game.IsRunning)
558  {
559  LogF("/start [time] - %s", LoadResStr("IDS_TEXT_STARTTHEROUNDWITHSPECIFIE"));
560  LogF("/abort - %s", LoadResStr("IDS_TEXT_ABORTSTARTCOUNTDOWN"));
561  LogF("/alert - %s", LoadResStr("IDS_TEXT_ALERTTHEHOSTIFTHEHOSTISAW"));
562  LogF("/joinplr [filename] - %s", LoadResStr("IDS_TEXT_JOINALOCALPLAYERFROMTHESP"));
563  LogF("/plrclr [player] [RGB] - %s", LoadResStr("IDS_TEXT_CHANGETHECOLOROFTHESPECIF"));
564  LogF("/plrclr [RGB] - %s", LoadResStr("IDS_TEXT_CHANGEYOUROWNPLAYERCOLOR"));
565  }
566  else
567  {
568  LogF("/fast [x] - %s", LoadResStr("IDS_TEXT_SETTOFASTMODESKIPPINGXFRA"));
569  LogF("/slow - %s", LoadResStr("IDS_TEXT_SETTONORMALSPEEDMODE"));
570  LogF("/chart - %s", LoadResStr("IDS_TEXT_DISPLAYNETWORKSTATISTICS"));
571  LogF("/nodebug - %s", LoadResStr("IDS_TEXT_PREVENTDEBUGMODEINTHISROU"));
572  LogF("/script [script] - %s", LoadResStr("IDS_TEXT_EXECUTEASCRIPTCOMMAND"));
573  LogF("/screenshot [zoom] - %s", LoadResStr("IDS_TEXT_SAFEZOOMEDFULLSCREENSHOT"));
574  }
575  LogF("/kick [client] - %s", LoadResStr("IDS_TEXT_KICKTHESPECIFIEDCLIENT"));
576  LogF("/observer [client] - %s", LoadResStr("IDS_TEXT_SETTHESPECIFIEDCLIENTTOOB"));
577  LogF("/me [action] - %s", LoadResStr("IDS_TEXT_PERFORMANACTIONINYOURNAME"));
578  LogF("/sound [sound] - %s", LoadResStr("IDS_TEXT_PLAYASOUNDFROMTHEGLOBALSO"));
579  if (Game.IsRunning) LogF("/private [player] [message] - %s", LoadResStr("IDS_MSG_SENDAPRIVATEMESSAGETOTHES"));
580  LogF("/team [message] - %s", LoadResStr("IDS_MSG_SENDAPRIVATEMESSAGETOYOUR"));
581  LogF("/set comment [comment] - %s", LoadResStr("IDS_TEXT_SETANEWNETWORKCOMMENT"));
582  LogF("/set password [password] - %s", LoadResStr("IDS_TEXT_SETANEWNETWORKPASSWORD"));
583  LogF("/set maxplayer [number] - %s", LoadResStr("IDS_TEXT_SETANEWMAXIMUMNUMBEROFPLA"));
584  LogF("/todo [text] - %s", LoadResStr("IDS_TEXT_ADDTODO"));
585  LogF("/clear - %s", LoadResStr("IDS_MSG_CLEARTHEMESSAGEBOARD"));
586  return true;
587  }
588  // dev-scripts
589  if (SEqual(szCmdName, "script"))
590  {
591  if (!Game.IsRunning) return false;
592  if (!Game.DebugMode) return false;
593 
595  return true;
596  }
597  // set runtime properties
598  if (SEqual(szCmdName, "set"))
599  {
600  if (SEqual2(pCmdPar, "maxplayer "))
601  {
602  if (::Control.isCtrlHost())
603  {
604  if (atoi(pCmdPar+10) == 0 && !SEqual(pCmdPar+10, "0"))
605  {
606  Log("Syntax: /set maxplayer count");
607  return false;
608  }
610  new C4ControlSet(C4CVT_MaxPlayer, atoi(pCmdPar+10)),
611  CDT_Decide);
612  return true;
613  }
614  }
615  if (SEqual2(pCmdPar, "comment ") || SEqual(pCmdPar, "comment"))
616  {
617  if (!::Network.isEnabled() || !::Network.isHost()) return false;
618  // Set in configuration, update reference
619  Config.Network.Comment.CopyValidated(pCmdPar[7] ? (pCmdPar+8) : "");
621  Log(LoadResStr("IDS_NET_COMMENTCHANGED"));
622  return true;
623  }
624  if (SEqual2(pCmdPar, "password ") || SEqual(pCmdPar, "password"))
625  {
626  if (!::Network.isEnabled() || !::Network.isHost()) return false;
627  ::Network.SetPassword(pCmdPar[8] ? (pCmdPar+9) : nullptr);
628  if (pLobby) pLobby->UpdatePassword();
629  return true;
630  }
631  // unknown property
632  return false;
633  }
634  // get szen from network folder - not in lobby; use res tab there
635  if (SEqual(szCmdName, "netgetscen"))
636  {
637  if (::Network.isEnabled() && !::Network.isHost() && !pLobby)
638  {
639  const C4Network2ResCore *pResCoreScen = Game.Parameters.Scenario.getResCore();
640  if (pResCoreScen)
641  {
642  C4Network2Res::Ref pScenario = ::Network.ResList.getRefRes(pResCoreScen->getID());
643  if (pScenario)
645  {
646  LogF(LoadResStr("IDS_MSG_CMD_NETGETSCEN_SAVED"), Config.AtUserDataPath(GetFilename(Game.ScenarioFilename)));
647  return true;
648  }
649  }
650  }
651  return false;
652  }
653  // clear message board
654  if (SEqual(szCmdName, "clear"))
655  {
656  // lobby
657  if (pLobby)
658  {
659  pLobby->ClearLog();
660  }
661  // fullscreen
662  else if (::GraphicsSystem.MessageBoard)
663  ::GraphicsSystem.MessageBoard->ClearLog();
664  else
665  {
666  // EM mode
667  Console.ClearLog();
668  }
669  return true;
670  }
671  // kick client
672  if (SEqual(szCmdName, "kick"))
673  {
674  if (::Network.isEnabled() && ::Network.isHost())
675  {
676  // find client
677  C4Client *pClient = Game.Clients.getClientByName(pCmdPar);
678  if (!pClient)
679  {
680  LogF(LoadResStr("IDS_MSG_CMD_NOCLIENT"), pCmdPar);
681  return false;
682  }
683  // league: Kick needs voting
684  if (Game.Parameters.isLeague() && ::Players.GetAtClient(pClient->getID()))
685  ::Network.Vote(VT_Kick, true, pClient->getID());
686  else
687  // add control
688  Game.Clients.CtrlRemove(pClient, LoadResStr("IDS_MSG_KICKFROMMSGBOARD"));
689  }
690  return true;
691  }
692  // set fast mode
693  if (SEqual(szCmdName, "fast"))
694  {
695  if (!Game.IsRunning) return false;
696  int32_t iFS;
697  if ((iFS=atoi(pCmdPar)) == 0) return false;
698  // set frameskip and fullspeed flag
699  Game.FrameSkip=Clamp<int32_t>(iFS,1,500);
700  Game.FullSpeed=true;
701  // start calculation immediatly
703  return true;
704  }
705  // reset fast mode
706  if (SEqual(szCmdName, "slow"))
707  {
708  if (!Game.IsRunning) return false;
709  Game.FullSpeed=false;
710  Game.FrameSkip=1;
711  return true;
712  }
713 
714  if (SEqual(szCmdName, "nodebug"))
715  {
716  if (!Game.IsRunning) return false;
718  return true;
719  }
720 
721  // kick/activate/deactivate/observer
722  if (SEqual(szCmdName, "activate") || SEqual(szCmdName, "deactivate") || SEqual(szCmdName, "observer"))
723  {
724  if (!::Network.isEnabled() || !::Network.isHost())
725  { Log(LoadResStr("IDS_MSG_CMD_HOSTONLY")); return false; }
726  // search for client
727  C4Client *pClient = Game.Clients.getClientByName(pCmdPar);
728  if (!pClient)
729  {
730  LogF(LoadResStr("IDS_MSG_CMD_NOCLIENT"), pCmdPar);
731  return false;
732  }
733  // what to do?
734  C4ControlClientUpdate *pCtrl = nullptr;
735  if (szCmdName[0] == 'a') // activate
736  pCtrl = new C4ControlClientUpdate(pClient->getID(), CUT_Activate, true);
737  else if (szCmdName[0] == 'd' && !Game.Parameters.isLeague()) // deactivate
738  pCtrl = new C4ControlClientUpdate(pClient->getID(), CUT_Activate, false);
739  else if (szCmdName[0] == 'o' && !Game.Parameters.isLeague()) // observer
740  pCtrl = new C4ControlClientUpdate(pClient->getID(), CUT_SetObserver);
741  // perform it
742  if (pCtrl)
744  else
745  Log(LoadResStr("IDS_LOG_COMMANDNOTALLOWEDINLEAGUE"));
746  return true;
747  }
748 
749  // control mode
750  if (SEqual(szCmdName, "centralctrl") || SEqual(szCmdName, "decentralctrl") || SEqual(szCmdName, "asyncctrl"))
751  {
752  if (!::Network.isEnabled() || !::Network.isHost())
753  { Log(LoadResStr("IDS_MSG_CMD_HOSTONLY")); return false; }
754  ::Network.SetCtrlMode(*szCmdName == 'c' ? CNM_Central : *szCmdName == 'd' ? CNM_Decentral : CNM_Async);
755  return true;
756  }
757 
758  // show chart
759  if (Game.IsRunning)
760  if (SEqual(szCmdName, "chart"))
761  return Game.ToggleChart();
762 
763  // whole map screenshot
764  if (SEqual(szCmdName, "screenshot"))
765  {
766  double zoom = atof(pCmdPar);
767  if (zoom<=0) return false;
768  ::GraphicsSystem.SaveScreenshot(true, zoom);
769  return true;
770  }
771 
772  // add to TODO list
773  if (SEqual(szCmdName, "todo"))
774  {
775  // must add something
776  if (!pCmdPar || !*pCmdPar) return false;
777  // try writing main file (usually {SCENARIO}/TODO.txt); if access is not possible, e.g. because scenario is packed, write to alternate file
778  const char *todo_filenames[] = { ::Config.Developer.TodoFilename, ::Config.Developer.AltTodoFilename };
779  bool success = false;
780  for (auto & i : todo_filenames)
781  {
782  StdCopyStrBuf todo_filename(i);
783  todo_filename.Replace("{USERPATH}", Config.General.UserDataPath);
784  int replacements = todo_filename.Replace("{SCENARIO}", Game.ScenarioFile.GetFullName().getData());
785  // sanity checks for writing scenario TODO file
786  if (replacements)
787  {
788  // entered in editor with no file open?
789  if (!::Game.ScenarioFile.IsOpen()) continue;
790  // not into packed
791  if (::Game.ScenarioFile.IsPacked()) continue;
792  // not into temp network file
793  if (::Control.isNetwork() && !::Control.isCtrlHost()) continue;
794  }
795  // try to append. May fail e.g. on packed scenario file, name getting too long, etc. Then fallback to alternate location.
796  CStdFile todo_file;
797  if (!todo_file.Append(todo_filename.getData())) continue;
798  if (!todo_file.WriteString(pCmdPar)) continue;
799  // check on file close because CStdFile may do a delayed write
800  if (!todo_file.Close()) continue;
801  success = true;
802  break;
803  }
804  // no message on success to avoid cluttering the chat during debug sessions
805  if (!success) Log(LoadResStr("IDS_ERR_TODO"));
806  return true;
807  }
808 
809  // custom command
810  C4MessageBoardCommand *pCmd;
811  if (Game.IsRunning)
812  if ((pCmd = GetCommand(szCmdName)))
813  {
814  // get player number of first local player; if multiple players
815  // share one computer, we can't distinguish between them anyway
816  int32_t player_num = NO_OWNER;
818  if (player) player_num = player->Number;
819 
820  // send command to network
821  ::Control.DoInput(CID_MsgBoardCmd, new C4ControlMsgBoardCmd(szCmdName, pCmdPar, player_num), CDT_Decide);
822 
823  // ok
824  return true;
825  }
826 
827  // unknown command
828  StdStrBuf sErr; sErr.Format(LoadResStr("IDS_ERR_UNKNOWNCMD"), szCmdName);
829  if (pLobby) pLobby->OnError(sErr.getData()); else Log(sErr.getData());
830  return false;
831 }
832 
833 void C4MessageInput::AddCommand(const char *strCommand, const char *strScript)
834 {
835  if (GetCommand(strCommand)) return;
836  // create entry
838  SCopy(strCommand, pCmd->Name, C4MaxName);
839  SCopy(strScript, pCmd->Script, _MAX_FNAME+30);
840  // add to list
841  pCmd->Next = pCommands; pCommands = pCmd;
842 }
843 
845 {
846  for (C4MessageBoardCommand *pCmd = pCommands; pCmd; pCmd = pCmd->Next)
847  if (SEqual(pCmd->Name, strName))
848  return pCmd;
849  return nullptr;
850 }
851 
853 {
854  // target object loose? stop input
855  C4ChatInputDialog *pDlg = GetTypeIn();
856  if (pDlg && pDlg->GetScriptTargetObject() == pObj) CloseTypeIn();
857 }
858 
860 {
861  // close typein if it is used for the given parameters
862  C4ChatInputDialog *pDlg = GetTypeIn();
863  if (pDlg && pDlg->IsScriptQueried() && pDlg->GetScriptTargetObject() == pObj && pDlg->GetScriptTargetPlayer() == iPlr) CloseTypeIn();
864 }
865 
866 void C4MessageInput::StoreBackBuffer(const char *szMessage)
867 {
868  if (!szMessage || !szMessage[0]) return;
869  int32_t i,cnt;
870  // Check: Remove doubled buffer
871  for (i=0; i<C4MSGB_BackBufferMax-1; ++i)
872  if (SEqual(BackBuffer[i], szMessage))
873  break;
874  // Move up buffers
875  for (cnt=i; cnt>0; cnt--) SCopy(BackBuffer[cnt-1],BackBuffer[cnt]);
876  // Add message
877  SCopy(szMessage,BackBuffer[0], C4MaxMessage);
878 }
879 
880 const char *C4MessageInput::GetBackBuffer(int32_t iIndex)
881 {
882  if (!Inside<int32_t>(iIndex, 0, C4MSGB_BackBufferMax-1)) return nullptr;
883  return BackBuffer[iIndex];
884 }
885 
887 {
888  Name[0] = '\0'; Script[0] = '\0'; Next = nullptr;
889 }
890 
892 {
893  // note that this CompileFunc does not save the fAnswered-flag, so pending message board queries will be re-asked when resuming SaveGames
894  pComp->Separator(StdCompiler::SEP_START); // '('
895  // callback object number
896  pComp->Value(CallbackObj); pComp->Separator();
897  // input query string
898  pComp->Value(sInputQuery); pComp->Separator();
899  // options
900  pComp->Value(fIsUppercase);
901  // list end
902  pComp->Separator(StdCompiler::SEP_END); // ')'
903 }
904 
char * GetFilename(char *szPath)
Definition: StdFile.cpp:45
const char * getData() const
Definition: StdBuf.h:442
C4Client * getClientByName(const char *szName) const
Definition: C4Client.cpp:216
bool Close(StdBuf **ppMemory=nullptr)
Definition: CStdFile.cpp:151
class C4GameLobby::MainDlg * GetLobby() const
Definition: C4Network2.h:216
virtual bool Separator(Sep eSep=SEP_SEP)
Definition: StdCompiler.h:119
bool IsRunning
Definition: C4Game.h:139
void SetPassword(const char *szToPassword)
Definition: C4Network2.cpp:771
bool Start()
Definition: C4Network2.cpp:505
char ScenarioFilename[_MAX_PATH+1]
Definition: C4Game.h:102
friend class C4ChatInputDialog
C4Config Config
Definition: C4Config.cpp:833
const char * GetName() const
Definition: C4PlayerInfo.h:157
void SCopy(const char *szSource, char *sTarget, size_t iMaxL)
Definition: Standard.cpp:130
void SetCtrlMode(int32_t iCtrlMode)
Definition: C4Network2.cpp:818
C4Console Console
Definition: C4Globals.cpp:45
bool isHost() const
Definition: C4Network2.h:209
const char * GetBackBuffer(int32_t iIndex)
int Replace(const char *szOld, const char *szNew, size_t iStartSearch=0)
Definition: StdBuf.cpp:284
void SetCustomEdit(Edit *pCustomEdit)
void AbortLobbyCountdown()
C4MessageBoardCommand * Next
void RequestPlayerInfoUpdate(const class C4ClientPlayerInfos &rRequest)
C4ChatInputDialog * GetTypeIn()
bool isCtrlHost() const
Definition: C4GameControl.h:99
void DeleteSelection()
Definition: C4GuiEdit.cpp:149
C4Game Game
Definition: C4Globals.cpp:52
bool IsSmallInputQuery(const char *szInputQuery)
int32_t Number
Definition: C4Player.h:86
C4Scenario C4S
Definition: C4Game.h:74
C4ConfigGeneral General
Definition: C4Config.h:251
const char * SSearch(const char *szString, const char *szIndex)
Definition: Standard.cpp:341
void SelectAll()
Definition: C4GuiEdit.cpp:641
bool KeyEscape()
Definition: C4Gui.h:2135
bool SEqualNoCase(const char *szStr1, const char *szStr2, int iLen)
Definition: Standard.cpp:185
ValidatedStdCopyStrBuf< C4InVal::VAL_Comment > Comment
Definition: C4Config.h:146
void Close(bool fOK)
const char * GetText()
Definition: C4Gui.h:1338
bool ShowRemoveDlg(Dialog *pDlg)
void Vote(C4ControlVoteType eType, bool fApprove=true, int32_t iData=0)
C4PlayerInfo * GetPrimaryInfoByClientID(int32_t iClientID) const
Definition: C4PlayerInfo.h:376
Definition: C4Rect.h:27
int32_t GetScreenWdt()
Definition: C4Gui.h:2821
C4Player * First
Definition: C4PlayerList.h:31
bool IsWhiteSpace(char cChar)
Definition: Standard.h:72
bool GetTextExtent(const char *szText, int32_t &rsx, int32_t &rsy, bool fCheckMarkup=true)
void Format(const char *szFmt,...) GNUC_FORMAT_ATTRIBUTE_O
Definition: StdBuf.cpp:174
bool IsTeamVisible() const
Definition: C4Teams.cpp:454
C4MessageInput MessageInput
void SCapitalize(char *str)
Definition: Standard.cpp:332
C4ObjectPtr CallbackObj
bool IsScriptQueried() const
C4PlayerInfo * GetNextPlayerInfoByID(int32_t id) const
size_t SLen(const char *sptr)
Definition: Standard.h:74
int32_t getID() const
C4GraphicsResource GraphicsResource
void CtrlRemove(const C4Client *pClient, const char *szReason)
Definition: C4Client.cpp:333
bool SEqual(const char *szStr1, const char *szStr2)
Definition: Standard.h:93
bool GetCurrentWord(char *szTargetBuf, int32_t iMaxTargetBufLen)
Definition: C4GuiEdit.cpp:676
C4GameParameters & Parameters
Definition: C4Game.h:67
void SetDelOnClose(bool fToVal=true)
Definition: C4Gui.h:2190
unsigned int SCharCount(char cTarget, const char *szInStr, const char *cpUntil)
Definition: Standard.cpp:298
char Script[_MAX_FNAME+30+1]
C4Player * GetAtClient(int iClient, int iIndex=0) const
C4TeamList & Teams
Definition: C4Game.h:70
bool DoKeyboardInput(C4KeyCode vk_code, C4KeyEventType eEventType, bool fAlt, bool fCtrl, bool fShift, bool fRepeated, class C4GUI::Dialog *pForDialog=nullptr, bool fPlrCtrlOnly=false, int32_t iStrength=-1)
Definition: C4Game.cpp:1865
C4ClientPlayerInfos * GetClientInfoByPlayerID(int32_t id) const
void StartLobbyCountdown(int32_t iCountdownTime)
const char * LoadResStr(const char *id)
Definition: C4Language.h:83
void LobbyError(const char *szErrorMsg)
const C4Network2ResCore * getResCore() const
int32_t getID() const
Definition: C4Client.h:105
void AddCommand(const char *strCommand, const char *strScript)
bool StartTypeIn(bool fObjInput=false, C4Object *pObj=nullptr, bool fUpperCase=false, bool fTeam=false, int32_t iPlr=-1, const StdStrBuf &rsInputQuery=StdStrBuf())
C4Player * Get(int iPlayer) const
const char * getFile() const
C4GUIScreen * pGUI
Definition: C4Gui.cpp:1191
bool SaveScreenshot(bool fSaveAll, float fSaveAllZoom)
void StoreBackBuffer(const char *szMessage)
int32_t getID() const
Definition: C4Network2Res.h:86
bool isNetwork() const
Definition: C4GameControl.h:97
C4Network2 Network
Definition: C4Globals.cpp:53
bool IsOpen() const
Definition: C4Group.cpp:1891
char TodoFilename[CFG_MaxString+1]
Definition: C4Config.h:84
bool InsertText(const char *szText, bool fUser)
Definition: C4GuiEdit.cpp:163
const char * LoadResStrNoAmp(const char *id)
Definition: StdResStr2.cpp:23
int32_t FrameSkip
Definition: C4Game.h:137
class C4Object * GetScriptTargetObject() const
char UserDataPath[CFG_MaxString+1]
Definition: C4Config.h:54
bool isLobbyCountDown()
Definition: C4Network2.h:297
StdStrBuf GetFullName() const
Definition: C4Group.cpp:2078
bool IsRepeated() const
void InvalidateReference()
void CompileFunc(StdCompiler *pComp)
C4GameControl Control
C4ConfigLobby Lobby
Definition: C4Config.h:256
bool Append(const char *szFilename, bool text=false)
Definition: CStdFile.cpp:132
C4GraphicsSystem GraphicsSystem
Definition: C4Globals.cpp:51
static bool IsShown()
int32_t GetClientID() const
Definition: C4PlayerInfo.h:257
void DoInput(C4PacketType eCtrlType, C4ControlPacket *pPkt, C4ControlDeliveryType eDelivery)
C4Group ScenarioFile
Definition: C4Game.h:86
C4PlayerList Players
bool C4Group_CopyItem(const char *szSource, const char *szTarget1, bool fNoSort, bool fResetAttributes)
Definition: C4Group.cpp:100
C4ConfigNetwork Network
Definition: C4Config.h:255
bool ProcessCommand(const char *szCommand)
C4ConfigDeveloper Developer
Definition: C4Config.h:252
C4Player * GetLocalByIndex(int iIndex) const
bool Key_IsGamepad(C4KeyCode key)
const unsigned int C4MaxName
const int NO_OWNER
Definition: C4Constants.h:138
void OnError(const char *szErrMsg)
void Start(int32_t iCountdownTime)
#define _MAX_FNAME
C4Network2Res::Ref getRefRes(int32_t iResID)
int32_t CountdownTime
Definition: C4Config.h:197
void Value(const T &rStruct)
Definition: StdCompiler.h:161
bool isEnabled() const
Definition: C4Network2.h:203
C4Network2ResList ResList
Definition: C4Network2.h:113
void AbortMsgBoardQuery(C4Object *pObj, int32_t iPlr)
C4KeyCode Key
C4Network2ClientList Clients
Definition: C4Network2.h:116
C4PlayerInfoList & PlayerInfos
Definition: C4Game.h:71
bool FullSpeed
Definition: C4Game.h:136
const char * GetName() const
Definition: C4Player.h:151
const int32_t C4MSGB_BackBufferMax
~C4ChatInputDialog() override
void SetOriginalColor(DWORD dwUseClr)
Definition: C4PlayerInfo.h:116
std::unique_ptr< C4MessageBoard > MessageBoard
const char * AtUserDataPath(const char *szFilename)
Definition: C4Config.cpp:526
bool WildcardMatch(const char *szWildcard, const char *szString)
Definition: StdFile.cpp:374
C4Network2Players Players
Definition: C4Network2.h:119
bool Log(const char *szMessage)
Definition: C4Log.cpp:192
C4Player * Next
Definition: C4Player.h:142
bool SEqual2(const char *szStr1, const char *szStr2)
Definition: Standard.cpp:176
C4ClientList & Clients
Definition: C4Game.h:69
bool isLeague() const
bool SEqual2NoCase(const char *szStr1, const char *szStr2, int iLen)
Definition: Standard.cpp:198
const C4KeyCode KEY_Any
bool ToggleChart()
Definition: C4Game.cpp:3660
bool ClearLog()
Definition: C4Console.cpp:667
C4GUI::Edit::InputResult OnChatInput(C4GUI::Edit *edt, bool fPasting, bool fPastingMore)
char AltTodoFilename[CFG_MaxString+1]
Definition: C4Config.h:85
bool IsPacked() const
Definition: C4Group.cpp:2009
char Name[C4MaxName+1]
int32_t GetID() const
Definition: C4PlayerInfo.h:194
C4PlayerInfo * GetPlayerInfoByID(int32_t id) const
C4Network2Client * GetLocal() const
int SCharPos(char cTarget, const char *szInStr, int iIndex)
Definition: Standard.cpp:211
C4SHead Head
Definition: C4Scenario.h:230
bool JoinLocalPlayer(const char *szLocalPlayerFilename, bool initial=false)
bool ItemExists(const char *szItemName)
Definition: StdFile.h:75
C4ChatInputDialog(bool fObjInput, C4Object *pScriptTarget, bool fUpperCase, bool fTeam, int32_t iPlr, const StdStrBuf &rsInputQuery)
const int C4MaxMessage
Definition: C4Constants.h:28
void OnClosed(bool fOK) override
bool WriteString(const char *szStr)
Definition: CStdFile.cpp:264
C4Player * GetByName(const char *szName, int iExcluding=NO_OWNER) const
bool KeyStartTypeIn(bool fTeam)
bool LogF(const char *strMessage,...)
Definition: C4Log.cpp:250
void Copy()
Definition: StdBuf.h:467
C4ControlMessageType
Definition: C4Control.h:505
void ClearPointers(C4Object *pObj)
void CopyValidated(const char *szFromVal)
C4Application Application
Definition: C4Globals.cpp:44
uint16_t WORD
void SCopyUntil(const char *szSource, char *sTarget, char cUntil, int iMaxL, int iIndex)
Definition: Standard.cpp:146
bool DebugMode
Definition: C4Game.h:144
bool MarkMessageBoardQueryAnswered(C4Object *pForObj)
Definition: C4Player.cpp:1686
class C4MessageBoardCommand * GetCommand(const char *strName)
int32_t Film
Definition: C4Scenario.h:73
int32_t GetScriptTargetPlayer() const
bool ProcessInput(const char *szText)
StdStrBuf FormatString(const char *szFmt,...)
Definition: StdBuf.cpp:270