1
0
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:
Ted John
2020-03-03 21:11:26 +00:00
parent 2ad37db817
commit 83fe0cf5c5
18 changed files with 135 additions and 41 deletions

View File

@@ -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())

View File

@@ -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());
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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());
}
/**

View File

@@ -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);

View File

@@ -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;

View File

@@ -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

View File

@@ -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)
{

View File

@@ -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

View File

@@ -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));

View File

@@ -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)

View File

@@ -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

View File

@@ -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");
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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)
{