diff --git a/src/openrct2-ui/WindowManager.cpp b/src/openrct2-ui/WindowManager.cpp index 869626a465..2a79bf564d 100644 --- a/src/openrct2-ui/WindowManager.cpp +++ b/src/openrct2-ui/WindowManager.cpp @@ -207,6 +207,11 @@ public: return window_error_open(title, message); } + rct_window* ShowError(const std::string_view& title, const std::string_view& message) override + { + return window_error_open(title, message); + } + rct_window* OpenIntent(Intent* intent) override { switch (intent->GetWindowClass()) diff --git a/src/openrct2-ui/windows/Error.cpp b/src/openrct2-ui/windows/Error.cpp index b7cad6960d..5e6f2101fd 100644 --- a/src/openrct2-ui/windows/Error.cpp +++ b/src/openrct2-ui/windows/Error.cpp @@ -62,7 +62,7 @@ static rct_window_event_list window_error_events = { }; // clang-format on -static char _window_error_text[512]; +static std::string _window_error_text; static uint16_t _window_error_num_lines; /** @@ -74,30 +74,38 @@ static uint16_t _window_error_num_lines; */ rct_window* window_error_open(rct_string_id title, rct_string_id message) { - utf8* dst; + auto titlez = format_string(title, gCommonFormatArgs); + auto messagez = format_string(message, gCommonFormatArgs); + return window_error_open(titlez, messagez); +} + +rct_window* window_error_open(const std::string_view& title, const std::string_view& message) +{ int32_t numLines, fontHeight, width, height, maxY; rct_window* w; window_close_by_class(WC_ERROR); - dst = _window_error_text; + auto& buffer = _window_error_text; + buffer.clear(); // Format the title - dst = utf8_write_codepoint(dst, FORMAT_BLACK); - if (title != STR_NONE) { - format_string(dst, 512 - (dst - _window_error_text), title, gCommonFormatArgs); - dst = get_string_end(dst); + char temp[8]{}; + utf8_write_codepoint(temp, FORMAT_BLACK); + buffer.append(temp); } + buffer.append(title); // Format the message - if (message != STR_NONE) + if (!message.empty()) { - dst = utf8_write_codepoint(dst, FORMAT_NEWLINE); - format_string(dst, 512 - (dst - _window_error_text), message, gCommonFormatArgs); - dst = get_string_end(dst); + char temp[8]{}; + utf8_write_codepoint(temp, FORMAT_NEWLINE); + buffer.append(temp); + buffer.append(message); } - log_verbose("show error, %s", _window_error_text + 1); + log_verbose("show error, %s", buffer.c_str() + 1); // Don't do unnecessary work in headless. Also saves checking if cursor state is null. if (gOpenRCT2Headless) @@ -106,15 +114,15 @@ rct_window* window_error_open(rct_string_id title, rct_string_id message) } // Check if there is any text to display - if (dst == _window_error_text + 1) + if (buffer.size() <= 1) return nullptr; gCurrentFontSpriteBase = FONT_SPRITE_BASE_MEDIUM; - width = gfx_get_string_width_new_lined(_window_error_text); + width = gfx_get_string_width_new_lined(buffer.data()); width = std::clamp(width, 64, 196); gCurrentFontSpriteBase = FONT_SPRITE_BASE_MEDIUM; - gfx_wrap_string(_window_error_text, width + 1, &numLines, &fontHeight); + gfx_wrap_string(buffer.data(), width + 1, &numLines, &fontHeight); _window_error_num_lines = numLines; width = width + 3; @@ -186,5 +194,5 @@ static void window_error_paint(rct_window* w, rct_drawpixelinfo* dpi) l = w->windowPos.x + (w->width + 1) / 2 - 1; t = w->windowPos.y + 1; - draw_string_centred_raw(dpi, l, t, _window_error_num_lines, _window_error_text); + draw_string_centred_raw(dpi, l, t, _window_error_num_lines, _window_error_text.data()); } diff --git a/src/openrct2-ui/windows/RideConstruction.cpp b/src/openrct2-ui/windows/RideConstruction.cpp index 8fc121ec55..f875773cc6 100644 --- a/src/openrct2-ui/windows/RideConstruction.cpp +++ b/src/openrct2-ui/windows/RideConstruction.cpp @@ -1824,7 +1824,10 @@ static void window_ride_construction_construct(rct_window* w) // Used by some functions if (res->Error != GA_ERROR::OK) { - gGameCommandErrorText = res->ErrorMessage; + if (auto error = std::get_if(&res->ErrorMessage)) + gGameCommandErrorText = *error; + else + gGameCommandErrorText = STR_NONE; _trackPlaceCost = MONEY32_UNDEFINED; } else diff --git a/src/openrct2-ui/windows/TopToolbar.cpp b/src/openrct2-ui/windows/TopToolbar.cpp index 0ddd5ade53..dc04ebfb0b 100644 --- a/src/openrct2-ui/windows/TopToolbar.cpp +++ b/src/openrct2-ui/windows/TopToolbar.cpp @@ -1947,9 +1947,12 @@ static void window_top_toolbar_scenery_tool_down(int16_t x, int16_t y, rct_windo break; } - if (res->ErrorMessage == STR_NOT_ENOUGH_CASH_REQUIRES || res->ErrorMessage == STR_CAN_ONLY_BUILD_THIS_ON_WATER) + if (auto message = std::get_if(&res->ErrorMessage)) { - break; + if (*message == STR_NOT_ENOUGH_CASH_REQUIRES || *message == STR_CAN_ONLY_BUILD_THIS_ON_WATER) + { + break; + } } if (zAttemptRange != 1) @@ -1997,9 +2000,12 @@ static void window_top_toolbar_scenery_tool_down(int16_t x, int16_t y, rct_windo break; } - if (res->ErrorMessage == STR_NOT_ENOUGH_CASH_REQUIRES || res->ErrorMessage == STR_CAN_ONLY_BUILD_THIS_ON_WATER) + if (auto message = std::get_if(&res->ErrorMessage)) { - break; + if (*message == STR_NOT_ENOUGH_CASH_REQUIRES || *message == STR_CAN_ONLY_BUILD_THIS_ON_WATER) + { + break; + } } if (zAttemptRange != 1) diff --git a/src/openrct2-ui/windows/TrackDesignPlace.cpp b/src/openrct2-ui/windows/TrackDesignPlace.cpp index 8d8b2ce063..fc40213817 100644 --- a/src/openrct2-ui/windows/TrackDesignPlace.cpp +++ b/src/openrct2-ui/windows/TrackDesignPlace.cpp @@ -23,11 +23,15 @@ #include #include #include +#include +#include #include #include #include #include +using namespace OpenRCT2; + constexpr int16_t TRACK_MINI_PREVIEW_WIDTH = 168; constexpr int16_t TRACK_MINI_PREVIEW_HEIGHT = 78; constexpr uint16_t TRACK_MINI_PREVIEW_SIZE = TRACK_MINI_PREVIEW_WIDTH * TRACK_MINI_PREVIEW_HEIGHT; @@ -391,8 +395,9 @@ static void window_track_place_tooldown(rct_window* w, rct_widgetindex widgetInd // Unable to build track audio_play_sound_at_location(SoundId::Error, trackLoc); - std::copy(res->ErrorMessageArgs.begin(), res->ErrorMessageArgs.end(), gCommonFormatArgs); - context_show_error(res->ErrorTitle, res->ErrorMessage); + + auto windowManager = GetContext()->GetUiContext()->GetWindowManager(); + windowManager->ShowError(res->GetErrorTitle(), res->GetErrorMessage()); } /** diff --git a/src/openrct2-ui/windows/Window.h b/src/openrct2-ui/windows/Window.h index 75c7f67d77..b5894144dd 100644 --- a/src/openrct2-ui/windows/Window.h +++ b/src/openrct2-ui/windows/Window.h @@ -13,6 +13,7 @@ #include #include #include +#include using loadsave_callback = void (*)(int32_t result, const utf8* path); using scenarioselect_callback = void (*)(const utf8* path); @@ -105,6 +106,7 @@ void window_title_command_editor_open(struct TitleSequence* sequence, int32_t co rct_window* window_scenarioselect_open(scenarioselect_callback callback, bool titleEditor); rct_window* window_error_open(rct_string_id title, rct_string_id message); +rct_window* window_error_open(const std::string_view& title, const std::string_view& message); struct TrackDesign; rct_window* window_loadsave_open(int32_t type, const char* defaultName, loadsave_callback callback, TrackDesign* t6Exporter); rct_window* window_track_place_open(const struct track_design_file_ref* tdFileRef); diff --git a/src/openrct2/actions/GameAction.cpp b/src/openrct2/actions/GameAction.cpp index 7462e05140..03cb680b93 100644 --- a/src/openrct2/actions/GameAction.cpp +++ b/src/openrct2/actions/GameAction.cpp @@ -18,12 +18,16 @@ #include "../network/network.h" #include "../platform/platform.h" #include "../scenario/Scenario.h" +#include "../ui/UiContext.h" +#include "../ui/WindowManager.h" #include "../world/Park.h" #include "../world/Scenery.h" #include #include +using namespace OpenRCT2; + GameActionResult::GameActionResult(GA_ERROR error, rct_string_id message) { Error = error; @@ -45,6 +49,34 @@ GameActionResult::GameActionResult(GA_ERROR error, rct_string_id title, rct_stri std::copy_n(args, ErrorMessageArgs.size(), ErrorMessageArgs.begin()); } +std::string GameActionResult::GetErrorTitle() const +{ + std::string titlez; + if (auto title = std::get_if(&ErrorTitle)) + { + titlez = *title; + } + else + { + titlez = format_string(std::get(ErrorTitle), nullptr); + } + return titlez; +} + +std::string GameActionResult::GetErrorMessage() const +{ + std::string messagez; + if (auto message = std::get_if(&ErrorMessage)) + { + messagez = *message; + } + else + { + messagez = format_string(std::get(ErrorMessage), ErrorMessageArgs.data()); + } + return messagez; +} + namespace GameActions { struct QueuedGameAction @@ -486,9 +518,8 @@ namespace GameActions if (result->Error != GA_ERROR::OK && shouldShowError) { - // Show the error box - std::copy(result->ErrorMessageArgs.begin(), result->ErrorMessageArgs.end(), gCommonFormatArgs); - context_show_error(result->ErrorTitle, result->ErrorMessage); + auto windowManager = GetContext()->GetUiContext()->GetWindowManager(); + windowManager->ShowError(result->GetErrorTitle(), result->GetErrorMessage()); } return result; diff --git a/src/openrct2/actions/GameAction.h b/src/openrct2/actions/GameAction.h index ab3005901b..474dddc6a0 100644 --- a/src/openrct2/actions/GameAction.h +++ b/src/openrct2/actions/GameAction.h @@ -20,6 +20,7 @@ #include #include #include +#include /** * Common error codes for game actions. @@ -69,8 +70,8 @@ public: using Ptr = std::unique_ptr; GA_ERROR Error = GA_ERROR::OK; - rct_string_id ErrorTitle = STR_NONE; - rct_string_id ErrorMessage = STR_NONE; + std::variant ErrorTitle = STR_NONE; + std::variant ErrorMessage = STR_NONE; std::array ErrorMessageArgs; CoordsXYZ Position = { LOCATION_NULL, LOCATION_NULL, LOCATION_NULL }; money32 Cost = 0; @@ -82,6 +83,9 @@ public: GameActionResult(GA_ERROR error, rct_string_id title, rct_string_id message, uint8_t* args); GameActionResult(const GameActionResult&) = delete; virtual ~GameActionResult(){}; + + std::string GetErrorTitle() const; + std::string GetErrorMessage() const; }; struct GameAction diff --git a/src/openrct2/actions/GameActionCompat.cpp b/src/openrct2/actions/GameActionCompat.cpp index d389c7cae5..aebf267c9f 100644 --- a/src/openrct2/actions/GameActionCompat.cpp +++ b/src/openrct2/actions/GameActionCompat.cpp @@ -140,8 +140,14 @@ money32 maze_set_track( // NOTE: ride_construction_tooldown_construct requires them to be set. // Refactor result type once theres no C code referencing this function. - gGameCommandErrorText = res->ErrorMessage; - gGameCommandErrorTitle = res->ErrorTitle; + if (auto title = std::get_if(&res->ErrorTitle)) + gGameCommandErrorTitle = *title; + else + gGameCommandErrorTitle = STR_NONE; + if (auto message = std::get_if(&res->ErrorMessage)) + gGameCommandErrorText = *message; + else + gGameCommandErrorText = STR_NONE; if (res->Error != GA_ERROR::OK) { diff --git a/src/openrct2/actions/LandSmoothAction.hpp b/src/openrct2/actions/LandSmoothAction.hpp index 502bc7b3aa..3d50624403 100644 --- a/src/openrct2/actions/LandSmoothAction.hpp +++ b/src/openrct2/actions/LandSmoothAction.hpp @@ -642,7 +642,7 @@ private: } default: log_error("Invalid map selection %u", _selectionType); - return MakeResult(GA_ERROR::INVALID_PARAMETERS, res->ErrorTitle); + return MakeResult(GA_ERROR::INVALID_PARAMETERS, std::get(res->ErrorTitle)); } // switch selectionType // Raise / lower the land tool selection area diff --git a/src/openrct2/actions/MazePlaceTrackAction.hpp b/src/openrct2/actions/MazePlaceTrackAction.hpp index 079c1a2c21..18c723399a 100644 --- a/src/openrct2/actions/MazePlaceTrackAction.hpp +++ b/src/openrct2/actions/MazePlaceTrackAction.hpp @@ -93,7 +93,7 @@ public: { _loc.ToTileStart(), baseHeight, clearanceHeight }, &map_place_non_scenery_clear_func, { 0b1111, 0 }, GetFlags(), &clearCost, CREATE_CROSSING_MODE_NONE)) { - return MakeResult(GA_ERROR::NO_CLEARANCE, res->ErrorTitle, gGameCommandErrorText, gCommonFormatArgs); + return MakeResult(GA_ERROR::NO_CLEARANCE, std::get(res->ErrorTitle), gGameCommandErrorText, gCommonFormatArgs); } if (gMapGroundFlags & ELEMENT_IS_UNDERWATER) @@ -162,7 +162,7 @@ public: { _loc.ToTileStart(), baseHeight, clearanceHeight }, &map_place_non_scenery_clear_func, { 0b1111, 0 }, GetFlags() | GAME_COMMAND_FLAG_APPLY, &clearCost, CREATE_CROSSING_MODE_NONE)) { - return MakeResult(GA_ERROR::NO_CLEARANCE, res->ErrorTitle, gGameCommandErrorText, gCommonFormatArgs); + return MakeResult(GA_ERROR::NO_CLEARANCE, std::get(res->ErrorTitle), gGameCommandErrorText, gCommonFormatArgs); } money32 price = (((RideTrackCosts[ride->type].track_price * TrackPricing[TRACK_ELEM_MAZE]) >> 16)); diff --git a/src/openrct2/actions/MazeSetTrackAction.hpp b/src/openrct2/actions/MazeSetTrackAction.hpp index 4e980be78a..7dc8adeeb5 100644 --- a/src/openrct2/actions/MazeSetTrackAction.hpp +++ b/src/openrct2/actions/MazeSetTrackAction.hpp @@ -136,7 +136,8 @@ public: if (!map_can_construct_at({ _loc.ToTileStart(), baseHeight, clearanceHeight }, { 0b1111, 0 })) { - return MakeResult(GA_ERROR::NO_CLEARANCE, res->ErrorTitle, gGameCommandErrorText, gCommonFormatArgs); + return MakeResult( + GA_ERROR::NO_CLEARANCE, std::get(res->ErrorTitle), gGameCommandErrorText, gCommonFormatArgs); } if (gMapGroundFlags & ELEMENT_IS_UNDERWATER) diff --git a/src/openrct2/scripting/Duktape.hpp b/src/openrct2/scripting/Duktape.hpp index 280f5293fe..d6e695d482 100644 --- a/src/openrct2/scripting/Duktape.hpp +++ b/src/openrct2/scripting/Duktape.hpp @@ -20,4 +20,19 @@ template DukValue GetObjectAsDukValue(duk_context* ctx, const std::s return DukValue::take_from_stack(ctx); } +template +const T AsOrDefault(const DukValue& value, const T& defaultValue = {}) = delete; + +template<> +inline const std::string AsOrDefault(const DukValue& value, const std::string& defaultValue) +{ + return value.type() == DukValue::STRING ? value.as_string() : defaultValue; +} + +template<> +inline const int32_t AsOrDefault(const DukValue& value, const int32_t& defaultValue) +{ + return value.type() == DukValue::NUMBER ? value.as_int() : defaultValue; +} + #endif diff --git a/src/openrct2/scripting/ScContext.hpp b/src/openrct2/scripting/ScContext.hpp index f6e72fbf1e..75f8abc26f 100644 --- a/src/openrct2/scripting/ScContext.hpp +++ b/src/openrct2/scripting/ScContext.hpp @@ -160,11 +160,11 @@ namespace OpenRCT2::Scripting if (res.Error != GA_ERROR::OK) { - auto title = format_string(res.ErrorTitle, nullptr); + auto title = res.GetErrorTitle(); duk_push_string(ctx, title.c_str()); duk_put_prop_string(ctx, objIdx, "errorTitle"); - auto message = format_string(res.ErrorMessage, res.ErrorMessageArgs.data()); + auto message = res.GetErrorMessage(); duk_push_string(ctx, message.c_str()); duk_put_prop_string(ctx, objIdx, "errorMessage"); } diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp index 95b5efc74d..981bae7005 100644 --- a/src/openrct2/scripting/ScriptEngine.cpp +++ b/src/openrct2/scripting/ScriptEngine.cpp @@ -732,10 +732,10 @@ std::unique_ptr ScriptEngine::QueryOrExecuteCustomGameAction( std::unique_ptr ScriptEngine::DukToGameActionResult(const DukValue& d) { auto result = std::make_unique(); - result->Error = d["error"].type() == DukValue::Type::NUMBER ? static_cast(d["error"].as_int()) : GA_ERROR::OK; - auto errorTitle = d["errorTitle"].type() == DukValue::Type::STRING ? d["errorTitle"].as_string() : std::string(); - auto errorMessage = d["errorMessage"].type() == DukValue::Type::STRING ? d["errorMessage"].as_string() : std::string(); - result->Cost = d["cost"].type() == DukValue::Type::NUMBER ? d["cost"].as_int() : 0; + result->Error = static_cast(AsOrDefault(d["error"])); + result->ErrorTitle = AsOrDefault(d["errorTitle"]); + result->ErrorMessage = AsOrDefault(d["errorMessage"]); + result->Cost = AsOrDefault(d["cost"]); return result; } diff --git a/src/openrct2/ui/DummyWindowManager.cpp b/src/openrct2/ui/DummyWindowManager.cpp index 8d1c6ab0a7..42c8d071ef 100644 --- a/src/openrct2/ui/DummyWindowManager.cpp +++ b/src/openrct2/ui/DummyWindowManager.cpp @@ -30,6 +30,10 @@ namespace OpenRCT2::Ui { return nullptr; } + rct_window* ShowError(const std::string_view& /*title*/, const std::string_view& /*message*/) override + { + return nullptr; + } rct_window* OpenIntent(Intent* /*intent*/) override { return nullptr; diff --git a/src/openrct2/ui/WindowManager.h b/src/openrct2/ui/WindowManager.h index 1392b68db9..fe49810854 100644 --- a/src/openrct2/ui/WindowManager.h +++ b/src/openrct2/ui/WindowManager.h @@ -30,6 +30,7 @@ namespace OpenRCT2::Ui virtual rct_window* OpenIntent(Intent * intent) abstract; virtual void BroadcastIntent(const Intent& intent) abstract; virtual rct_window* ShowError(rct_string_id title, rct_string_id message) abstract; + virtual rct_window* ShowError(const std::string_view& title, const std::string_view& message) abstract; virtual void ForceClose(rct_windowclass windowClass) abstract; virtual void UpdateMapTooltip() abstract; virtual void HandleInput() abstract; diff --git a/src/openrct2/world/Map.cpp b/src/openrct2/world/Map.cpp index 2937cec6ef..469de6cd97 100644 --- a/src/openrct2/world/Map.cpp +++ b/src/openrct2/world/Map.cpp @@ -1481,7 +1481,10 @@ bool map_can_construct_with_clear_at( uint8_t crossingMode) { GameActionResult::Ptr res = map_can_construct_with_clear_at(pos, clearFunc, quarterTile, flags, crossingMode); - gGameCommandErrorText = res->ErrorMessage; + if (auto message = std::get_if(&res->ErrorMessage)) + gGameCommandErrorText = *message; + else + gGameCommandErrorText = STR_NONE; std::copy(res->ErrorMessageArgs.begin(), res->ErrorMessageArgs.end(), gCommonFormatArgs); if (price != nullptr) {