OpenTTD
ai_gui.cpp
Go to the documentation of this file.
1 /* $Id: ai_gui.cpp 27468 2015-12-10 18:28:01Z zuu $ */
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)
905 #endif
906  }
907  break;
908  }
909  }
910 
916  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
917  {
918  if (!IsEditable(this->selected_slot)) {
920  }
921 
922  if (!gui_scope) return;
923 
924  this->SetWidgetDisabledState(WID_AIC_DECREASE, GetGameSettings().difficulty.max_no_competitors == 0);
925  this->SetWidgetDisabledState(WID_AIC_INCREASE, GetGameSettings().difficulty.max_no_competitors == MAX_COMPANIES - 1);
926  this->SetWidgetDisabledState(WID_AIC_CHANGE, (this->selected_slot == OWNER_DEITY && _game_mode == GM_NORMAL) || this->selected_slot == INVALID_COMPANY);
927  this->SetWidgetDisabledState(WID_AIC_CONFIGURE, this->selected_slot == INVALID_COMPANY || GetConfig(this->selected_slot)->GetConfigList()->size() == 0);
930 
931  for (TextfileType tft = TFT_BEGIN; tft < TFT_END; tft++) {
932  this->SetWidgetDisabledState(WID_AIC_TEXTFILE + tft, this->selected_slot == INVALID_COMPANY || (GetConfig(this->selected_slot)->GetTextfile(tft, this->selected_slot) == NULL));
933  }
934  }
935 };
936 
939 {
941  new AIConfigWindow();
942 }
943 
952 static bool SetScriptButtonColour(NWidgetCore &button, bool dead, bool paused)
953 {
954  /* Dead scripts are indicated with red background and
955  * paused scripts are indicated with yellow background. */
956  Colours colour = dead ? COLOUR_RED :
957  (paused ? COLOUR_YELLOW : COLOUR_GREY);
958  if (button.colour != colour) {
959  button.colour = colour;
960  return true;
961  }
962  return false;
963 }
964 
968 struct AIDebugWindow : public Window {
969  static const int top_offset;
970  static const int bottom_offset;
971 
972  static const uint MAX_BREAK_STR_STRING_LENGTH = 256;
973 
977  bool autoscroll;
979  static bool break_check_enabled;
986 
987  ScriptLog::LogData *GetLogPointer() const
988  {
989  if (ai_debug_company == OWNER_DEITY) return (ScriptLog::LogData *)Game::GetInstance()->GetLogPointer();
990  return (ScriptLog::LogData *)Company::Get(ai_debug_company)->ai_instance->GetLogPointer();
991  }
992 
997  bool IsDead() const
998  {
999  if (ai_debug_company == OWNER_DEITY) {
1000  GameInstance *game = Game::GetInstance();
1001  return game == NULL || game->IsDead();
1002  }
1003  return !Company::IsValidAiID(ai_debug_company) || Company::Get(ai_debug_company)->ai_instance->IsDead();
1004  }
1005 
1011  bool IsValidDebugCompany(CompanyID company) const
1012  {
1013  switch (company) {
1014  case INVALID_COMPANY: return false;
1015  case OWNER_DEITY: return Game::GetInstance() != NULL;
1016  default: return Company::IsValidAiID(company);
1017  }
1018  }
1019 
1025  {
1026  /* Check if the currently selected company is still active. */
1027  if (this->IsValidDebugCompany(ai_debug_company)) return;
1028 
1030 
1031  const Company *c;
1032  FOR_ALL_COMPANIES(c) {
1033  if (c->is_ai) {
1034  ChangeToAI(c->index);
1035  return;
1036  }
1037  }
1038 
1039  /* If no AI is available, see if there is a game script. */
1040  if (Game::GetInstance() != NULL) ChangeToAI(OWNER_DEITY);
1041  }
1042 
1049  {
1050  this->CreateNestedTree();
1051  this->vscroll = this->GetScrollbar(WID_AID_SCROLLBAR);
1053  this->GetWidget<NWidgetStacked>(WID_AID_BREAK_STRING_WIDGETS)->SetDisplayedPlane(this->show_break_box ? 0 : SZSP_HORIZONTAL);
1054  this->FinishInitNested(number);
1055 
1056  if (!this->show_break_box) break_check_enabled = false;
1057 
1058  this->last_vscroll_pos = 0;
1059  this->autoscroll = true;
1060  this->highlight_row = -1;
1061 
1063 
1065 
1066  /* Restore the break string value from static variable */
1067  this->break_editbox.text.Assign(this->break_string);
1068 
1069  this->SelectValidDebugCompany();
1070  this->InvalidateData(-1);
1071  }
1072 
1073  virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize)
1074  {
1075  if (widget == WID_AID_LOG_PANEL) {
1076  resize->height = FONT_HEIGHT_NORMAL + WD_PAR_VSEP_NORMAL;
1077  size->height = 14 * resize->height + this->top_offset + this->bottom_offset;
1078  }
1079  }
1080 
1081  virtual void OnPaint()
1082  {
1083  this->SelectValidDebugCompany();
1084 
1085  /* Draw standard stuff */
1086  this->DrawWidgets();
1087 
1088  if (this->IsShaded()) return; // Don't draw anything when the window is shaded.
1089 
1090  bool dirty = false;
1091 
1092  /* Paint the company icons */
1093  for (CompanyID i = COMPANY_FIRST; i < MAX_COMPANIES; i++) {
1094  NWidgetCore *button = this->GetWidget<NWidgetCore>(i + WID_AID_COMPANY_BUTTON_START);
1095 
1096  bool valid = Company::IsValidAiID(i);
1097 
1098  /* Check whether the validity of the company changed */
1099  dirty |= (button->IsDisabled() == valid);
1100 
1101  /* Mark dead/paused AIs by setting the background colour. */
1102  bool dead = valid && Company::Get(i)->ai_instance->IsDead();
1103  bool paused = valid && Company::Get(i)->ai_instance->IsPaused();
1104  /* Re-paint if the button was updated.
1105  * (note that it is intentional that SetScriptButtonColour is always called) */
1106  dirty |= SetScriptButtonColour(*button, dead, paused);
1107 
1108  /* Draw company icon only for valid AI companies */
1109  if (!valid) continue;
1110 
1111  byte offset = (i == ai_debug_company) ? 1 : 0;
1112  DrawCompanyIcon(i, button->pos_x + button->current_x / 2 - 7 + offset, this->GetWidget<NWidgetBase>(WID_AID_COMPANY_BUTTON_START + i)->pos_y + 2 + offset);
1113  }
1114 
1115  /* Set button colour for Game Script. */
1116  GameInstance *game = Game::GetInstance();
1117  bool valid = game != NULL;
1118  bool dead = valid && game->IsDead();
1119  bool paused = valid && game->IsPaused();
1120 
1121  NWidgetCore *button = this->GetWidget<NWidgetCore>(WID_AID_SCRIPT_GAME);
1122  dirty |= (button->IsDisabled() == valid) || SetScriptButtonColour(*button, dead, paused);
1123 
1124  if (dirty) this->InvalidateData(-1);
1125 
1126  /* If there are no active companies, don't display anything else. */
1127  if (ai_debug_company == INVALID_COMPANY) return;
1128 
1129  ScriptLog::LogData *log = this->GetLogPointer();
1130 
1131  int scroll_count = (log == NULL) ? 0 : log->used;
1132  if (this->vscroll->GetCount() != scroll_count) {
1133  this->vscroll->SetCount(scroll_count);
1134 
1135  /* We need a repaint */
1137  }
1138 
1139  if (log == NULL) return;
1140 
1141  /* Detect when the user scrolls the window. Enable autoscroll when the
1142  * bottom-most line becomes visible. */
1143  if (this->last_vscroll_pos != this->vscroll->GetPosition()) {
1144  this->autoscroll = this->vscroll->GetPosition() >= log->used - this->vscroll->GetCapacity();
1145  }
1146  if (this->autoscroll) {
1147  int scroll_pos = max(0, log->used - this->vscroll->GetCapacity());
1148  if (scroll_pos != this->vscroll->GetPosition()) {
1149  this->vscroll->SetPosition(scroll_pos);
1150 
1151  /* We need a repaint */
1154  }
1155  }
1156  this->last_vscroll_pos = this->vscroll->GetPosition();
1157  }
1158 
1159  virtual void SetStringParameters(int widget) const
1160  {
1161  switch (widget) {
1162  case WID_AID_NAME_TEXT:
1163  if (ai_debug_company == OWNER_DEITY) {
1164  const GameInfo *info = Game::GetInfo();
1165  assert(info != NULL);
1166  SetDParam(0, STR_AI_DEBUG_NAME_AND_VERSION);
1167  SetDParamStr(1, info->GetName());
1168  SetDParam(2, info->GetVersion());
1170  SetDParam(0, STR_EMPTY);
1171  } else {
1172  const AIInfo *info = Company::Get(ai_debug_company)->ai_info;
1173  assert(info != NULL);
1174  SetDParam(0, STR_AI_DEBUG_NAME_AND_VERSION);
1175  SetDParamStr(1, info->GetName());
1176  SetDParam(2, info->GetVersion());
1177  }
1178  break;
1179  }
1180  }
1181 
1182  virtual void DrawWidget(const Rect &r, int widget) const
1183  {
1184  if (ai_debug_company == INVALID_COMPANY) return;
1185 
1186  switch (widget) {
1187  case WID_AID_LOG_PANEL: {
1188  ScriptLog::LogData *log = this->GetLogPointer();
1189  if (log == NULL) return;
1190 
1191  int y = this->top_offset;
1192  for (int i = this->vscroll->GetPosition(); this->vscroll->IsVisible(i) && i < log->used; i++) {
1193  int pos = (i + log->pos + 1 - log->used + log->count) % log->count;
1194  if (log->lines[pos] == NULL) break;
1195 
1196  TextColour colour;
1197  switch (log->type[pos]) {
1198  case ScriptLog::LOG_SQ_INFO: colour = TC_BLACK; break;
1199  case ScriptLog::LOG_SQ_ERROR: colour = TC_RED; break;
1200  case ScriptLog::LOG_INFO: colour = TC_BLACK; break;
1201  case ScriptLog::LOG_WARNING: colour = TC_YELLOW; break;
1202  case ScriptLog::LOG_ERROR: colour = TC_RED; break;
1203  default: colour = TC_BLACK; break;
1204  }
1205 
1206  /* Check if the current line should be highlighted */
1207  if (pos == this->highlight_row) {
1208  GfxFillRect(r.left + 1, r.top + y, r.right - 1, r.top + y + this->resize.step_height - WD_PAR_VSEP_NORMAL, PC_BLACK);
1209  if (colour == TC_BLACK) colour = TC_WHITE; // Make black text readable by inverting it to white.
1210  }
1211 
1212  DrawString(r.left + 7, r.right - 7, r.top + y, log->lines[pos], colour, SA_LEFT | SA_FORCE);
1213  y += this->resize.step_height;
1214  }
1215  break;
1216  }
1217  }
1218  }
1219 
1224  void ChangeToAI(CompanyID show_ai)
1225  {
1226  if (!this->IsValidDebugCompany(show_ai)) return;
1227 
1228  ai_debug_company = show_ai;
1229 
1230  this->highlight_row = -1; // The highlight of one AI make little sense for another AI.
1231 
1232  /* Close AI settings window to prevent confusion */
1234 
1235  this->InvalidateData(-1);
1236 
1237  this->autoscroll = true;
1238  this->last_vscroll_pos = this->vscroll->GetPosition();
1239  }
1240 
1241  virtual void OnClick(Point pt, int widget, int click_count)
1242  {
1243  /* Also called for hotkeys, so check for disabledness */
1244  if (this->IsWidgetDisabled(widget)) return;
1245 
1246  /* Check which button is clicked */
1249  }
1250 
1251  switch (widget) {
1252  case WID_AID_SCRIPT_GAME:
1254  break;
1255 
1256  case WID_AID_RELOAD_TOGGLE:
1257  if (ai_debug_company == OWNER_DEITY) break;
1258  /* First kill the company of the AI, then start a new one. This should start the current AI again */
1260  DoCommandP(0, 1 | ai_debug_company << 16, 0, CMD_COMPANY_CTRL);
1261  break;
1262 
1263  case WID_AID_SETTINGS:
1265  break;
1266 
1269  this->InvalidateData(-1);
1270  break;
1271 
1274  this->InvalidateData(-1);
1275  break;
1276 
1277  case WID_AID_CONTINUE_BTN:
1278  /* Unpause current AI / game script and mark the corresponding script button dirty. */
1279  if (!this->IsDead()) {
1280  if (ai_debug_company == OWNER_DEITY) {
1281  Game::Unpause();
1282  } else {
1284  }
1285  }
1286 
1287  /* If the last AI/Game Script is unpaused, unpause the game too. */
1288  if ((_pause_mode & PM_PAUSED_NORMAL) == PM_PAUSED_NORMAL) {
1289  bool all_unpaused = !Game::IsPaused();
1290  if (all_unpaused) {
1291  Company *c;
1292  FOR_ALL_COMPANIES(c) {
1293  if (c->is_ai && AI::IsPaused(c->index)) {
1294  all_unpaused = false;
1295  break;
1296  }
1297  }
1298  if (all_unpaused) {
1299  /* All scripts have been unpaused => unpause the game. */
1300  DoCommandP(0, PM_PAUSED_NORMAL, 0, CMD_PAUSE);
1301  }
1302  }
1303  }
1304 
1305  this->highlight_row = -1;
1306  this->InvalidateData(-1);
1307  break;
1308  }
1309  }
1310 
1311  virtual void OnEditboxChanged(int wid)
1312  {
1313  if (wid == WID_AID_BREAK_STR_EDIT_BOX) {
1314  /* Save the current string to static member so it can be restored next time the window is opened. */
1315  strecpy(this->break_string, this->break_editbox.text.buf, lastof(this->break_string));
1317  }
1318  }
1319 
1326  virtual void OnInvalidateData(int data = 0, bool gui_scope = true)
1327  {
1328  /* If the log message is related to the active company tab, check the break string.
1329  * This needs to be done in gameloop-scope, so the AI is suspended immediately. */
1330  if (!gui_scope && data == ai_debug_company && this->IsValidDebugCompany(ai_debug_company) && this->break_check_enabled && !this->break_string_filter.IsEmpty()) {
1331  /* Get the log instance of the active company */
1332  ScriptLog::LogData *log = this->GetLogPointer();
1333 
1334  if (log != NULL) {
1336  this->break_string_filter.AddLine(log->lines[log->pos]);
1337  if (this->break_string_filter.GetState()) {
1338  /* Pause execution of script. */
1339  if (!this->IsDead()) {
1340  if (ai_debug_company == OWNER_DEITY) {
1341  Game::Pause();
1342  } else {
1344  }
1345  }
1346 
1347  /* Pause the game. */
1349  DoCommandP(0, PM_PAUSED_NORMAL, 1, CMD_PAUSE);
1350  }
1351 
1352  /* Highlight row that matched */
1353  this->highlight_row = log->pos;
1354  }
1355  }
1356  }
1357 
1358  if (!gui_scope) return;
1359 
1360  this->SelectValidDebugCompany();
1361 
1362  ScriptLog::LogData *log = ai_debug_company != INVALID_COMPANY ? this->GetLogPointer() : NULL;
1363  this->vscroll->SetCount((log == NULL) ? 0 : log->used);
1364 
1365  /* Update company buttons */
1366  for (CompanyID i = COMPANY_FIRST; i < MAX_COMPANIES; i++) {
1369  }
1370 
1373 
1376 
1381  }
1382 
1383  virtual void OnResize()
1384  {
1386  }
1387 
1388  static HotkeyList hotkeys;
1389 };
1390 
1394 char AIDebugWindow::break_string[MAX_BREAK_STR_STRING_LENGTH] = "";
1397 StringFilter AIDebugWindow::break_string_filter(&AIDebugWindow::case_sensitive_break_check);
1398 
1401 {
1402  return MakeCompanyButtonRows(biggest_index, WID_AID_COMPANY_BUTTON_START, WID_AID_COMPANY_BUTTON_END, 8, STR_AI_DEBUG_SELECT_AI_TOOLTIP);
1403 }
1404 
1411 {
1412  if (_game_mode != GM_NORMAL) return ES_NOT_HANDLED;
1414  if (w == NULL) return ES_NOT_HANDLED;
1415  return w->OnHotkey(hotkey);
1416 }
1417 
1418 static Hotkey aidebug_hotkeys[] = {
1419  Hotkey('1', "company_1", WID_AID_COMPANY_BUTTON_START),
1420  Hotkey('2', "company_2", WID_AID_COMPANY_BUTTON_START + 1),
1421  Hotkey('3', "company_3", WID_AID_COMPANY_BUTTON_START + 2),
1422  Hotkey('4', "company_4", WID_AID_COMPANY_BUTTON_START + 3),
1423  Hotkey('5', "company_5", WID_AID_COMPANY_BUTTON_START + 4),
1424  Hotkey('6', "company_6", WID_AID_COMPANY_BUTTON_START + 5),
1425  Hotkey('7', "company_7", WID_AID_COMPANY_BUTTON_START + 6),
1426  Hotkey('8', "company_8", WID_AID_COMPANY_BUTTON_START + 7),
1427  Hotkey('9', "company_9", WID_AID_COMPANY_BUTTON_START + 8),
1428  Hotkey((uint16)0, "company_10", WID_AID_COMPANY_BUTTON_START + 9),
1429  Hotkey((uint16)0, "company_11", WID_AID_COMPANY_BUTTON_START + 10),
1430  Hotkey((uint16)0, "company_12", WID_AID_COMPANY_BUTTON_START + 11),
1431  Hotkey((uint16)0, "company_13", WID_AID_COMPANY_BUTTON_START + 12),
1432  Hotkey((uint16)0, "company_14", WID_AID_COMPANY_BUTTON_START + 13),
1433  Hotkey((uint16)0, "company_15", WID_AID_COMPANY_BUTTON_START + 14),
1434  Hotkey('S', "settings", WID_AID_SETTINGS),
1435  Hotkey('0', "game_script", WID_AID_SCRIPT_GAME),
1436  Hotkey((uint16)0, "reload", WID_AID_RELOAD_TOGGLE),
1437  Hotkey('B', "break_toggle", WID_AID_BREAK_STR_ON_OFF_BTN),
1438  Hotkey('F', "break_string", WID_AID_BREAK_STR_EDIT_BOX),
1439  Hotkey('C', "match_case", WID_AID_MATCH_CASE_BTN),
1440  Hotkey(WKC_RETURN, "continue", WID_AID_CONTINUE_BTN),
1441  HOTKEY_LIST_END
1442 };
1443 HotkeyList AIDebugWindow::hotkeys("aidebug", aidebug_hotkeys, AIDebugGlobalHotkeys);
1444 
1448  NWidget(WWT_CLOSEBOX, COLOUR_GREY),
1449  NWidget(WWT_CAPTION, COLOUR_GREY), SetDataTip(STR_AI_DEBUG, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
1450  NWidget(WWT_SHADEBOX, COLOUR_GREY),
1451  NWidget(WWT_DEFSIZEBOX, COLOUR_GREY),
1452  NWidget(WWT_STICKYBOX, COLOUR_GREY),
1453  EndContainer(),
1454  NWidget(WWT_PANEL, COLOUR_GREY, WID_AID_VIEW),
1456  EndContainer(),
1458  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),
1459  NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_AID_NAME_TEXT), SetFill(1, 0), SetResize(1, 0), SetDataTip(STR_JUST_STRING, STR_AI_DEBUG_NAME_TOOLTIP),
1460  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_AID_SETTINGS), SetMinimalSize(100, 20), SetDataTip(STR_AI_DEBUG_SETTINGS, STR_AI_DEBUG_SETTINGS_TOOLTIP),
1461  NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_AID_RELOAD_TOGGLE), SetMinimalSize(100, 20), SetDataTip(STR_AI_DEBUG_RELOAD, STR_AI_DEBUG_RELOAD_TOOLTIP),
1462  EndContainer(),
1465  /* Log panel */
1467  EndContainer(),
1468  /* Break string widgets */
1471  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),
1472  NWidget(WWT_PANEL, COLOUR_GREY),
1474  NWidget(WWT_LABEL, COLOUR_GREY), SetPadding(2, 2, 2, 4), SetDataTip(STR_AI_DEBUG_BREAK_ON_LABEL, 0x0),
1475  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),
1476  EndContainer(),
1477  EndContainer(),
1478  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),
1479  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),
1480  EndContainer(),
1481  EndContainer(),
1482  EndContainer(),
1484  NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_AID_SCROLLBAR),
1485  NWidget(WWT_RESIZEBOX, COLOUR_GREY),
1486  EndContainer(),
1487  EndContainer(),
1488 };
1489 
1491 static WindowDesc _ai_debug_desc(
1492  WDP_AUTO, "script_debug", 600, 450,
1494  0,
1495  _nested_ai_debug_widgets, lengthof(_nested_ai_debug_widgets),
1496  &AIDebugWindow::hotkeys
1497 );
1498 
1504 {
1505  if (!_networking || _network_server) {
1507  if (w == NULL) w = new AIDebugWindow(&_ai_debug_desc, 0);
1508  if (show_company != INVALID_COMPANY) w->ChangeToAI(show_company);
1509  return w;
1510  } else {
1511  ShowErrorMessage(STR_ERROR_AI_DEBUG_SERVER_ONLY, INVALID_STRING_ID, WL_INFO);
1512  }
1513 
1514  return NULL;
1515 }
1516 
1521 {
1522  AIDebugWindow::ai_debug_company = INVALID_COMPANY;
1523 }
1524 
1527 {
1528  /* Network clients can't debug AIs. */
1529  if (_networking && !_network_server) return;
1530 
1531  Company *c;
1532  FOR_ALL_COMPANIES(c) {
1533  if (c->is_ai && c->ai_instance->IsDead()) {
1535  break;
1536  }
1537  }
1538 
1540  if (g != NULL && g->IsDead()) {
1542  }
1543 }