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