From 184ade43ebc25776a7db01776453d2c6df2e1374 Mon Sep 17 00:00:00 2001 From: Andreas Schmitt Date: Fri, 11 Jun 2021 18:10:46 +0200 Subject: [PATCH 1/3] Query before destroying important structures (rail stations and industries) Prior to this change, the use of NewObjects often lead to the problem of players trying to clear those and accidentally destroying a rail station or (when using magic bulldozer) an industry. This action cannot be undone. This change shows a query making sure the player actually wants to destroy the station or industry. NOTE: The switch to a std::function no longer allows for the inequality check in ShowQuery in misc_gui. If this was required a different solution has to be found. --- src/lang/english.txt | 4 ++++ src/misc_gui.cpp | 16 ++++++++------ src/network/network_gui.cpp | 2 +- src/terraform_gui.cpp | 43 +++++++++++++++++++++++++++++++++++-- src/textbuf_gui.h | 9 +++++--- 5 files changed, 61 insertions(+), 13 deletions(-) diff --git a/src/lang/english.txt b/src/lang/english.txt index 80eae3b445..df1a368edc 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -5550,6 +5550,10 @@ STR_ERROR_NAME_MUST_BE_UNIQUE :{WHITE}Name mus STR_ERROR_GENERIC_OBJECT_IN_THE_WAY :{WHITE}{1:STRING} in the way STR_ERROR_NOT_ALLOWED_WHILE_PAUSED :{WHITE}Not allowed while paused +# Clear area query +STR_QUERY_CLEAR_AREA_CAPTION :{WHITE}Clear area +STR_CLEAR_AREA_CONFIRMATION_TEXT :{YELLOW}You are about to destroy an important structure. Are you sure? + # Local authority errors STR_ERROR_LOCAL_AUTHORITY_REFUSES_TO_ALLOW_THIS :{WHITE}{TOWN} local authority refuses to allow this STR_ERROR_LOCAL_AUTHORITY_REFUSES_AIRPORT :{WHITE}{TOWN} local authority refuses to allow another airport to be built in this town diff --git a/src/misc_gui.cpp b/src/misc_gui.cpp index 49eb34824b..3cc340d696 100644 --- a/src/misc_gui.cpp +++ b/src/misc_gui.cpp @@ -12,6 +12,8 @@ #include "landscape.h" #include "error.h" #include "gui.h" + +#include #include "command_func.h" #include "company_func.h" #include "town.h" @@ -1230,19 +1232,19 @@ void ShowQueryString(StringID str, StringID caption, uint maxsize, Window *paren * Window used for asking the user a YES/NO question. */ struct QueryWindow : public Window { - QueryCallbackProc *proc; ///< callback function executed on closing of popup. Window* points to parent, bool is true if 'yes' clicked, false otherwise + QueryCallbackProc proc; ///< callback function executed on closing of popup. Window* points to parent, bool is true if 'yes' clicked, false otherwise uint64 params[10]; ///< local copy of #_global_string_params StringID message; ///< message shown for query window StringID caption; ///< title of window - QueryWindow(WindowDesc *desc, StringID caption, StringID message, Window *parent, QueryCallbackProc *callback) : Window(desc) + QueryWindow(WindowDesc *desc, StringID caption, StringID message, Window *parent, QueryCallbackProc callback) : Window(desc) { /* Create a backup of the variadic arguments to strings because it will be * overridden pretty often. We will copy these back for drawing */ CopyOutDParam(this->params, 0, lengthof(this->params)); this->caption = caption; this->message = message; - this->proc = callback; + this->proc = std::move(callback); this->parent = parent; this->InitNested(WN_CONFIRM_POPUP_QUERY); @@ -1299,7 +1301,7 @@ struct QueryWindow : public Window { case WID_Q_YES: { /* in the Generate New World window, clicking 'Yes' causes * DeleteNonVitalWindows() to be called - we shouldn't be in a window then */ - QueryCallbackProc *proc = this->proc; + QueryCallbackProc proc = this->proc; Window *parent = this->parent; /* Prevent the destructor calling the callback function */ this->proc = nullptr; @@ -1364,9 +1366,9 @@ static WindowDesc _query_desc( * @param message string that will be shown for the window * @param parent pointer to parent window, if this pointer is nullptr the parent becomes * the main window WC_MAIN_WINDOW - * @param callback callback function pointer to set in the window descriptor + * @param callback callback function to set in the window descriptor */ -void ShowQuery(StringID caption, StringID message, Window *parent, QueryCallbackProc *callback) +void ShowQuery(StringID caption, StringID message, Window *parent, QueryCallbackProc callback) { if (parent == nullptr) parent = FindWindowById(WC_MAIN_WINDOW, 0); @@ -1374,7 +1376,7 @@ void ShowQuery(StringID caption, StringID message, Window *parent, QueryCallback if (w->window_class != WC_CONFIRM_POPUP_QUERY) continue; const QueryWindow *qw = (const QueryWindow *)w; - if (qw->parent != parent || qw->proc != callback) continue; + if (qw->parent != parent) continue; delete qw; break; diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp index 844326775b..e0563c04f0 100644 --- a/src/network/network_gui.cpp +++ b/src/network/network_gui.cpp @@ -2141,7 +2141,7 @@ public: case WID_CL_MATRIX: { StringID text = STR_NULL; - QueryCallbackProc *callback = nullptr; + QueryCallbackProc callback = nullptr; switch (index) { case DD_CLIENT_ADMIN_KICK: diff --git a/src/terraform_gui.cpp b/src/terraform_gui.cpp index 2db07278b4..17061601f9 100644 --- a/src/terraform_gui.cpp +++ b/src/terraform_gui.cpp @@ -30,6 +30,7 @@ #include "hotkeys.h" #include "engine_base.h" #include "terraform_gui.h" +#include "cheat_func.h" #include "town_gui.h" #include "zoom_func.h" @@ -95,6 +96,30 @@ static void GenerateRockyArea(TileIndex end, TileIndex start) if (success && _settings_client.sound.confirm) SndPlayTileFx(SND_1F_CONSTRUCTION_OTHER, end); } +/** Checks if the area contains any structures that are important enough to query about first */ +static bool IsIndustryOrRailStationInArea(TileIndex start_tile, TileIndex end_tile, bool diagonal) +{ + std::unique_ptr tile_iterator; + + if (diagonal) { + tile_iterator = std::make_unique(end_tile, start_tile); + } else { + tile_iterator = std::make_unique(end_tile, start_tile); + } + + bool destroying_industry_or_station = false; + + for (; *tile_iterator != INVALID_TILE; ++(*tile_iterator)) { + if ((_cheats.magic_bulldozer.value && IsTileType(*tile_iterator, MP_INDUSTRY)) || + IsRailStationTile(*tile_iterator)) { + destroying_industry_or_station = true; + break; + } + } + + return destroying_industry_or_station; +} + /** * A central place to handle all X_AND_Y dragged GUI functions. * @param proc Procedure related to the dragging @@ -114,9 +139,23 @@ bool GUIPlaceProcDragXY(ViewportDragDropSelectionProcess proc, TileIndex start_t } switch (proc) { - case DDSP_DEMOLISH_AREA: - DoCommandP(end_tile, start_tile, _ctrl_pressed ? 1 : 0, CMD_CLEAR_AREA | CMD_MSG(STR_ERROR_CAN_T_CLEAR_THIS_AREA), CcPlaySound_EXPLOSION); + case DDSP_DEMOLISH_AREA: { + const bool should_query_first = IsIndustryOrRailStationInArea(start_tile, end_tile, _ctrl_pressed); + + const auto cmd_clear_area = [=](Window*, bool confirmed) { + if (confirmed) { + DoCommandP(end_tile, start_tile, _ctrl_pressed ? 1 : 0, CMD_CLEAR_AREA | CMD_MSG(STR_ERROR_CAN_T_CLEAR_THIS_AREA), CcPlaySound_EXPLOSION); + } + }; + + if (should_query_first) { + ShowQuery(STR_QUERY_CLEAR_AREA_CAPTION, STR_CLEAR_AREA_CONFIRMATION_TEXT, nullptr, cmd_clear_area); + } else { + cmd_clear_area(nullptr, true); + } + break; + } case DDSP_RAISE_AND_LEVEL_AREA: DoCommandP(end_tile, start_tile, LM_RAISE << 1 | (_ctrl_pressed ? 1 : 0), CMD_LEVEL_LAND | CMD_MSG(STR_ERROR_CAN_T_RAISE_LAND_HERE), CcTerraform); break; diff --git a/src/textbuf_gui.h b/src/textbuf_gui.h index c96f4e2e06..a7a225eb49 100644 --- a/src/textbuf_gui.h +++ b/src/textbuf_gui.h @@ -10,10 +10,13 @@ #ifndef TEXTBUF_GUI_H #define TEXTBUF_GUI_H -#include "window_type.h" #include "string_type.h" #include "strings_type.h" +#include + +struct Window; + /** Flags used in ShowQueryString() call */ enum QueryStringFlags { QSF_NONE = 0, @@ -26,10 +29,10 @@ enum QueryStringFlags { DECLARE_ENUM_AS_BIT_SET(QueryStringFlags) /** Callback procedure for the ShowQuery method. */ -typedef void QueryCallbackProc(Window*, bool); +typedef std::function QueryCallbackProc; void ShowQueryString(StringID str, StringID caption, uint max_len, Window *parent, CharSetFilter afilter, QueryStringFlags flags); -void ShowQuery(StringID caption, StringID message, Window *w, QueryCallbackProc *callback); +void ShowQuery(StringID caption, StringID message, Window *w, QueryCallbackProc callback); /** The number of 'characters' on the on-screen keyboard. */ static const uint OSK_KEYBOARD_ENTRIES = 50; From a49d91fa3920d8949337d5bda7fc5aeb490aea52 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Tue, 15 Jun 2021 14:18:03 +0100 Subject: [PATCH 2/3] Use CommandContainer for demolish area callback state storage --- src/misc_gui.cpp | 16 +++++++--------- src/network/network_gui.cpp | 2 +- src/terraform_gui.cpp | 23 ++++++++++++----------- src/textbuf_gui.h | 9 +++------ 4 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/misc_gui.cpp b/src/misc_gui.cpp index 3cc340d696..49eb34824b 100644 --- a/src/misc_gui.cpp +++ b/src/misc_gui.cpp @@ -12,8 +12,6 @@ #include "landscape.h" #include "error.h" #include "gui.h" - -#include #include "command_func.h" #include "company_func.h" #include "town.h" @@ -1232,19 +1230,19 @@ void ShowQueryString(StringID str, StringID caption, uint maxsize, Window *paren * Window used for asking the user a YES/NO question. */ struct QueryWindow : public Window { - QueryCallbackProc proc; ///< callback function executed on closing of popup. Window* points to parent, bool is true if 'yes' clicked, false otherwise + QueryCallbackProc *proc; ///< callback function executed on closing of popup. Window* points to parent, bool is true if 'yes' clicked, false otherwise uint64 params[10]; ///< local copy of #_global_string_params StringID message; ///< message shown for query window StringID caption; ///< title of window - QueryWindow(WindowDesc *desc, StringID caption, StringID message, Window *parent, QueryCallbackProc callback) : Window(desc) + QueryWindow(WindowDesc *desc, StringID caption, StringID message, Window *parent, QueryCallbackProc *callback) : Window(desc) { /* Create a backup of the variadic arguments to strings because it will be * overridden pretty often. We will copy these back for drawing */ CopyOutDParam(this->params, 0, lengthof(this->params)); this->caption = caption; this->message = message; - this->proc = std::move(callback); + this->proc = callback; this->parent = parent; this->InitNested(WN_CONFIRM_POPUP_QUERY); @@ -1301,7 +1299,7 @@ struct QueryWindow : public Window { case WID_Q_YES: { /* in the Generate New World window, clicking 'Yes' causes * DeleteNonVitalWindows() to be called - we shouldn't be in a window then */ - QueryCallbackProc proc = this->proc; + QueryCallbackProc *proc = this->proc; Window *parent = this->parent; /* Prevent the destructor calling the callback function */ this->proc = nullptr; @@ -1366,9 +1364,9 @@ static WindowDesc _query_desc( * @param message string that will be shown for the window * @param parent pointer to parent window, if this pointer is nullptr the parent becomes * the main window WC_MAIN_WINDOW - * @param callback callback function to set in the window descriptor + * @param callback callback function pointer to set in the window descriptor */ -void ShowQuery(StringID caption, StringID message, Window *parent, QueryCallbackProc callback) +void ShowQuery(StringID caption, StringID message, Window *parent, QueryCallbackProc *callback) { if (parent == nullptr) parent = FindWindowById(WC_MAIN_WINDOW, 0); @@ -1376,7 +1374,7 @@ void ShowQuery(StringID caption, StringID message, Window *parent, QueryCallback if (w->window_class != WC_CONFIRM_POPUP_QUERY) continue; const QueryWindow *qw = (const QueryWindow *)w; - if (qw->parent != parent) continue; + if (qw->parent != parent || qw->proc != callback) continue; delete qw; break; diff --git a/src/network/network_gui.cpp b/src/network/network_gui.cpp index e0563c04f0..844326775b 100644 --- a/src/network/network_gui.cpp +++ b/src/network/network_gui.cpp @@ -2141,7 +2141,7 @@ public: case WID_CL_MATRIX: { StringID text = STR_NULL; - QueryCallbackProc callback = nullptr; + QueryCallbackProc *callback = nullptr; switch (index) { case DD_CLIENT_ADMIN_KICK: diff --git a/src/terraform_gui.cpp b/src/terraform_gui.cpp index 17061601f9..24345fc09f 100644 --- a/src/terraform_gui.cpp +++ b/src/terraform_gui.cpp @@ -120,6 +120,14 @@ static bool IsIndustryOrRailStationInArea(TileIndex start_tile, TileIndex end_ti return destroying_industry_or_station; } +static CommandContainer _demolish_area_command; + +static void DemolishAreaConfirmationCallback(Window*, bool confirmed) { + if (confirmed) { + DoCommandP(&_demolish_area_command); + } +} + /** * A central place to handle all X_AND_Y dragged GUI functions. * @param proc Procedure related to the dragging @@ -140,20 +148,13 @@ bool GUIPlaceProcDragXY(ViewportDragDropSelectionProcess proc, TileIndex start_t switch (proc) { case DDSP_DEMOLISH_AREA: { - const bool should_query_first = IsIndustryOrRailStationInArea(start_tile, end_tile, _ctrl_pressed); + _demolish_area_command = NewCommandContainerBasic(end_tile, start_tile, _ctrl_pressed ? 1 : 0, CMD_CLEAR_AREA | CMD_MSG(STR_ERROR_CAN_T_CLEAR_THIS_AREA), CcPlaySound_EXPLOSION); - const auto cmd_clear_area = [=](Window*, bool confirmed) { - if (confirmed) { - DoCommandP(end_tile, start_tile, _ctrl_pressed ? 1 : 0, CMD_CLEAR_AREA | CMD_MSG(STR_ERROR_CAN_T_CLEAR_THIS_AREA), CcPlaySound_EXPLOSION); - } - }; - - if (should_query_first) { - ShowQuery(STR_QUERY_CLEAR_AREA_CAPTION, STR_CLEAR_AREA_CONFIRMATION_TEXT, nullptr, cmd_clear_area); + if (IsIndustryOrRailStationInArea(start_tile, end_tile, _ctrl_pressed)) { + ShowQuery(STR_QUERY_CLEAR_AREA_CAPTION, STR_CLEAR_AREA_CONFIRMATION_TEXT, nullptr, DemolishAreaConfirmationCallback); } else { - cmd_clear_area(nullptr, true); + DemolishAreaConfirmationCallback(nullptr, true); } - break; } case DDSP_RAISE_AND_LEVEL_AREA: diff --git a/src/textbuf_gui.h b/src/textbuf_gui.h index a7a225eb49..c96f4e2e06 100644 --- a/src/textbuf_gui.h +++ b/src/textbuf_gui.h @@ -10,13 +10,10 @@ #ifndef TEXTBUF_GUI_H #define TEXTBUF_GUI_H +#include "window_type.h" #include "string_type.h" #include "strings_type.h" -#include - -struct Window; - /** Flags used in ShowQueryString() call */ enum QueryStringFlags { QSF_NONE = 0, @@ -29,10 +26,10 @@ enum QueryStringFlags { DECLARE_ENUM_AS_BIT_SET(QueryStringFlags) /** Callback procedure for the ShowQuery method. */ -typedef std::function QueryCallbackProc; +typedef void QueryCallbackProc(Window*, bool); void ShowQueryString(StringID str, StringID caption, uint max_len, Window *parent, CharSetFilter afilter, QueryStringFlags flags); -void ShowQuery(StringID caption, StringID message, Window *w, QueryCallbackProc callback); +void ShowQuery(StringID caption, StringID message, Window *w, QueryCallbackProc *callback); /** The number of 'characters' on the on-screen keyboard. */ static const uint OSK_KEYBOARD_ENTRIES = 50; From 696fb746b7ba779a57e7bbbfabb226095019c0c7 Mon Sep 17 00:00:00 2001 From: Jonathan G Rennison Date: Tue, 15 Jun 2021 18:46:12 +0100 Subject: [PATCH 3/3] Add setting for demolition confirmation mode --- src/lang/english.txt | 6 ++++++ src/settings_gui.cpp | 1 + src/settings_type.h | 1 + src/table/settings.ini | 13 +++++++++++++ src/terraform_gui.cpp | 14 +++++++++++--- 5 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/lang/english.txt b/src/lang/english.txt index df1a368edc..931aa740be 100644 --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -1453,6 +1453,12 @@ STR_CONFIG_SETTING_VEHICLE_NAMES_LONG :Long STR_CONFIG_SETTING_SHADED_TREES_ON_SLOPES :Shade trees on slopes: {STRING2} STR_CONFIG_SETTING_SHADED_TREES_ON_SLOPES_HELPTEXT :Change brightness of trees drawn on slopes. Improves the look of tree cover in mountainous areas. +STR_CONFIG_SETTING_DEMOLISH_CONFIRM_MODE :Ask before demolishing structures: {STRING2} +STR_CONFIG_SETTING_DEMOLISH_CONFIRM_MODE_HELPTEXT :Ask for confirmation before irreversibly demolishing structures. +STR_CONFIG_SETTING_DEMOLISH_CONFIRM_MODE_OFF :Off +STR_CONFIG_SETTING_DEMOLISH_CONFIRM_MODE_INDUSTRY :Industries +STR_CONFIG_SETTING_DEMOLISH_CONFIRM_MODE_ALL :Industries and rail stations + STR_CONFIG_SETTING_ADV_SIG_BRIDGE_TUN_MODES :Enable signals on bridges/tunnels advanced modes: {STRING2} STR_CONFIG_SETTING_ADV_SIG_BRIDGE_TUN_MODES_HELPTEXT :Enables use of advanced modes of signal simulation on bridges and tunnels. When disabled, bridges/tunnels which are not already in an advanced mode cannot be changed to an advanced mode, however other players may choose to enable this setting and use an advanced mode. diff --git a/src/settings_gui.cpp b/src/settings_gui.cpp index 06c43c1fb2..b9ae7b8eb8 100644 --- a/src/settings_gui.cpp +++ b/src/settings_gui.cpp @@ -1758,6 +1758,7 @@ static SettingsContainer &GetSettingsTree() construction->Add(new SettingEntry("gui.quick_goto")); construction->Add(new SettingEntry("gui.default_rail_type")); construction->Add(new SettingEntry("gui.default_road_type")); + construction->Add(new SettingEntry("gui.demolish_confirm_mode")); } SettingsPage *departureboards = interface->Add(new SettingsPage(STR_CONFIG_SETTING_INTERFACE_DEPARTUREBOARDS)); diff --git a/src/settings_type.h b/src/settings_type.h index e38788d014..74891092c3 100644 --- a/src/settings_type.h +++ b/src/settings_type.h @@ -220,6 +220,7 @@ struct GUISettings : public TimeSettings { uint8 linkgraph_colours; ///< linkgraph overlay colours uint8 vehicle_names; ///< Vehicle naming scheme bool shade_trees_on_slopes; ///< Shade trees on slopes + uint8 demolish_confirm_mode; ///< Demolition confirmation mode uint16 console_backlog_timeout; ///< the minimum amount of time items should be in the console backlog before they will be removed in ~3 seconds granularity. uint16 console_backlog_length; ///< the minimum amount of items in the console backlog before items will be removed. diff --git a/src/table/settings.ini b/src/table/settings.ini index b1000f307f..bef025d425 100644 --- a/src/table/settings.ini +++ b/src/table/settings.ini @@ -5467,6 +5467,19 @@ strhelp = STR_CONFIG_SETTING_SHADED_TREES_ON_SLOPES_HELPTEXT proc = RedrawScreen cat = SC_BASIC +[SDTC_VAR] +var = gui.demolish_confirm_mode +type = SLE_UINT8 +flags = SLF_NOT_IN_SAVE | SLF_NO_NETWORK_SYNC +guiflags = SGF_MULTISTRING +def = 2 +min = 0 +max = 2 +str = STR_CONFIG_SETTING_DEMOLISH_CONFIRM_MODE +strhelp = STR_CONFIG_SETTING_DEMOLISH_CONFIRM_MODE_HELPTEXT +strval = STR_CONFIG_SETTING_DEMOLISH_CONFIRM_MODE_OFF +cat = SC_BASIC + ; For the dedicated build we'll enable dates in logs by default. [SDTC_BOOL] ifdef = DEDICATED diff --git a/src/terraform_gui.cpp b/src/terraform_gui.cpp index 24345fc09f..253b55c1d4 100644 --- a/src/terraform_gui.cpp +++ b/src/terraform_gui.cpp @@ -40,6 +40,12 @@ #include "safeguards.h" +enum DemolishConfirmMode { + DCM_OFF, + DCM_INDUSTRY, + DCM_INDUSTRY_RAIL_STATION, +}; + void CcTerraform(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2, uint64 p3, uint32 cmd) { if (result.Succeeded()) { @@ -97,8 +103,10 @@ static void GenerateRockyArea(TileIndex end, TileIndex start) } /** Checks if the area contains any structures that are important enough to query about first */ -static bool IsIndustryOrRailStationInArea(TileIndex start_tile, TileIndex end_tile, bool diagonal) +static bool IsQueryConfirmIndustryOrRailStationInArea(TileIndex start_tile, TileIndex end_tile, bool diagonal) { + if (_settings_client.gui.demolish_confirm_mode == DCM_OFF) return false; + std::unique_ptr tile_iterator; if (diagonal) { @@ -111,7 +119,7 @@ static bool IsIndustryOrRailStationInArea(TileIndex start_tile, TileIndex end_ti for (; *tile_iterator != INVALID_TILE; ++(*tile_iterator)) { if ((_cheats.magic_bulldozer.value && IsTileType(*tile_iterator, MP_INDUSTRY)) || - IsRailStationTile(*tile_iterator)) { + (_settings_client.gui.demolish_confirm_mode == DCM_INDUSTRY_RAIL_STATION && IsRailStationTile(*tile_iterator))) { destroying_industry_or_station = true; break; } @@ -150,7 +158,7 @@ bool GUIPlaceProcDragXY(ViewportDragDropSelectionProcess proc, TileIndex start_t case DDSP_DEMOLISH_AREA: { _demolish_area_command = NewCommandContainerBasic(end_tile, start_tile, _ctrl_pressed ? 1 : 0, CMD_CLEAR_AREA | CMD_MSG(STR_ERROR_CAN_T_CLEAR_THIS_AREA), CcPlaySound_EXPLOSION); - if (IsIndustryOrRailStationInArea(start_tile, end_tile, _ctrl_pressed)) { + if (IsQueryConfirmIndustryOrRailStationInArea(start_tile, end_tile, _ctrl_pressed)) { ShowQuery(STR_QUERY_CLEAR_AREA_CAPTION, STR_CLEAR_AREA_CONFIRMATION_TEXT, nullptr, DemolishAreaConfirmationCallback); } else { DemolishAreaConfirmationCallback(nullptr, true);