OpenTTD
ai_gui.cpp
Go to the documentation of this file.
1 /* $Id: ai_gui.cpp 27187 2015-03-15 12:19:58Z frosch $ */
2 
3 /*
4  * This file is part of OpenTTD.
5  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8  */
9 
12 #include "../stdafx.h"
13 #include "../table/sprites.h"
14 #include "../error.h"
15 #include "../settings_gui.h"
16 #include "../querystring_gui.h"
17 #include "../stringfilter_type.h"
18 #include "../company_base.h"
19 #include "../company_gui.h"
20 #include "../strings_func.h"
21 #include "../window_func.h"
22 #include "../gfx_func.h"
23 #include "../command_func.h"
24 #include "../network/network.h"
25 #include "../settings_func.h"
26 #include "../network/network_content.h"
27 #include "../textfile_gui.h"
28 #include "../widgets/dropdown_type.h"
29 #include "../widgets/dropdown_func.h"
30 #include "../hotkeys.h"
31 
32 #include "ai.hpp"
33 #include "ai_gui.hpp"
34 #include "../script/api/script_log.hpp"
35 #include "ai_config.hpp"
36 #include "ai_info.hpp"
37 #include "ai_instance.hpp"
38 #include "../game/game.hpp"
39 #include "../game/game_config.hpp"
40 #include "../game/game_info.hpp"
41 #include "../game/game_instance.hpp"
42 
43 #include "table/strings.h"
44 
45 #include <vector>
46 
47 #include "../safeguards.h"
48 
49 static ScriptConfig *GetConfig(CompanyID slot)
50 {
51  if (slot == OWNER_DEITY) return GameConfig::GetConfig();
52  return AIConfig::GetConfig(slot);
53 }
54 
58 struct AIListWindow : public Window {
60  int selected;
64 
70  AIListWindow(WindowDesc *desc, CompanyID slot) : Window(desc),
71  slot(slot)
72  {
73  if (slot == OWNER_DEITY) {
75  } else {
77  }
78 
79  this->CreateNestedTree();
80  this->vscroll = this->GetScrollbar(WID_AIL_SCROLLBAR);
81  this->FinishInitNested(); // Initializes 'this->line_height' as side effect.
82 
83  this->vscroll->SetCount((int)this->info_list->size() + 1);
84 
85  /* Try if we can find the currently selected AI */
86  this->selected = -1;
87  if (GetConfig(slot)->HasScript()) {
88  ScriptInfo *info = GetConfig(slot)->GetInfo();
89  int i = 0;
90  for (ScriptInfoList::const_iterator it = this->info_list->begin(); it != this->info_list->end(); it++, i++) {
91  if ((*it).second == info) {
92  this->selected = i;
93  break;
94  }
95  }
96  }
97  }
98 
99  virtual void SetStringParameters(int widget) const
100  {
101  switch (widget) {
102  case WID_AIL_CAPTION:
103  SetDParam(0, (this->slot == OWNER_DEITY) ? STR_AI_LIST_CAPTION_GAMESCRIPT : STR_AI_LIST_CAPTION_AI);
104  break;
105  }
106  }
107 
108  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
109  {
110  if (widget == WID_AIL_LIST) {
112 
113  resize->width = 1;
114  resize->height = this->line_height;
115  size->height = 5 * this->line_height;
116  }
117  }
118 
119  virtual void DrawWidget(const Rect &r, int widget) const
120  {
121  switch (widget) {
122  case WID_AIL_LIST: {
123  /* Draw a list of all available AIs. */
124  int y = this->GetWidget<NWidgetBase>(WID_AIL_LIST)->pos_y;
125  /* First AI in the list is hardcoded to random */
126  if (this->vscroll->IsVisible(0)) {
127  DrawString(r.left + WD_MATRIX_LEFT, r.right - WD_MATRIX_LEFT, y + WD_MATRIX_TOP, this->slot == OWNER_DEITY ? STR_AI_CONFIG_NONE : STR_AI_CONFIG_RANDOM_AI, this->selected == -1 ? TC_WHITE : TC_ORANGE);
128  y += this->line_height;
129  }
130  ScriptInfoList::const_iterator it = this->info_list->begin();
131  for (int i = 1; it != this->info_list->end(); i++, it++) {
132  if (this->vscroll->IsVisible(i)) {
133  DrawString(r.left + WD_MATRIX_LEFT, r.right - WD_MATRIX_RIGHT, y + WD_MATRIX_TOP, (*it).second->GetName(), (this->selected == i - 1) ? TC_WHITE : TC_ORANGE);
134  y += this->line_height;
135  }
136  }
137  break;
138  }
139  case WID_AIL_INFO_BG: {
140  AIInfo *selected_info = NULL;
141  ScriptInfoList::const_iterator it = this->info_list->begin();
142  for (int i = 1; selected_info == NULL && it != this->info_list->end(); i++, it++) {
143  if (this->selected == i - 1) selected_info = static_cast<AIInfo *>((*it).second);
144  }
145  /* Some info about the currently selected AI. */
146  if (selected_info != NULL) {
147  int y = r.top + WD_FRAMERECT_TOP;
148  SetDParamStr(0, selected_info->GetAuthor());
149  DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, y, STR_AI_LIST_AUTHOR);
151  SetDParam(0, selected_info->GetVersion());
152  DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, y, STR_AI_LIST_VERSION);
154  if (selected_info->GetURL() != NULL) {
155  SetDParamStr(0, selected_info->GetURL());
156  DrawString(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, y, STR_AI_LIST_URL);
158  }
159  SetDParamStr(0, selected_info->GetDescription());
160  DrawStringMultiLine(r.left + WD_FRAMETEXT_LEFT, r.right - WD_FRAMETEXT_RIGHT, y, r.bottom - WD_FRAMERECT_BOTTOM, STR_JUST_RAW_STRING, TC_WHITE);
161  }
162  break;
163  }
164  }
165  }
166 
170  void ChangeAI()
171  {
172  if (this->selected == -1) {
173  GetConfig(slot)->Change(NULL);
174  } else {
175  ScriptInfoList::const_iterator it = this->info_list->begin();
176  for (int i = 0; i < this->selected; i++) it++;
177  GetConfig(slot)->Change((*it).second->GetName(), (*it).second->GetVersion());
178  }
182  }
183 
184  virtual void OnClick(Point pt, int widget, int click_count)
185  {
186  switch (widget) {
187  case WID_AIL_LIST: { // Select one of the AIs
188  int sel = this->vscroll->GetScrolledRowFromWidget(pt.y, this, WID_AIL_LIST, 0, this->line_height) - 1;
189  if (sel < (int)this->info_list->size()) {
190  this->selected = sel;
191  this->SetDirty();
192  if (click_count > 1) {
193  this->ChangeAI();
194  delete this;
195  }
196  }
197  break;
198  }
199 
200  case WID_AIL_ACCEPT: {
201  this->ChangeAI();
202  delete this;
203  break;
204  }
205 
206  case WID_AIL_CANCEL:
207  delete this;
208  break;
209  }
210  }
211 
212  virtual void OnResize()
213  {
215  }
216 
222  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
223  {
224  if (_game_mode == GM_NORMAL && Company::IsValidID(this->slot)) {
225  delete this;
226  return;
227  }
228 
229  if (!gui_scope) return;
230 
231  this->vscroll->SetCount((int)this->info_list->size() + 1);
232 
233  /* selected goes from -1 .. length of ai list - 1. */
234  this->selected = min(this->selected, this->vscroll->GetCount() - 2);
235  }
236 };
237 
241  NWidget(WWT_CLOSEBOX, COLOUR_MAUVE),
242  NWidget(WWT_CAPTION, COLOUR_MAUVE, WID_AIL_CAPTION), SetDataTip(STR_AI_LIST_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
243  NWidget(WWT_DEFSIZEBOX, COLOUR_MAUVE),
244  EndContainer(),
246  NWidget(WWT_MATRIX, COLOUR_MAUVE, WID_AIL_LIST), SetMinimalSize(188, 112), SetFill(1, 1), SetResize(1, 1), SetMatrixDataTip(1, 0, STR_AI_LIST_TOOLTIP), SetScrollbar(WID_AIL_SCROLLBAR),
247  NWidget(NWID_VSCROLLBAR, COLOUR_MAUVE, WID_AIL_SCROLLBAR),
248  EndContainer(),
250  EndContainer(),
253  NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_AIL_ACCEPT), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_AI_LIST_ACCEPT, STR_AI_LIST_ACCEPT_TOOLTIP),
254  NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_AIL_CANCEL), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_AI_LIST_CANCEL, STR_AI_LIST_CANCEL_TOOLTIP),
255  EndContainer(),
256  NWidget(WWT_RESIZEBOX, COLOUR_MAUVE),
257  EndContainer(),
258 };
259 
262  WDP_CENTER, "settings_script_list", 200, 234,
264  0,
265  _nested_ai_list_widgets, lengthof(_nested_ai_list_widgets)
266 );
267 
272 static void ShowAIListWindow(CompanyID slot)
273 {
275  new AIListWindow(&_ai_list_desc, slot);
276 }
277 
281 struct AISettingsWindow : public Window {
288  int timeout;
292  typedef std::vector<const ScriptConfigItem *> VisibleSettingsList;
293  VisibleSettingsList visible_settings;
294 
301  slot(slot),
302  clicked_button(-1),
303  clicked_dropdown(false),
304  closing_dropdown(false),
305  timeout(0)
306  {
307  this->ai_config = GetConfig(slot);
308  this->RebuildVisibleSettings();
309 
310  this->CreateNestedTree();
311  this->vscroll = this->GetScrollbar(WID_AIS_SCROLLBAR);
312  this->FinishInitNested(slot); // Initializes 'this->line_height' as side effect.
313 
314  this->SetWidgetDisabledState(WID_AIS_RESET, _game_mode != GM_MENU && Company::IsValidID(this->slot));
315 
316  this->vscroll->SetCount((int)this->visible_settings.size());
317  }
318 
319  virtual void SetStringParameters(int widget) const
320  {
321  switch (widget) {
322  case WID_AIS_CAPTION:
323  SetDParam(0, (this->slot == OWNER_DEITY) ? STR_AI_SETTINGS_CAPTION_GAMESCRIPT : STR_AI_SETTINGS_CAPTION_AI);
324  break;
325  }
326  }
327 
334  {
335  visible_settings.clear();
336 
337  ScriptConfigItemList::const_iterator it = this->ai_config->GetConfigList()->begin();
338  for (; it != this->ai_config->GetConfigList()->end(); it++) {
339  bool no_hide = (it->flags & SCRIPTCONFIG_DEVELOPER) == 0;
340  if (no_hide || _settings_client.gui.ai_developer_tools) {
341  visible_settings.push_back(&(*it));
342  }
343  }
344  }
345 
346  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
347  {
348  if (widget == WID_AIS_BACKGROUND) {
350 
351  resize->width = 1;
352  resize->height = this->line_height;
353  size->height = 5 * this->line_height;
354  }
355  }
356 
357  virtual void DrawWidget(const Rect &r, int widget) const
358  {
359  if (widget != WID_AIS_BACKGROUND) return;
360 
361  ScriptConfig *config = this->ai_config;
362  VisibleSettingsList::const_iterator it = this->visible_settings.begin();
363  int i = 0;
364  for (; !this->vscroll->IsVisible(i); i++) it++;
365 
366  bool rtl = _current_text_dir == TD_RTL;
367  uint buttons_left = rtl ? r.right - SETTING_BUTTON_WIDTH - 3 : r.left + 4;
368  uint text_left = r.left + (rtl ? WD_FRAMERECT_LEFT : SETTING_BUTTON_WIDTH + 8);
369  uint text_right = r.right - (rtl ? SETTING_BUTTON_WIDTH + 8 : WD_FRAMERECT_RIGHT);
370 
371 
372  int y = r.top;
373  int button_y_offset = (this->line_height - SETTING_BUTTON_HEIGHT) / 2;
374  int text_y_offset = (this->line_height - FONT_HEIGHT_NORMAL) / 2;
375  for (; this->vscroll->IsVisible(i) && it != visible_settings.end(); i++, it++) {
376  const ScriptConfigItem &config_item = **it;
377  int current_value = config->GetSetting((config_item).name);
378  bool editable = _game_mode == GM_MENU || ((this->slot != OWNER_DEITY) && !Company::IsValidID(this->slot)) || (config_item.flags & SCRIPTCONFIG_INGAME) != 0;
379 
380  StringID str;
381  TextColour colour;
382  uint idx = 0;
383  if (StrEmpty(config_item.description)) {
384  if (!strcmp(config_item.name, "start_date")) {
385  /* Build-in translation */
386  str = STR_AI_SETTINGS_START_DELAY;
387  colour = TC_LIGHT_BLUE;
388  } else {
389  str = STR_JUST_STRING;
390  colour = TC_ORANGE;
391  }
392  } else {
393  str = STR_AI_SETTINGS_SETTING;
394  colour = TC_LIGHT_BLUE;
395  SetDParamStr(idx++, config_item.description);
396  }
397 
398  if ((config_item.flags & SCRIPTCONFIG_BOOLEAN) != 0) {
399  DrawBoolButton(buttons_left, y + button_y_offset, current_value != 0, editable);
400  SetDParam(idx++, current_value == 0 ? STR_CONFIG_SETTING_OFF : STR_CONFIG_SETTING_ON);
401  } else {
402  if (config_item.complete_labels) {
403  DrawDropDownButton(buttons_left, y + button_y_offset, COLOUR_YELLOW, this->clicked_row == i && clicked_dropdown, editable);
404  } else {
405  DrawArrowButtons(buttons_left, y + button_y_offset, COLOUR_YELLOW, (this->clicked_button == i) ? 1 + (this->clicked_increase != rtl) : 0, editable && current_value > config_item.min_value, editable && current_value < config_item.max_value);
406  }
407  if (config_item.labels != NULL && config_item.labels->Contains(current_value)) {
408  SetDParam(idx++, STR_JUST_RAW_STRING);
409  SetDParamStr(idx++, config_item.labels->Find(current_value)->second);
410  } else {
411  SetDParam(idx++, STR_JUST_INT);
412  SetDParam(idx++, current_value);
413  }
414  }
415 
416  DrawString(text_left, text_right, y + text_y_offset, str, colour);
417  y += this->line_height;
418  }
419  }
420 
421  virtual void OnPaint()
422  {
423  if (this->closing_dropdown) {
424  this->closing_dropdown = false;
425  this->clicked_dropdown = false;
426  }
427  this->DrawWidgets();
428  }
429 
430  virtual void OnClick(Point pt, int widget, int click_count)
431  {
432  switch (widget) {
433  case WID_AIS_BACKGROUND: {
434  const NWidgetBase *wid = this->GetWidget<NWidgetBase>(WID_AIS_BACKGROUND);
435  int num = (pt.y - wid->pos_y) / this->line_height + this->vscroll->GetPosition();
436  if (num >= (int)this->visible_settings.size()) break;
437 
438  VisibleSettingsList::const_iterator it = this->visible_settings.begin();
439  for (int i = 0; i < num; i++) it++;
440  const ScriptConfigItem config_item = **it;
441  if (_game_mode == GM_NORMAL && ((this->slot == OWNER_DEITY) || Company::IsValidID(this->slot)) && (config_item.flags & SCRIPTCONFIG_INGAME) == 0) return;
442 
443  if (this->clicked_row != num) {
445  HideDropDownMenu(this);
446  this->clicked_row = num;
447  this->clicked_dropdown = false;
448  }
449 
450  bool bool_item = (config_item.flags & SCRIPTCONFIG_BOOLEAN) != 0;
451 
452  int x = pt.x - wid->pos_x;
453  if (_current_text_dir == TD_RTL) x = wid->current_x - 1 - x;
454  x -= 4;
455 
456  /* One of the arrows is clicked (or green/red rect in case of bool value) */
457  int old_val = this->ai_config->GetSetting(config_item.name);
458  if (!bool_item && IsInsideMM(x, 0, SETTING_BUTTON_WIDTH) && config_item.complete_labels) {
459  if (this->clicked_dropdown) {
460  /* unclick the dropdown */
461  HideDropDownMenu(this);
462  this->clicked_dropdown = false;
463  this->closing_dropdown = false;
464  } else {
465  const NWidgetBase *wid = this->GetWidget<NWidgetBase>(WID_AIS_BACKGROUND);
466  int rel_y = (pt.y - (int)wid->pos_y) % this->line_height;
467 
468  Rect wi_rect;
469  wi_rect.left = pt.x - (_current_text_dir == TD_RTL ? SETTING_BUTTON_WIDTH - 1 - x : x);
470  wi_rect.right = wi_rect.left + SETTING_BUTTON_WIDTH - 1;
471  wi_rect.top = pt.y - rel_y + (this->line_height - SETTING_BUTTON_HEIGHT) / 2;
472  wi_rect.bottom = wi_rect.top + SETTING_BUTTON_HEIGHT - 1;
473 
474  /* For dropdowns we also have to check the y position thoroughly, the mouse may not above the just opening dropdown */
475  if (pt.y >= wi_rect.top && pt.y <= wi_rect.bottom) {
476  this->clicked_dropdown = true;
477  this->closing_dropdown = false;
478 
479  DropDownList *list = new DropDownList();
480  for (int i = config_item.min_value; i <= config_item.max_value; i++) {
481  *list->Append() = new DropDownListCharStringItem(config_item.labels->Find(i)->second, i, false);
482  }
483 
484  ShowDropDownListAt(this, list, old_val, -1, wi_rect, COLOUR_ORANGE, true);
485  }
486  }
487  } else if (IsInsideMM(x, 0, SETTING_BUTTON_WIDTH)) {
488  int new_val = old_val;
489  if (bool_item) {
490  new_val = !new_val;
491  } else if (x >= SETTING_BUTTON_WIDTH / 2) {
492  /* Increase button clicked */
493  new_val += config_item.step_size;
494  if (new_val > config_item.max_value) new_val = config_item.max_value;
495  this->clicked_increase = true;
496  } else {
497  /* Decrease button clicked */
498  new_val -= config_item.step_size;
499  if (new_val < config_item.min_value) new_val = config_item.min_value;
500  this->clicked_increase = false;
501  }
502 
503  if (new_val != old_val) {
504  this->ai_config->SetSetting(config_item.name, new_val);
505  this->clicked_button = num;
506  this->timeout = 5;
507  }
508  } else if (!bool_item && !config_item.complete_labels) {
509  /* Display a query box so users can enter a custom value. */
510  SetDParam(0, old_val);
511  ShowQueryString(STR_JUST_INT, STR_CONFIG_SETTING_QUERY_CAPTION, 10, this, CS_NUMERAL, QSF_NONE);
512  }
513  this->SetDirty();
514  break;
515  }
516 
517  case WID_AIS_ACCEPT:
518  delete this;
519  break;
520 
521  case WID_AIS_RESET:
522  if (_game_mode == GM_MENU || !Company::IsValidID(this->slot)) {
523  this->ai_config->ResetSettings();
524  this->SetDirty();
525  }
526  break;
527  }
528  }
529 
530  virtual void OnQueryTextFinished(char *str)
531  {
532  if (StrEmpty(str)) return;
533  ScriptConfigItemList::const_iterator it = this->ai_config->GetConfigList()->begin();
534  for (int i = 0; i < this->clicked_row; i++) it++;
535  if (_game_mode == GM_NORMAL && ((this->slot == OWNER_DEITY) || Company::IsValidID(this->slot)) && (it->flags & SCRIPTCONFIG_INGAME) == 0) return;
536  int32 value = atoi(str);
537  this->ai_config->SetSetting((*it).name, value);
538  this->SetDirty();
539  }
540 
541  virtual void OnDropdownSelect(int widget, int index)
542  {
543  assert(this->clicked_dropdown);
544  ScriptConfigItemList::const_iterator it = this->ai_config->GetConfigList()->begin();
545  for (int i = 0; i < this->clicked_row; i++) it++;
546  if (_game_mode == GM_NORMAL && ((this->slot == OWNER_DEITY) || Company::IsValidID(this->slot)) && (it->flags & SCRIPTCONFIG_INGAME) == 0) return;
547  this->ai_config->SetSetting((*it).name, index);
548  this->SetDirty();
549  }
550 
551  virtual void OnDropdownClose(Point pt, int widget, int index, bool instant_close)
552  {
553  /* We cannot raise the dropdown button just yet. OnClick needs some hint, whether
554  * the same dropdown button was clicked again, and then not open the dropdown again.
555  * So, we only remember that it was closed, and process it on the next OnPaint, which is
556  * after OnClick. */
557  assert(this->clicked_dropdown);
558  this->closing_dropdown = true;
559  this->SetDirty();
560  }
561 
562  virtual void OnResize()
563  {
565  }
566 
567  virtual void OnTick()
568  {
569  if (--this->timeout == 0) {
570  this->clicked_button = -1;
571  this->SetDirty();
572  }
573  }
574 
580  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
581  {
582  this->RebuildVisibleSettings();
583  }
584 };
585 
589  NWidget(WWT_CLOSEBOX, COLOUR_MAUVE),
590  NWidget(WWT_CAPTION, COLOUR_MAUVE, WID_AIS_CAPTION), SetDataTip(STR_AI_SETTINGS_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
591  NWidget(WWT_DEFSIZEBOX, COLOUR_MAUVE),
592  EndContainer(),
594  NWidget(WWT_MATRIX, COLOUR_MAUVE, WID_AIS_BACKGROUND), SetMinimalSize(188, 182), SetResize(1, 1), SetFill(1, 0), SetMatrixDataTip(1, 0, STR_NULL), SetScrollbar(WID_AIS_SCROLLBAR),
595  NWidget(NWID_VSCROLLBAR, COLOUR_MAUVE, WID_AIS_SCROLLBAR),
596  EndContainer(),
599  NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_AIS_ACCEPT), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_AI_SETTINGS_CLOSE, STR_NULL),
600  NWidget(WWT_PUSHTXTBTN, COLOUR_MAUVE, WID_AIS_RESET), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_AI_SETTINGS_RESET, STR_NULL),
601  EndContainer(),
602  NWidget(WWT_RESIZEBOX, COLOUR_MAUVE),
603  EndContainer(),
604 };
605 
608  WDP_CENTER, "settings_script", 500, 208,
610  0,
611  _nested_ai_settings_widgets, lengthof(_nested_ai_settings_widgets)
612 );
613 
619 {
623 }
624 
625 
629 
630  ScriptTextfileWindow(TextfileType file_type, CompanyID slot) : TextfileWindow(file_type), slot(slot)
631  {
632  const char *textfile = GetConfig(slot)->GetTextfile(file_type, slot);
633  this->LoadTextfile(textfile, (slot == OWNER_DEITY) ? GAME_DIR : AI_DIR);
634  }
635 
636  /* virtual */ void SetStringParameters(int widget) const
637  {
638  if (widget == WID_TF_CAPTION) {
639  SetDParam(0, (slot == OWNER_DEITY) ? STR_CONTENT_TYPE_GAME_SCRIPT : STR_CONTENT_TYPE_AI);
640  SetDParamStr(1, GetConfig(slot)->GetName());
641  }
642  }
643 };
644 
651 {
653  new ScriptTextfileWindow(file_type, slot);
654 }
655 
656 
660  NWidget(WWT_CLOSEBOX, COLOUR_MAUVE),
661  NWidget(WWT_CAPTION, COLOUR_MAUVE), SetDataTip(STR_AI_CONFIG_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
662  EndContainer(),
663  NWidget(WWT_PANEL, COLOUR_MAUVE, WID_AIC_BACKGROUND),
664  NWidget(NWID_VERTICAL), SetPIP(4, 4, 4),
665  NWidget(NWID_HORIZONTAL), SetPIP(7, 0, 7),
666  NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_AIC_DECREASE), SetFill(0, 1), SetDataTip(AWV_DECREASE, STR_NULL),
667  NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_AIC_INCREASE), SetFill(0, 1), SetDataTip(AWV_INCREASE, STR_NULL),
669  NWidget(WWT_TEXT, COLOUR_MAUVE, WID_AIC_NUMBER), SetDataTip(STR_DIFFICULTY_LEVEL_SETTING_MAXIMUM_NO_COMPETITORS, STR_NULL), SetFill(1, 0), SetPadding(1, 0, 0, 0),
670  EndContainer(),
672  NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_MOVE_UP), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_AI_CONFIG_MOVE_UP, STR_AI_CONFIG_MOVE_UP_TOOLTIP),
673  NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_MOVE_DOWN), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_AI_CONFIG_MOVE_DOWN, STR_AI_CONFIG_MOVE_DOWN_TOOLTIP),
674  EndContainer(),
675  EndContainer(),
676  NWidget(WWT_FRAME, COLOUR_MAUVE), SetDataTip(STR_AI_CONFIG_AI, STR_NULL), SetPadding(0, 5, 0, 5),
678  NWidget(WWT_MATRIX, COLOUR_MAUVE, WID_AIC_LIST), SetMinimalSize(288, 112), SetFill(1, 0), SetMatrixDataTip(1, 8, STR_AI_CONFIG_AILIST_TOOLTIP), SetScrollbar(WID_AIC_SCROLLBAR),
679  NWidget(NWID_VSCROLLBAR, COLOUR_MAUVE, WID_AIC_SCROLLBAR),
680  EndContainer(),
681  EndContainer(),
683  NWidget(WWT_FRAME, COLOUR_MAUVE), SetDataTip(STR_AI_CONFIG_GAMESCRIPT, STR_NULL), SetPadding(0, 5, 4, 5),
684  NWidget(WWT_MATRIX, COLOUR_MAUVE, WID_AIC_GAMELIST), SetMinimalSize(288, 14), SetFill(1, 0), SetMatrixDataTip(1, 1, STR_AI_CONFIG_GAMELIST_TOOLTIP),
685  EndContainer(),
687  NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_CHANGE), SetFill(1, 0), SetMinimalSize(93, 12), SetDataTip(STR_AI_CONFIG_CHANGE, STR_AI_CONFIG_CHANGE_TOOLTIP),
688  NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_CONFIGURE), SetFill(1, 0), SetMinimalSize(93, 12), SetDataTip(STR_AI_CONFIG_CONFIGURE, STR_AI_CONFIG_CONFIGURE_TOOLTIP),
689  NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_CLOSE), SetFill(1, 0), SetMinimalSize(93, 12), SetDataTip(STR_AI_SETTINGS_CLOSE, STR_NULL),
690  EndContainer(),
692  NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_TEXTFILE + TFT_README), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_README, STR_NULL),
693  NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_TEXTFILE + TFT_CHANGELOG), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_CHANGELOG, STR_NULL),
694  NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_TEXTFILE + TFT_LICENSE), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_LICENCE, STR_NULL),
695  EndContainer(),
696  NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_CONTENT_DOWNLOAD), SetFill(1, 0), SetMinimalSize(279, 12), SetPadding(0, 7, 9, 7), SetDataTip(STR_INTRO_ONLINE_CONTENT, STR_INTRO_TOOLTIP_ONLINE_CONTENT),
697  EndContainer(),
698 };
699 
702  WDP_CENTER, "settings_script_config", 0, 0,
704  0,
705  _nested_ai_config_widgets, lengthof(_nested_ai_config_widgets)
706 );
707 
711 struct AIConfigWindow : public Window {
715 
717  {
718  this->InitNested(WN_GAME_OPTIONS_AI); // Initializes 'this->line_height' as a side effect.
719  this->vscroll = this->GetScrollbar(WID_AIC_SCROLLBAR);
721  NWidgetCore *nwi = this->GetWidget<NWidgetCore>(WID_AIC_LIST);
722  this->vscroll->SetCapacity(nwi->current_y / this->line_height);
724  this->OnInvalidateData(0);
725  }
726 
727  ~AIConfigWindow()
728  {
731  }
732 
733  virtual void SetStringParameters(int widget) const
734  {
735  switch (widget) {
736  case WID_AIC_NUMBER:
737  SetDParam(0, GetGameSettings().difficulty.max_no_competitors);
738  break;
739  case WID_AIC_CHANGE:
740  switch (selected_slot) {
741  case OWNER_DEITY:
742  SetDParam(0, STR_AI_CONFIG_CHANGE_GAMESCRIPT);
743  break;
744 
745  case INVALID_COMPANY:
746  SetDParam(0, STR_AI_CONFIG_CHANGE_NONE);
747  break;
748 
749  default:
750  SetDParam(0, STR_AI_CONFIG_CHANGE_AI);
751  break;
752  }
753  break;
754  }
755  }
756 
757  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
758  {
759  switch (widget) {
760  case WID_AIC_GAMELIST:
762  size->height = 1 * this->line_height;
763  break;
764 
765  case WID_AIC_LIST:
767  size->height = 8 * this->line_height;
768  break;
769  }
770  }
771 
777  static bool IsEditable(CompanyID slot)
778  {
779  if (slot == OWNER_DEITY) return _game_mode != GM_NORMAL || Game::GetInstance() != NULL;
780 
781  if (_game_mode != GM_NORMAL) {
782  return slot > 0 && slot <= GetGameSettings().difficulty.max_no_competitors;
783  }
784  if (Company::IsValidID(slot) || slot < 0) return false;
785 
787  for (CompanyID cid = COMPANY_FIRST; cid < (CompanyID)max_slot && cid < MAX_COMPANIES; cid++) {
788  if (Company::IsValidHumanID(cid)) max_slot++;
789  }
790  return slot < max_slot;
791  }
792 
793  virtual void DrawWidget(const Rect &r, int widget) const
794  {
795  switch (widget) {
796  case WID_AIC_GAMELIST: {
797  StringID text = STR_AI_CONFIG_NONE;
798 
799  if (GameConfig::GetConfig()->GetInfo() != NULL) {
800  SetDParamStr(0, GameConfig::GetConfig()->GetInfo()->GetName());
801  text = STR_JUST_RAW_STRING;
802  }
803 
804  DrawString(r.left + 10, r.right - 10, r.top + WD_MATRIX_TOP, text,
805  (this->selected_slot == OWNER_DEITY) ? TC_WHITE : (IsEditable(OWNER_DEITY) ? TC_ORANGE : TC_SILVER));
806 
807  break;
808  }
809 
810  case WID_AIC_LIST: {
811  int y = r.top;
812  for (int i = this->vscroll->GetPosition(); this->vscroll->IsVisible(i) && i < MAX_COMPANIES; i++) {
813  StringID text;
814 
815  if ((_game_mode != GM_NORMAL && i == 0) || (_game_mode == GM_NORMAL && Company::IsValidHumanID(i))) {
816  text = STR_AI_CONFIG_HUMAN_PLAYER;
817  } else if (AIConfig::GetConfig((CompanyID)i)->GetInfo() != NULL) {
818  SetDParamStr(0, AIConfig::GetConfig((CompanyID)i)->GetInfo()->GetName());
819  text = STR_JUST_RAW_STRING;
820  } else {
821  text = STR_AI_CONFIG_RANDOM_AI;
822  }
823  DrawString(r.left + 10, r.right - 10, y + WD_MATRIX_TOP, text,
824  (this->selected_slot == i) ? TC_WHITE : (IsEditable((CompanyID)i) ? TC_ORANGE : TC_SILVER));
825  y += this->line_height;
826  }
827  break;
828  }
829  }
830  }
831 
832  virtual void OnClick(Point pt, int widget, int click_count)
833  {
834  if (widget >= WID_AIC_TEXTFILE && widget < WID_AIC_TEXTFILE + TFT_END) {
835  if (this->selected_slot == INVALID_COMPANY || GetConfig(this->selected_slot) == NULL) return;
836 
838  return;
839  }
840 
841  switch (widget) {
842  case WID_AIC_DECREASE:
843  case WID_AIC_INCREASE: {
844  int new_value;
845  if (widget == WID_AIC_DECREASE) {
846  new_value = max(0, GetGameSettings().difficulty.max_no_competitors - 1);
847  } else {
848  new_value = min(MAX_COMPANIES - 1, GetGameSettings().difficulty.max_no_competitors + 1);
849  }
850  IConsoleSetSetting("difficulty.max_no_competitors", new_value);
851  this->InvalidateData();
852  break;
853  }
854 
855  case WID_AIC_GAMELIST: {
856  this->selected_slot = OWNER_DEITY;
857  this->InvalidateData();
858  if (click_count > 1 && this->selected_slot != INVALID_COMPANY && _game_mode != GM_NORMAL) ShowAIListWindow((CompanyID)this->selected_slot);
859  break;
860  }
861 
862  case WID_AIC_LIST: { // Select a slot
863  this->selected_slot = (CompanyID)this->vscroll->GetScrolledRowFromWidget(pt.y, this, widget, 0, this->line_height);
864  this->InvalidateData();
865  if (click_count > 1 && this->selected_slot != INVALID_COMPANY) ShowAIListWindow((CompanyID)this->selected_slot);
866  break;
867  }
868 
869  case WID_AIC_MOVE_UP:
870  if (IsEditable(this->selected_slot) && IsEditable((CompanyID)(this->selected_slot - 1))) {
871  Swap(GetGameSettings().ai_config[this->selected_slot], GetGameSettings().ai_config[this->selected_slot - 1]);
872  this->selected_slot--;
873  this->vscroll->ScrollTowards(this->selected_slot);
874  this->InvalidateData();
875  }
876  break;
877 
878  case WID_AIC_MOVE_DOWN:
879  if (IsEditable(this->selected_slot) && IsEditable((CompanyID)(this->selected_slot + 1))) {
880  Swap(GetGameSettings().ai_config[this->selected_slot], GetGameSettings().ai_config[this->selected_slot + 1]);
881  this->selected_slot++;
882  this->vscroll->ScrollTowards(this->selected_slot);
883  this->InvalidateData();
884  }
885  break;
886 
887  case WID_AIC_CHANGE: // choose other AI
889  break;
890 
891  case WID_AIC_CONFIGURE: // change the settings for an AI
892  ShowAISettingsWindow((CompanyID)this->selected_slot);
893  break;
894 
895  case WID_AIC_CLOSE:
896  delete this;
897  break;
898 
900  if (!_network_available) {
901  ShowErrorMessage(STR_NETWORK_ERROR_NOTAVAILABLE, INVALID_STRING_ID, WL_ERROR);
902  } else {
903 #if defined(ENABLE_NETWORK)
906 #endif
907  }
908  break;
909  }
910  }
911 
917  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
918  {
919  if (!IsEditable(this->selected_slot)) {
921  }
922 
923  if (!gui_scope) return;
924 
925  this->SetWidgetDisabledState(WID_AIC_DECREASE, GetGameSettings().difficulty.max_no_competitors == 0);
926  this->SetWidgetDisabledState(WID_AIC_INCREASE, GetGameSettings().difficulty.max_no_competitors == MAX_COMPANIES - 1);
927  this->SetWidgetDisabledState(WID_AIC_CHANGE, (this->selected_slot == OWNER_DEITY && _game_mode == GM_NORMAL) || this->selected_slot == INVALID_COMPANY);
928  this->SetWidgetDisabledState(WID_AIC_CONFIGURE, this->selected_slot == INVALID_COMPANY || GetConfig(this->selected_slot)->GetConfigList()->size() == 0);
931 
932  for (TextfileType tft = TFT_BEGIN; tft < TFT_END; tft++) {
933  this->SetWidgetDisabledState(WID_AIC_TEXTFILE + tft, this->selected_slot == INVALID_COMPANY || (GetConfig(this->selected_slot)->GetTextfile(tft, this->selected_slot) == NULL));
934  }
935  }
936 };
937 
940 {
942  new AIConfigWindow();
943 }
944 
953 static bool SetScriptButtonColour(NWidgetCore &button, bool dead, bool paused)
954 {
955  /* Dead scripts are indicated with red background and
956  * paused scripts are indicated with yellow background. */
957  Colours colour = dead ? COLOUR_RED :
958  (paused ? COLOUR_YELLOW : COLOUR_GREY);
959  if (button.colour != colour) {
960  button.colour = colour;
961  return true;
962  }
963  return false;
964 }
965 
969 struct AIDebugWindow : public Window {
970  static const int top_offset;
971  static const int bottom_offset;
972 
973  static const uint MAX_BREAK_STR_STRING_LENGTH = 256;
974 
978  bool autoscroll;
980  static bool break_check_enabled;
987 
988  ScriptLog::LogData *GetLogPointer() const
989  {
990  if (ai_debug_company == OWNER_DEITY) return (ScriptLog::LogData *)Game::GetInstance()->GetLogPointer();
991  return (ScriptLog::LogData *)Company::Get(ai_debug_company)->ai_instance->GetLogPointer();
992  }
993 
998  bool IsDead() const
999  {
1000  if (ai_debug_company == OWNER_DEITY) {
1001  GameInstance *game = Game::GetInstance();
1002  return game == NULL || game->IsDead();
1003  }
1004  return !Company::IsValidAiID(ai_debug_company) || Company::Get(ai_debug_company)->ai_instance->IsDead();
1005  }
1006 
1012  bool IsValidDebugCompany(CompanyID company) const
1013  {
1014  switch (company) {
1015  case INVALID_COMPANY: return false;
1016  case OWNER_DEITY: return Game::GetInstance() != NULL;
1017  default: return Company::IsValidAiID(company);
1018  }
1019  }
1020 
1026  {
1027  /* Check if the currently selected company is still active. */
1028  if (this->IsValidDebugCompany(ai_debug_company)) return;
1029 
1031 
1032  const Company *c;
1033  FOR_ALL_COMPANIES(c) {
1034  if (c->is_ai) {
1035  ChangeToAI(c->index);
1036  return;
1037  }
1038  }
1039 
1040  /* If no AI is available, see if there is a game script. */
1041  if (Game::GetInstance() != NULL) ChangeToAI(OWNER_DEITY);
1042  }
1043 
1050  {
1051  this->CreateNestedTree();
1052  this->vscroll = this->GetScrollbar(WID_AID_SCROLLBAR);
1054  this->GetWidget<NWidgetStacked>(WID_AID_BREAK_STRING_WIDGETS)->SetDisplayedPlane(this->show_break_box ? 0 : SZSP_HORIZONTAL);
1055  this->FinishInitNested(number);
1056 
1057  if (!this->show_break_box) break_check_enabled = false;
1058 
1059  this->last_vscroll_pos = 0;
1060  this->autoscroll = true;
1061  this->highlight_row = -1;
1062 
1064 
1066 
1067  /* Restore the break string value from static variable */
1068  this->break_editbox.text.Assign(this->break_string);
1069 
1070  this->SelectValidDebugCompany();
1071  this->InvalidateData(-1);
1072  }
1073 
1074  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
1075  {
1076  if (widget == WID_AID_LOG_PANEL) {
1077  resize->height = FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL;
1078  size->height = 14 * resize->height + this->top_offset + this->bottom_offset;
1079  }
1080  }
1081 
1082  virtual void OnPaint()
1083  {
1084  this->SelectValidDebugCompany();
1085 
1086  /* Draw standard stuff */
1087  this->DrawWidgets();
1088 
1089  if (this->IsShaded()) return; // Don't draw anything when the window is shaded.
1090 
1091  bool dirty = false;
1092 
1093  /* Paint the company icons */
1094  for (CompanyID i = COMPANY_FIRST; i < MAX_COMPANIES; i++) {
1095  NWidgetCore *button = this->GetWidget<NWidgetCore>(i + WID_AID_COMPANY_BUTTON_START);
1096 
1097  bool valid = Company::IsValidAiID(i);
1098 
1099  /* Check whether the validity of the company changed */
1100  dirty |= (button->IsDisabled() == valid);
1101 
1102  /* Mark dead/paused AIs by setting the background colour. */
1103  bool dead = valid && Company::Get(i)->ai_instance->IsDead();
1104  bool paused = valid && Company::Get(i)->ai_instance->IsPaused();
1105  /* Re-paint if the button was updated.
1106  * (note that it is intentional that SetScriptButtonColour is always called) */
1107  dirty |= SetScriptButtonColour(*button, dead, paused);
1108 
1109  /* Draw company icon only for valid AI companies */
1110  if (!valid) continue;
1111 
1112  byte offset = (i == ai_debug_company) ? 1 : 0;
1113  DrawCompanyIcon(i, button->pos_x + button->current_x / 2 - 7 + offset, this->GetWidget<NWidgetBase>(WID_AID_COMPANY_BUTTON_START + i)->pos_y + 2 + offset);
1114  }
1115 
1116  /* Set button colour for Game Script. */
1117  GameInstance *game = Game::GetInstance();
1118  bool valid = game != NULL;
1119  bool dead = valid && game->IsDead();
1120  bool paused = valid && game->IsPaused();
1121 
1122  NWidgetCore *button = this->GetWidget<NWidgetCore>(WID_AID_SCRIPT_GAME);
1123  dirty |= (button->IsDisabled() == valid) || SetScriptButtonColour(*button, dead, paused);
1124 
1125  if (dirty) this->InvalidateData(-1);
1126 
1127  /* If there are no active companies, don't display anything else. */
1128  if (ai_debug_company == INVALID_COMPANY) return;
1129 
1130  ScriptLog::LogData *log = this->GetLogPointer();
1131 
1132  int scroll_count = (log == NULL) ? 0 : log->used;
1133  if (this->vscroll->GetCount() != scroll_count) {
1134  this->vscroll->SetCount(scroll_count);
1135 
1136  /* We need a repaint */
1138  }
1139 
1140  if (log == NULL) return;
1141 
1142  /* Detect when the user scrolls the window. Enable autoscroll when the
1143  * bottom-most line becomes visible. */
1144  if (this->last_vscroll_pos != this->vscroll->GetPosition()) {
1145  this->autoscroll = this->vscroll->GetPosition() >= log->used - this->vscroll->GetCapacity();
1146  }
1147  if (this->autoscroll) {
1148  int scroll_pos = max(0, log->used - this->vscroll->GetCapacity());
1149  if (scroll_pos != this->vscroll->GetPosition()) {
1150  this->vscroll->SetPosition(scroll_pos);
1151 
1152  /* We need a repaint */
1155  }
1156  }
1157  this->last_vscroll_pos = this->vscroll->GetPosition();
1158  }
1159 
1160  virtual void SetStringParameters(int widget) const
1161  {
1162  switch (widget) {
1163  case WID_AID_NAME_TEXT:
1164  if (ai_debug_company == OWNER_DEITY) {
1165  const GameInfo *info = Game::GetInfo();
1166  assert(info != NULL);
1167  SetDParam(0, STR_AI_DEBUG_NAME_AND_VERSION);
1168  SetDParamStr(1, info->GetName());
1169  SetDParam(2, info->GetVersion());
1171  SetDParam(0, STR_EMPTY);
1172  } else {
1173  const AIInfo *info = Company::Get(ai_debug_company)->ai_info;
1174  assert(info != NULL);
1175  SetDParam(0, STR_AI_DEBUG_NAME_AND_VERSION);
1176  SetDParamStr(1, info->GetName());
1177  SetDParam(2, info->GetVersion());
1178  }
1179  break;
1180  }
1181  }
1182 
1183  virtual void DrawWidget(const Rect &r, int widget) const
1184  {
1185  if (ai_debug_company == INVALID_COMPANY) return;
1186 
1187  switch (widget) {
1188  case WID_AID_LOG_PANEL: {
1189  ScriptLog::LogData *log = this->GetLogPointer();
1190  if (log == NULL) return;
1191 
1192  int y = this->top_offset;
1193  for (int i = this->vscroll->GetPosition(); this->vscroll->IsVisible(i) && i < log->used; i++) {
1194  int pos = (i + log->pos + 1 - log->used + log->count) % log->count;
1195  if (log->lines[pos] == NULL) break;
1196 
1197  TextColour colour;
1198  switch (log->type[pos]) {
1199  case ScriptLog::LOG_SQ_INFO: colour = TC_BLACK; break;
1200  case ScriptLog::LOG_SQ_ERROR: colour = TC_RED; break;
1201  case ScriptLog::LOG_INFO: colour = TC_BLACK; break;
1202  case ScriptLog::LOG_WARNING: colour = TC_YELLOW; break;
1203  case ScriptLog::LOG_ERROR: colour = TC_RED; break;
1204  default: colour = TC_BLACK; break;
1205  }
1206 
1207  /* Check if the current line should be highlighted */
1208  if (pos == this->highlight_row) {
1209  GfxFillRect(r.left + 1, r.top + y, r.right - 1, r.top + y + this->resize.step_height - WD_PAR_VSEP_NORMAL, PC_BLACK);
1210  if (colour == TC_BLACK) colour = TC_WHITE; // Make black text readable by inverting it to white.
1211  }
1212 
1213  DrawString(r.left + 7, r.right - 7, r.top + y, log->lines[pos], colour, SA_LEFT | SA_FORCE);
1214  y += this->resize.step_height;
1215  }
1216  break;
1217  }
1218  }
1219  }
1220 
1225  void ChangeToAI(CompanyID show_ai)
1226  {
1227  if (!this->IsValidDebugCompany(show_ai)) return;
1228 
1229  ai_debug_company = show_ai;
1230 
1231  this->highlight_row = -1; // The highlight of one AI make little sense for another AI.
1232 
1233  /* Close AI settings window to prevent confusion */
1235 
1236  this->InvalidateData(-1);
1237 
1238  this->autoscroll = true;
1239  this->last_vscroll_pos = this->vscroll->GetPosition();
1240  }
1241 
1242  virtual void OnClick(Point pt, int widget, int click_count)
1243  {
1244  /* Also called for hotkeys, so check for disabledness */
1245  if (this->IsWidgetDisabled(widget)) return;
1246 
1247  /* Check which button is clicked */
1250  }
1251 
1252  switch (widget) {
1253  case WID_AID_SCRIPT_GAME:
1255  break;
1256 
1257  case WID_AID_RELOAD_TOGGLE:
1258  if (ai_debug_company == OWNER_DEITY) break;
1259  /* First kill the company of the AI, then start a new one. This should start the current AI again */
1261  DoCommandP(0, 1 | ai_debug_company << 16, 0, CMD_COMPANY_CTRL);
1262  break;
1263 
1264  case WID_AID_SETTINGS:
1266  break;
1267 
1270  this->InvalidateData(-1);
1271  break;
1272 
1275  this->InvalidateData(-1);
1276  break;
1277 
1278  case WID_AID_CONTINUE_BTN:
1279  /* Unpause current AI / game script and mark the corresponding script button dirty. */
1280  if (!this->IsDead()) {
1281  if (ai_debug_company == OWNER_DEITY) {
1282  Game::Unpause();
1283  } else {
1285  }
1286  }
1287 
1288  /* If the last AI/Game Script is unpaused, unpause the game too. */
1289  if ((_pause_mode & PM_PAUSED_NORMAL) == PM_PAUSED_NORMAL) {
1290  bool all_unpaused = !Game::IsPaused();
1291  if (all_unpaused) {
1292  Company *c;
1293  FOR_ALL_COMPANIES(c) {
1294  if (c->is_ai && AI::IsPaused(c->index)) {
1295  all_unpaused = false;
1296  break;
1297  }
1298  }
1299  if (all_unpaused) {
1300  /* All scripts have been unpaused => unpause the game. */
1301  DoCommandP(0, PM_PAUSED_NORMAL, 0, CMD_PAUSE);
1302  }
1303  }
1304  }
1305 
1306  this->highlight_row = -1;
1307  this->InvalidateData(-1);
1308  break;
1309  }
1310  }
1311 
1312  virtual void OnEditboxChanged(int wid)
1313  {
1314  if (wid == WID_AID_BREAK_STR_EDIT_BOX) {
1315  /* Save the current string to static member so it can be restored next time the window is opened. */
1316  strecpy(this->break_string, this->break_editbox.text.buf, lastof(this->break_string));
1318  }
1319  }
1320 
1327  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
1328  {
1329  /* If the log message is related to the active company tab, check the break string.
1330  * This needs to be done in gameloop-scope, so the AI is suspended immediately. */
1331  if (!gui_scope && data == ai_debug_company && this->IsValidDebugCompany(ai_debug_company) && this->break_check_enabled && !this->break_string_filter.IsEmpty()) {
1332  /* Get the log instance of the active company */
1333  ScriptLog::LogData *log = this->GetLogPointer();
1334 
1335  if (log != NULL) {
1337  this->break_string_filter.AddLine(log->lines[log->pos]);
1338  if (this->break_string_filter.GetState()) {
1339  /* Pause execution of script. */
1340  if (!this->IsDead()) {
1341  if (ai_debug_company == OWNER_DEITY) {
1342  Game::Pause();
1343  } else {
1345  }
1346  }
1347 
1348  /* Pause the game. */
1350  DoCommandP(0, PM_PAUSED_NORMAL, 1, CMD_PAUSE);
1351  }
1352 
1353  /* Highlight row that matched */
1354  this->highlight_row = log->pos;
1355  }
1356  }
1357  }
1358 
1359  if (!gui_scope) return;
1360 
1361  this->SelectValidDebugCompany();
1362 
1363  ScriptLog::LogData *log = ai_debug_company != INVALID_COMPANY ? this->GetLogPointer() : NULL;
1364  this->vscroll->SetCount((log == NULL) ? 0 : log->used);
1365 
1366  /* Update company buttons */
1367  for (CompanyID i = COMPANY_FIRST; i < MAX_COMPANIES; i++) {
1370  }
1371 
1374 
1377 
1382  }
1383 
1384  virtual void OnResize()
1385  {
1387  }
1388 
1389  static HotkeyList hotkeys;
1390 };
1391 
1395 char AIDebugWindow::break_string[MAX_BREAK_STR_STRING_LENGTH] = "";
1398 StringFilter AIDebugWindow::break_string_filter(&AIDebugWindow::case_sensitive_break_check);
1399 
1402 {
1403  return MakeCompanyButtonRows(biggest_index, WID_AID_COMPANY_BUTTON_START, WID_AID_COMPANY_BUTTON_END, 8, STR_AI_DEBUG_SELECT_AI_TOOLTIP);
1404 }
1405 
1412 {
1413  if (_game_mode != GM_NORMAL) return ES_NOT_HANDLED;
1415  if (w == NULL) return ES_NOT_HANDLED;
1416  return w->OnHotkey(hotkey);
1417 }
1418 
1419 static Hotkey aidebug_hotkeys[] = {
1420  Hotkey('1', "company_1", WID_AID_COMPANY_BUTTON_START),
1421  Hotkey('2', "company_2", WID_AID_COMPANY_BUTTON_START + 1),
1422  Hotkey('3', "company_3", WID_AID_COMPANY_BUTTON_START + 2),
1423  Hotkey('4', "company_4", WID_AID_COMPANY_BUTTON_START + 3),
1424  Hotkey('5', "company_5", WID_AID_COMPANY_BUTTON_START + 4),
1425  Hotkey('6', "company_6", WID_AID_COMPANY_BUTTON_START + 5),
1426  Hotkey('7', "company_7", WID_AID_COMPANY_BUTTON_START + 6),
1427  Hotkey('8', "company_8", WID_AID_COMPANY_BUTTON_START + 7),
1428  Hotkey('9', "company_9", WID_AID_COMPANY_BUTTON_START + 8),
1429  Hotkey((uint16)0, "company_10", WID_AID_COMPANY_BUTTON_START + 9),
1430  Hotkey((uint16)0, "company_11", WID_AID_COMPANY_BUTTON_START + 10),
1431  Hotkey((uint16)0, "company_12", WID_AID_COMPANY_BUTTON_START + 11),
1432  Hotkey((uint16)0, "company_13", WID_AID_COMPANY_BUTTON_START + 12),
1433  Hotkey((uint16)0, "company_14", WID_AID_COMPANY_BUTTON_START + 13),
1434  Hotkey((uint16)0, "company_15", WID_AID_COMPANY_BUTTON_START + 14),
1435  Hotkey('S', "settings", WID_AID_SETTINGS),
1436  Hotkey('0', "game_script", WID_AID_SCRIPT_GAME),
1437  Hotkey((uint16)0, "reload", WID_AID_RELOAD_TOGGLE),
1438  Hotkey('B', "break_toggle", WID_AID_BREAK_STR_ON_OFF_BTN),
1439  Hotkey('F', "break_string", WID_AID_BREAK_STR_EDIT_BOX),
1440  Hotkey('C', "match_case", WID_AID_MATCH_CASE_BTN),
1441  Hotkey(WKC_RETURN, "continue", WID_AID_CONTINUE_BTN),
1442  HOTKEY_LIST_END
1443 };
1444 HotkeyList AIDebugWindow::hotkeys("aidebug", aidebug_hotkeys, AIDebugGlobalHotkeys);
1445 
1449  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
1450  NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_AI_DEBUG, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1451  NWidget(WWT_SHADEBOX, COLOUR_GREY),
1452  NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
1453  NWidget(WWT_STICKYBOX, COLOUR_GREY),
1454  EndContainer(),
1455  NWidget(WWT_PANEL, COLOUR_GREY, WID_AID_VIEW),
1457  EndContainer(),
1459  NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_AID_SCRIPT_GAME), SetMinimalSize(100, 20), SetResize(1, 0), SetDataTip(STR_AI_GAME_SCRIPT, STR_AI_GAME_SCRIPT_TOOLTIP),
1460  NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_AID_NAME_TEXT), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_JUST_STRING, STR_AI_DEBUG_NAME_TOOLTIP),
1461  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_AID_SETTINGS), SetMinimalSize(100, 20), SetDataTip(STR_AI_DEBUG_SETTINGS, STR_AI_DEBUG_SETTINGS_TOOLTIP),
1462  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_AID_RELOAD_TOGGLE), SetMinimalSize(100, 20), SetDataTip(STR_AI_DEBUG_RELOAD, STR_AI_DEBUG_RELOAD_TOOLTIP),
1463  EndContainer(),
1466  /* Log panel */
1468  EndContainer(),
1469  /* Break string widgets */
1472  NWidget(WWT_IMGBTN_2, COLOUR_GREY, WID_AID_BREAK_STR_ON_OFF_BTN), SetFill(0, 1), SetDataTip(SPR_FLAG_VEH_STOPPED, STR_AI_DEBUG_BREAK_STR_ON_OFF_TOOLTIP),
1473  NWidget(WWT_PANEL, COLOUR_GREY),
1475  NWidget(WWT_LABEL, COLOUR_GREY), SetPadding(2, 2, 2, 4), SetDataTip(STR_AI_DEBUG_BREAK_ON_LABEL, 0x0),
1476  NWidget(WWT_EDITBOX, COLOUR_GREY, WID_AID_BREAK_STR_EDIT_BOX), SetFill(1, 1), SetResize(1, 0), SetPadding(2, 2, 2, 2), SetDataTip(STR_AI_DEBUG_BREAK_STR_OSKTITLE, STR_AI_DEBUG_BREAK_STR_TOOLTIP),
1477  EndContainer(),
1478  EndContainer(),
1479  NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_AID_MATCH_CASE_BTN), SetMinimalSize(100, 0), SetFill(0, 1), SetDataTip(STR_AI_DEBUG_MATCH_CASE, STR_AI_DEBUG_MATCH_CASE_TOOLTIP),
1480  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_AID_CONTINUE_BTN), SetMinimalSize(100, 0), SetFill(0, 1), SetDataTip(STR_AI_DEBUG_CONTINUE, STR_AI_DEBUG_CONTINUE_TOOLTIP),
1481  EndContainer(),
1482  EndContainer(),
1483  EndContainer(),
1485  NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_AID_SCROLLBAR),
1486  NWidget(WWT_RESIZEBOX, COLOUR_GREY),
1487  EndContainer(),
1488  EndContainer(),
1489 };
1490 
1492 static WindowDesc _ai_debug_desc(
1493  WDP_AUTO, "script_debug", 600, 450,
1495  0,
1496  _nested_ai_debug_widgets, lengthof(_nested_ai_debug_widgets),
1497  &AIDebugWindow::hotkeys
1498 );
1499 
1505 {
1506  if (!_networking || _network_server) {
1508  if (w == NULL) w = new AIDebugWindow(&_ai_debug_desc, 0);
1509  if (show_company != INVALID_COMPANY) w->ChangeToAI(show_company);
1510  return w;
1511  } else {
1512  ShowErrorMessage(STR_ERROR_AI_DEBUG_SERVER_ONLY, INVALID_STRING_ID, WL_INFO);
1513  }
1514 
1515  return NULL;
1516 }
1517 
1522 {
1523  AIDebugWindow::ai_debug_company = INVALID_COMPANY;
1524 }
1525 
1528 {
1529  /* Network clients can't debug AIs. */
1530  if (_networking && !_network_server) return;
1531 
1532  Company *c;
1533  FOR_ALL_COMPANIES(c) {
1534  if (c->is_ai && c->ai_instance->IsDead()) {
1536  break;
1537  }
1538  }
1539 
1541  if (g != NULL && g->IsDead()) {
1543  }
1544 }