Fix: Don't allow right-click to close world generation progress window. (#13084)
[openttd-github.git] / src / ai / ai_gui.cpp
blob387e1dde8ddf14ec447104005ee7bd5e7983ce32
1 /*
2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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/>.
6 */
8 /** @file ai_gui.cpp %Window for configuring the AIs */
10 #include "../stdafx.h"
11 #include "../error.h"
12 #include "../company_base.h"
13 #include "../window_func.h"
14 #include "../network/network.h"
15 #include "../settings_func.h"
16 #include "../network/network_content.h"
17 #include "../core/geometry_func.hpp"
19 #include "ai.hpp"
20 #include "ai_gui.hpp"
21 #include "ai_config.hpp"
22 #include "ai_info.hpp"
23 #include "../script/script_gui.h"
24 #include "table/strings.h"
26 #include "../safeguards.h"
29 /** Widgets for the configure AI window. */
30 static constexpr NWidgetPart _nested_ai_config_widgets[] = {
31 NWidget(NWID_HORIZONTAL),
32 NWidget(WWT_CLOSEBOX, COLOUR_MAUVE),
33 NWidget(WWT_CAPTION, COLOUR_MAUVE), SetDataTip(STR_AI_CONFIG_CAPTION_AI, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
34 EndContainer(),
35 NWidget(WWT_PANEL, COLOUR_MAUVE, WID_AIC_BACKGROUND),
36 NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_wide, 0), SetPadding(WidgetDimensions::unscaled.sparse),
37 NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_sparse, 0),
38 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0),
39 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
40 NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_AIC_DECREASE_NUMBER), SetDataTip(AWV_DECREASE, STR_NULL),
41 NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_AIC_INCREASE_NUMBER), SetDataTip(AWV_INCREASE, STR_NULL),
42 EndContainer(),
43 NWidget(WWT_TEXT, COLOUR_MAUVE, WID_AIC_NUMBER), SetDataTip(STR_AI_CONFIG_MAX_COMPETITORS, STR_NULL), SetFill(1, 0),
44 EndContainer(),
45 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0),
46 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
47 NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_AIC_DECREASE_INTERVAL), SetDataTip(AWV_DECREASE, STR_NULL),
48 NWidget(WWT_PUSHARROWBTN, COLOUR_YELLOW, WID_AIC_INCREASE_INTERVAL), SetDataTip(AWV_INCREASE, STR_NULL),
49 EndContainer(),
50 NWidget(WWT_TEXT, COLOUR_MAUVE, WID_AIC_INTERVAL), SetDataTip(STR_AI_CONFIG_COMPETITORS_INTERVAL, STR_NULL), SetFill(1, 0),
51 EndContainer(),
52 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
53 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),
54 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),
55 EndContainer(),
56 EndContainer(),
57 NWidget(WWT_FRAME, COLOUR_MAUVE), SetDataTip(STR_AI_CONFIG_AI, STR_NULL), SetPIP(0, WidgetDimensions::unscaled.vsep_sparse, 0),
58 NWidget(NWID_HORIZONTAL),
59 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),
60 NWidget(NWID_VSCROLLBAR, COLOUR_MAUVE, WID_AIC_SCROLLBAR),
61 EndContainer(),
62 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_CONFIGURE), SetFill(1, 0), SetDataTip(STR_AI_CONFIG_CONFIGURE, STR_AI_CONFIG_CONFIGURE_TOOLTIP),
63 EndContainer(),
64 NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_wide, 0),
65 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
66 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_CHANGE), SetFill(1, 1), SetDataTip(STR_AI_CONFIG_CHANGE_AI, STR_AI_CONFIG_CHANGE_TOOLTIP),
67 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_CONTENT_DOWNLOAD), SetFill(1, 1), SetDataTip(STR_INTRO_ONLINE_CONTENT, STR_INTRO_TOOLTIP_ONLINE_CONTENT),
68 EndContainer(),
69 NWidget(NWID_VERTICAL, NC_EQUALSIZE),
70 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
71 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_OPEN_URL), SetResize(1, 0), SetFill(1, 0), SetDataTip(STR_CONTENT_OPEN_URL, STR_CONTENT_OPEN_URL_TOOLTIP),
72 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_TEXTFILE + TFT_README), SetFill(1, 1), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_README, STR_TEXTFILE_VIEW_README_TOOLTIP),
73 EndContainer(),
74 NWidget(NWID_HORIZONTAL, NC_EQUALSIZE),
75 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_TEXTFILE + TFT_CHANGELOG), SetFill(1, 1), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_CHANGELOG, STR_TEXTFILE_VIEW_CHANGELOG_TOOLTIP),
76 NWidget(WWT_PUSHTXTBTN, COLOUR_YELLOW, WID_AIC_TEXTFILE + TFT_LICENSE), SetFill(1, 1), SetResize(1, 0), SetDataTip(STR_TEXTFILE_VIEW_LICENCE, STR_TEXTFILE_VIEW_LICENCE_TOOLTIP),
77 EndContainer(),
78 EndContainer(),
79 EndContainer(),
80 EndContainer(),
81 EndContainer(),
84 /** Window definition for the configure AI window. */
85 static WindowDesc _ai_config_desc(
86 WDP_CENTER, nullptr, 0, 0,
87 WC_GAME_OPTIONS, WC_NONE,
89 _nested_ai_config_widgets
92 /**
93 * Window to configure which AIs will start.
95 struct AIConfigWindow : public Window {
96 CompanyID selected_slot; ///< The currently selected AI slot or \c INVALID_COMPANY.
97 int line_height; ///< Height of a single AI-name line.
98 Scrollbar *vscroll; ///< Cache of the vertical scrollbar.
100 AIConfigWindow() : Window(_ai_config_desc)
102 this->InitNested(WN_GAME_OPTIONS_AI); // Initializes 'this->line_height' as a side effect.
103 this->vscroll = this->GetScrollbar(WID_AIC_SCROLLBAR);
104 this->selected_slot = INVALID_COMPANY;
105 NWidgetCore *nwi = this->GetWidget<NWidgetCore>(WID_AIC_LIST);
106 this->vscroll->SetCapacity(nwi->current_y / this->line_height);
107 this->vscroll->SetCount(MAX_COMPANIES);
108 this->OnInvalidateData(0);
111 void Close([[maybe_unused]] int data = 0) override
113 CloseWindowByClass(WC_SCRIPT_LIST);
114 CloseWindowByClass(WC_SCRIPT_SETTINGS);
115 this->Window::Close();
118 void SetStringParameters(WidgetID widget) const override
120 switch (widget) {
121 case WID_AIC_NUMBER:
122 SetDParam(0, GetGameSettings().difficulty.max_no_competitors);
123 break;
125 case WID_AIC_INTERVAL:
126 SetDParam(0, GetGameSettings().difficulty.competitors_interval);
127 break;
131 void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
133 switch (widget) {
134 case WID_AIC_DECREASE_NUMBER:
135 case WID_AIC_INCREASE_NUMBER:
136 case WID_AIC_DECREASE_INTERVAL:
137 case WID_AIC_INCREASE_INTERVAL:
138 size = maxdim(size, NWidgetScrollbar::GetHorizontalDimension());
139 break;
141 case WID_AIC_LIST:
142 this->line_height = GetCharacterHeight(FS_NORMAL) + padding.height;
143 resize.height = this->line_height;
144 size.height = 8 * this->line_height;
145 break;
150 * Can the AI config in the given company slot be edited?
151 * @param slot The slot to query.
152 * @return True if and only if the given AI Config slot can be edited.
154 static bool IsEditable(CompanyID slot)
156 if (_game_mode != GM_NORMAL) {
157 return slot > 0 && slot < MAX_COMPANIES;
159 return slot < MAX_COMPANIES && !Company::IsValidID(slot);
162 void DrawWidget(const Rect &r, WidgetID widget) const override
164 switch (widget) {
165 case WID_AIC_LIST: {
166 Rect tr = r.Shrink(WidgetDimensions::scaled.matrix);
167 int max_slot = GetGameSettings().difficulty.max_no_competitors;
168 if (_game_mode == GM_NORMAL) {
169 for (const Company *c : Company::Iterate()) {
170 if (c->is_ai) max_slot--;
172 for (CompanyID cid = COMPANY_FIRST; cid < (CompanyID)max_slot && cid < MAX_COMPANIES; cid++) {
173 if (Company::IsValidID(cid)) max_slot++;
175 } else {
176 max_slot++; // Slot 0 is human
178 for (int i = this->vscroll->GetPosition(); this->vscroll->IsVisible(i) && i < MAX_COMPANIES; i++) {
179 StringID text;
181 if ((_game_mode != GM_NORMAL && i == 0) || (_game_mode == GM_NORMAL && Company::IsValidHumanID(i))) {
182 text = STR_AI_CONFIG_HUMAN_PLAYER;
183 } else if (AIConfig::GetConfig((CompanyID)i)->GetInfo() != nullptr) {
184 SetDParamStr(0, AIConfig::GetConfig((CompanyID)i)->GetInfo()->GetName());
185 text = STR_JUST_RAW_STRING;
186 } else {
187 text = STR_AI_CONFIG_RANDOM_AI;
190 TextColour tc = TC_SILVER;
191 if (this->selected_slot == i) {
192 tc = TC_WHITE;
193 } else if (IsEditable((CompanyID)i)) {
194 if (i < max_slot) tc = TC_ORANGE;
195 } else if (Company::IsValidAiID(i)) {
196 tc = TC_GREEN;
198 DrawString(tr, text, tc);
199 tr.top += this->line_height;
201 break;
206 void OnClick([[maybe_unused]] Point pt, WidgetID widget, [[maybe_unused]] int click_count) override
208 if (widget >= WID_AIC_TEXTFILE && widget < WID_AIC_TEXTFILE + TFT_CONTENT_END) {
209 if (this->selected_slot == INVALID_COMPANY || AIConfig::GetConfig(this->selected_slot) == nullptr) return;
211 ShowScriptTextfileWindow((TextfileType)(widget - WID_AIC_TEXTFILE), this->selected_slot);
212 return;
215 switch (widget) {
216 case WID_AIC_DECREASE_NUMBER:
217 case WID_AIC_INCREASE_NUMBER: {
218 int new_value;
219 if (widget == WID_AIC_DECREASE_NUMBER) {
220 new_value = std::max(0, GetGameSettings().difficulty.max_no_competitors - 1);
221 } else {
222 new_value = std::min(MAX_COMPANIES - 1, GetGameSettings().difficulty.max_no_competitors + 1);
224 IConsoleSetSetting("difficulty.max_no_competitors", new_value);
225 this->InvalidateData();
226 break;
229 case WID_AIC_DECREASE_INTERVAL:
230 case WID_AIC_INCREASE_INTERVAL: {
231 int new_value;
232 if (widget == WID_AIC_DECREASE_INTERVAL) {
233 new_value = std::max(static_cast<int>(MIN_COMPETITORS_INTERVAL), GetGameSettings().difficulty.competitors_interval - 1);
234 } else {
235 new_value = std::min(static_cast<int>(MAX_COMPETITORS_INTERVAL), GetGameSettings().difficulty.competitors_interval + 1);
237 IConsoleSetSetting("difficulty.competitors_interval", new_value);
238 this->InvalidateData();
239 break;
242 case WID_AIC_LIST: { // Select a slot
243 this->selected_slot = (CompanyID)this->vscroll->GetScrolledRowFromWidget(pt.y, this, widget);
244 this->InvalidateData();
245 if (click_count > 1 && IsEditable(this->selected_slot)) ShowScriptListWindow((CompanyID)this->selected_slot, _ctrl_pressed);
246 break;
249 case WID_AIC_MOVE_UP:
250 if (IsEditable(this->selected_slot) && IsEditable((CompanyID)(this->selected_slot - 1))) {
251 Swap(GetGameSettings().ai_config[this->selected_slot], GetGameSettings().ai_config[this->selected_slot - 1]);
252 this->selected_slot--;
253 this->vscroll->ScrollTowards(this->selected_slot);
254 this->InvalidateData();
256 break;
258 case WID_AIC_MOVE_DOWN:
259 if (IsEditable(this->selected_slot) && IsEditable((CompanyID)(this->selected_slot + 1))) {
260 Swap(GetGameSettings().ai_config[this->selected_slot], GetGameSettings().ai_config[this->selected_slot + 1]);
261 this->selected_slot++;
262 this->vscroll->ScrollTowards(this->selected_slot);
263 this->InvalidateData();
265 break;
267 case WID_AIC_OPEN_URL: {
268 const AIConfig *config = AIConfig::GetConfig(this->selected_slot);
269 if (this->selected_slot == INVALID_COMPANY || config == nullptr || config->GetInfo() == nullptr) return;
270 OpenBrowser(config->GetInfo()->GetURL());
271 break;
274 case WID_AIC_CHANGE: // choose other AI
275 if (IsEditable(this->selected_slot)) ShowScriptListWindow((CompanyID)this->selected_slot, _ctrl_pressed);
276 break;
278 case WID_AIC_CONFIGURE: // change the settings for an AI
279 ShowScriptSettingsWindow((CompanyID)this->selected_slot);
280 break;
282 case WID_AIC_CONTENT_DOWNLOAD:
283 if (!_network_available) {
284 ShowErrorMessage(STR_NETWORK_ERROR_NOTAVAILABLE, INVALID_STRING_ID, WL_ERROR);
285 } else {
286 ShowNetworkContentListWindow(nullptr, CONTENT_TYPE_AI);
288 break;
293 * Some data on this window has become invalid.
294 * @param data Information about the changed data.
295 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
297 void OnInvalidateData([[maybe_unused]] int data = 0, [[maybe_unused]] bool gui_scope = true) override
299 if (!IsEditable(this->selected_slot) && !Company::IsValidAiID(this->selected_slot)) {
300 this->selected_slot = INVALID_COMPANY;
303 if (!gui_scope) return;
305 AIConfig *config = this->selected_slot == INVALID_COMPANY ? nullptr : AIConfig::GetConfig(this->selected_slot);
307 this->SetWidgetDisabledState(WID_AIC_DECREASE_NUMBER, GetGameSettings().difficulty.max_no_competitors == 0);
308 this->SetWidgetDisabledState(WID_AIC_INCREASE_NUMBER, GetGameSettings().difficulty.max_no_competitors == MAX_COMPANIES - 1);
309 this->SetWidgetDisabledState(WID_AIC_DECREASE_INTERVAL, GetGameSettings().difficulty.competitors_interval == MIN_COMPETITORS_INTERVAL);
310 this->SetWidgetDisabledState(WID_AIC_INCREASE_INTERVAL, GetGameSettings().difficulty.competitors_interval == MAX_COMPETITORS_INTERVAL);
311 this->SetWidgetDisabledState(WID_AIC_CHANGE, !IsEditable(this->selected_slot));
312 this->SetWidgetDisabledState(WID_AIC_CONFIGURE, this->selected_slot == INVALID_COMPANY || config->GetConfigList()->empty());
313 this->SetWidgetDisabledState(WID_AIC_MOVE_UP, !IsEditable(this->selected_slot) || !IsEditable((CompanyID)(this->selected_slot - 1)));
314 this->SetWidgetDisabledState(WID_AIC_MOVE_DOWN, !IsEditable(this->selected_slot) || !IsEditable((CompanyID)(this->selected_slot + 1)));
316 this->SetWidgetDisabledState(WID_AIC_OPEN_URL, this->selected_slot == INVALID_COMPANY || config->GetInfo() == nullptr || config->GetInfo()->GetURL().empty());
317 for (TextfileType tft = TFT_CONTENT_BEGIN; tft < TFT_CONTENT_END; tft++) {
318 this->SetWidgetDisabledState(WID_AIC_TEXTFILE + tft, this->selected_slot == INVALID_COMPANY || !config->GetTextfile(tft, this->selected_slot).has_value());
323 /** Open the AI config window. */
324 void ShowAIConfigWindow()
326 CloseWindowByClass(WC_GAME_OPTIONS);
327 new AIConfigWindow();