mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-16 11:33:03 +01:00
Implement strings for GameAction errors
This commit is contained in:
@@ -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())
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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<rct_string_id>(&res->ErrorMessage))
|
||||
gGameCommandErrorText = *error;
|
||||
else
|
||||
gGameCommandErrorText = STR_NONE;
|
||||
_trackPlaceCost = MONEY32_UNDEFINED;
|
||||
}
|
||||
else
|
||||
|
||||
@@ -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<rct_string_id>(&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<rct_string_id>(&res->ErrorMessage))
|
||||
{
|
||||
break;
|
||||
if (*message == STR_NOT_ENOUGH_CASH_REQUIRES || *message == STR_CAN_ONLY_BUILD_THIS_ON_WATER)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (zAttemptRange != 1)
|
||||
|
||||
@@ -23,11 +23,15 @@
|
||||
#include <openrct2/ride/TrackDesign.h>
|
||||
#include <openrct2/ride/TrackDesignRepository.h>
|
||||
#include <openrct2/sprites.h>
|
||||
#include <openrct2/ui/UiContext.h>
|
||||
#include <openrct2/ui/WindowManager.h>
|
||||
#include <openrct2/windows/Intent.h>
|
||||
#include <openrct2/world/Park.h>
|
||||
#include <openrct2/world/Surface.h>
|
||||
#include <vector>
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <openrct2/common.h>
|
||||
#include <openrct2/ride/Ride.h>
|
||||
#include <openrct2/windows/tile_inspector.h>
|
||||
#include <string_view>
|
||||
|
||||
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);
|
||||
|
||||
@@ -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 <algorithm>
|
||||
#include <iterator>
|
||||
|
||||
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<std::string>(&ErrorTitle))
|
||||
{
|
||||
titlez = *title;
|
||||
}
|
||||
else
|
||||
{
|
||||
titlez = format_string(std::get<rct_string_id>(ErrorTitle), nullptr);
|
||||
}
|
||||
return titlez;
|
||||
}
|
||||
|
||||
std::string GameActionResult::GetErrorMessage() const
|
||||
{
|
||||
std::string messagez;
|
||||
if (auto message = std::get_if<std::string>(&ErrorMessage))
|
||||
{
|
||||
messagez = *message;
|
||||
}
|
||||
else
|
||||
{
|
||||
messagez = format_string(std::get<rct_string_id>(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;
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
/**
|
||||
* Common error codes for game actions.
|
||||
@@ -69,8 +70,8 @@ public:
|
||||
using Ptr = std::unique_ptr<GameActionResult>;
|
||||
|
||||
GA_ERROR Error = GA_ERROR::OK;
|
||||
rct_string_id ErrorTitle = STR_NONE;
|
||||
rct_string_id ErrorMessage = STR_NONE;
|
||||
std::variant<rct_string_id, std::string> ErrorTitle = STR_NONE;
|
||||
std::variant<rct_string_id, std::string> ErrorMessage = STR_NONE;
|
||||
std::array<uint8_t, 32> 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
|
||||
|
||||
@@ -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<rct_string_id>(&res->ErrorTitle))
|
||||
gGameCommandErrorTitle = *title;
|
||||
else
|
||||
gGameCommandErrorTitle = STR_NONE;
|
||||
if (auto message = std::get_if<rct_string_id>(&res->ErrorMessage))
|
||||
gGameCommandErrorText = *message;
|
||||
else
|
||||
gGameCommandErrorText = STR_NONE;
|
||||
|
||||
if (res->Error != GA_ERROR::OK)
|
||||
{
|
||||
|
||||
@@ -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<rct_string_id>(res->ErrorTitle));
|
||||
} // switch selectionType
|
||||
|
||||
// Raise / lower the land tool selection area
|
||||
|
||||
@@ -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<rct_string_id>(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<rct_string_id>(res->ErrorTitle), gGameCommandErrorText, gCommonFormatArgs);
|
||||
}
|
||||
|
||||
money32 price = (((RideTrackCosts[ride->type].track_price * TrackPricing[TRACK_ELEM_MAZE]) >> 16));
|
||||
|
||||
@@ -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<rct_string_id>(res->ErrorTitle), gGameCommandErrorText, gCommonFormatArgs);
|
||||
}
|
||||
|
||||
if (gMapGroundFlags & ELEMENT_IS_UNDERWATER)
|
||||
|
||||
@@ -20,4 +20,19 @@ template<typename T> DukValue GetObjectAsDukValue(duk_context* ctx, const std::s
|
||||
return DukValue::take_from_stack(ctx);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
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
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -732,10 +732,10 @@ std::unique_ptr<GameActionResult> ScriptEngine::QueryOrExecuteCustomGameAction(
|
||||
std::unique_ptr<GameActionResult> ScriptEngine::DukToGameActionResult(const DukValue& d)
|
||||
{
|
||||
auto result = std::make_unique<GameActionResult>();
|
||||
result->Error = d["error"].type() == DukValue::Type::NUMBER ? static_cast<GA_ERROR>(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<GA_ERROR>(AsOrDefault<int32_t>(d["error"]));
|
||||
result->ErrorTitle = AsOrDefault<std::string>(d["errorTitle"]);
|
||||
result->ErrorMessage = AsOrDefault<std::string>(d["errorMessage"]);
|
||||
result->Cost = AsOrDefault<int32_t>(d["cost"]);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<rct_string_id>(&res->ErrorMessage))
|
||||
gGameCommandErrorText = *message;
|
||||
else
|
||||
gGameCommandErrorText = STR_NONE;
|
||||
std::copy(res->ErrorMessageArgs.begin(), res->ErrorMessageArgs.end(), gCommonFormatArgs);
|
||||
if (price != nullptr)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user