diff --git a/src/autoreplace_cmd.cpp b/src/autoreplace_cmd.cpp --- a/src/autoreplace_cmd.cpp +++ b/src/autoreplace_cmd.cpp @@ -442,7 +442,7 @@ static CommandCost ReplaceFreeUnit(Vehic Train *old_v = Train::From(*single_unit); assert(!old_v->IsArticulatedPart() && !old_v->IsRearDualheaded()); - CommandCost cost = CommandCost(EXPENSES_NEW_VEHICLES, 0); + CommandCost cost = CommandCost(EXPENSES_NEW_VEHICLES, (Money)0); /* Build and refit replacement vehicle */ Vehicle *new_v = nullptr; @@ -494,7 +494,7 @@ static CommandCost ReplaceChain(Vehicle Vehicle *old_head = *chain; assert(old_head->IsPrimaryVehicle()); - CommandCost cost = CommandCost(EXPENSES_NEW_VEHICLES, 0); + CommandCost cost = CommandCost(EXPENSES_NEW_VEHICLES, (Money)0); if (old_head->type == VEH_TRAIN) { /* Store the length of the old vehicle chain, rounded up to whole tiles */ @@ -753,7 +753,7 @@ CommandCost CmdAutoreplaceVehicle(DoComm w = (!free_wagon && w->type == VEH_TRAIN ? Train::From(w)->GetNextUnit() : nullptr); } - CommandCost cost = CommandCost(EXPENSES_NEW_VEHICLES, 0); + CommandCost cost = CommandCost(EXPENSES_NEW_VEHICLES, (Money)0); bool nothing_to_do = true; if (any_replacements) { diff --git a/src/command.cpp b/src/command.cpp --- a/src/command.cpp +++ b/src/command.cpp @@ -259,7 +259,7 @@ void CommandHelperBase::InternalPostResu if (res.Failed()) { /* Only show the error when it's for us. */ if (estimate_only || (IsLocalCompany() && err_message != 0 && my_cmd)) { - ShowErrorMessage(err_message, res.GetErrorMessage(), WL_INFO, x, y, res.GetTextRefStackGRF(), res.GetTextRefStackSize(), res.GetTextRefStack()); + ShowErrorMessage(err_message, x, y, res); } } else if (estimate_only) { ShowEstimatedCostOrIncome(res.GetCost(), x, y); diff --git a/src/command_type.h b/src/command_type.h --- a/src/command_type.h +++ b/src/command_type.h @@ -22,12 +22,13 @@ struct GRFFile; * a possible error message/state together. */ class CommandCost { - ExpensesType expense_type; ///< the type of expence as shown on the finances view - Money cost; ///< The cost of this action - StringID message; ///< Warning message for when success is unset - bool success; ///< Whether the command went fine up to this moment - const GRFFile *textref_stack_grffile; ///< NewGRF providing the #TextRefStack content. - uint textref_stack_size; ///< Number of uint32 values to put on the #TextRefStack for the error message. + ExpensesType expense_type; ///< the type of expence as shown on the finances view + Money cost; ///< The cost of this action + StringID message; ///< Warning message for when success is unset + bool success; ///< Whether the command went fine up to this moment + const GRFFile *textref_stack_grffile; ///< NewGRF providing the #TextRefStack content. + uint textref_stack_size; ///< Number of uint32 values to put on the #TextRefStack for the error message. + StringID extra_message = INVALID_STRING_ID; ///< Additional warning message for when success is unset static uint32 textref_stack[16]; @@ -38,9 +39,9 @@ public: CommandCost() : expense_type(INVALID_EXPENSES), cost(0), message(INVALID_STRING_ID), success(true), textref_stack_grffile(nullptr), textref_stack_size(0) {} /** - * Creates a command return value the is failed with the given message + * Creates a command return value with one, or optionally two, error message strings. */ - explicit CommandCost(StringID msg) : expense_type(INVALID_EXPENSES), cost(0), message(msg), success(false), textref_stack_grffile(nullptr), textref_stack_size(0) {} + explicit CommandCost(StringID msg, StringID extra_msg = INVALID_STRING_ID) : expense_type(INVALID_EXPENSES), cost(0), message(msg), success(false), textref_stack_grffile(nullptr), textref_stack_size(0), extra_message(extra_msg) {} /** * Creates a command cost with given expense type and start cost of 0 @@ -98,11 +99,12 @@ public: * Makes this #CommandCost behave like an error command. * @param message The error message. */ - void MakeError(StringID message) + void MakeError(StringID message, StringID extra_message = INVALID_STRING_ID) { assert(message != INVALID_STRING_ID); this->success = false; this->message = message; + this->extra_message = extra_message; } void UseTextRefStack(const GRFFile *grffile, uint num_registers); @@ -145,6 +147,16 @@ public: } /** + * Returns the extra error message of a command + * @return the extra error message, if succeeded #INVALID_STRING_ID + */ + StringID GetExtraErrorMessage() const + { + if (this->success) return INVALID_STRING_ID; + return this->extra_message; + } + + /** * Did this command succeed? * @return true if and only if it succeeded */ diff --git a/src/error.h b/src/error.h --- a/src/error.h +++ b/src/error.h @@ -13,6 +13,7 @@ #include #include "strings_type.h" #include "company_type.h" +#include "command_type.h" #include "core/geometry_type.hpp" #include "guitimer_func.h" @@ -37,13 +38,14 @@ protected: uint32 textref_stack[16]; ///< Values to put on the #TextRefStack for the error message. StringID summary_msg; ///< General error message showed in first line. Must be valid. StringID detailed_msg; ///< Detailed error message showed in second line. Can be #INVALID_STRING_ID. + StringID extra_msg; ///< Extra error message shown in third line. Can be #INVALID_STRING_ID. Point position; ///< Position of the error message window. CompanyID face; ///< Company belonging to the face being shown. #INVALID_COMPANY if no face present. public: ErrorMessageData(const ErrorMessageData &data); ~ErrorMessageData(); - ErrorMessageData(StringID summary_msg, StringID detailed_msg, uint duration = 0, int x = 0, int y = 0, const GRFFile *textref_stack_grffile = nullptr, uint textref_stack_size = 0, const uint32 *textref_stack = nullptr); + ErrorMessageData(StringID summary_msg, StringID detailed_msg, uint duration = 0, int x = 0, int y = 0, const GRFFile *textref_stack_grffile = nullptr, uint textref_stack_size = 0, const uint32 *textref_stack = nullptr, StringID extra_msg = INVALID_STRING_ID); /* Remove the copy assignment, as the default implementation will not do the right thing. */ ErrorMessageData &operator=(ErrorMessageData &rhs) = delete; @@ -64,7 +66,8 @@ typedef std::list Erro void ScheduleErrorMessage(ErrorList &datas); void ScheduleErrorMessage(const ErrorMessageData &data); -void ShowErrorMessage(StringID summary_msg, StringID detailed_msg, WarningLevel wl, int x = 0, int y = 0, const GRFFile *textref_stack_grffile = nullptr, uint textref_stack_size = 0, const uint32 *textref_stack = nullptr); +void ShowErrorMessage(StringID summary_msg, int x, int y, CommandCost cc); +void ShowErrorMessage(StringID summary_msg, StringID detailed_msg, WarningLevel wl, int x = 0, int y = 0, const GRFFile *textref_stack_grffile = nullptr, uint textref_stack_size = 0, const uint32 *textref_stack = nullptr, StringID extra_msg = INVALID_STRING_ID); bool HideActiveErrorMessage(); void ClearErrorMessages(); diff --git a/src/error_gui.cpp b/src/error_gui.cpp --- a/src/error_gui.cpp +++ b/src/error_gui.cpp @@ -72,7 +72,7 @@ static WindowDesc _errmsg_face_desc( */ ErrorMessageData::ErrorMessageData(const ErrorMessageData &data) : display_timer(data.display_timer), textref_stack_grffile(data.textref_stack_grffile), textref_stack_size(data.textref_stack_size), - summary_msg(data.summary_msg), detailed_msg(data.detailed_msg), position(data.position), face(data.face) + summary_msg(data.summary_msg), detailed_msg(data.detailed_msg), extra_msg(data.extra_msg), position(data.position), face(data.face) { memcpy(this->textref_stack, data.textref_stack, sizeof(this->textref_stack)); memcpy(this->decode_params, data.decode_params, sizeof(this->decode_params)); @@ -101,12 +101,14 @@ ErrorMessageData::~ErrorMessageData() * @param textref_stack_grffile NewGRF that provides the #TextRefStack for the error message. * @param textref_stack_size Number of uint32 values to put on the #TextRefStack for the error message; 0 if the #TextRefStack shall not be used. * @param textref_stack Values to put on the #TextRefStack. + * @param extra_msg Extra error message showed in third line. Can be INVALID_STRING_ID. */ -ErrorMessageData::ErrorMessageData(StringID summary_msg, StringID detailed_msg, uint duration, int x, int y, const GRFFile *textref_stack_grffile, uint textref_stack_size, const uint32 *textref_stack) : +ErrorMessageData::ErrorMessageData(StringID summary_msg, StringID detailed_msg, uint duration, int x, int y, const GRFFile *textref_stack_grffile, uint textref_stack_size, const uint32 *textref_stack, StringID extra_msg) : textref_stack_grffile(textref_stack_grffile), textref_stack_size(textref_stack_size), summary_msg(summary_msg), detailed_msg(detailed_msg), + extra_msg(extra_msg), face(INVALID_COMPANY) { this->position.x = x; @@ -184,6 +186,7 @@ struct ErrmsgWindow : public Window, Err private: uint height_summary; ///< Height of the #summary_msg string in pixels in the #WID_EM_MESSAGE widget. uint height_detailed; ///< Height of the #detailed_msg string in pixels in the #WID_EM_MESSAGE widget. + uint height_extra; ///< Height of the #extra_msg string in pixels in the #WID_EM_MESSAGE widget. public: ErrmsgWindow(const ErrorMessageData &data) : Window(data.HasFace() ? &_errmsg_face_desc : &_errmsg_desc), ErrorMessageData(data) @@ -200,11 +203,13 @@ public: this->height_summary = GetStringHeight(this->summary_msg, size->width); this->height_detailed = (this->detailed_msg == INVALID_STRING_ID) ? 0 : GetStringHeight(this->detailed_msg, size->width); + this->height_extra = (this->extra_msg == INVALID_STRING_ID) ? 0 : GetStringHeight(this->extra_msg, size->width); if (this->textref_stack_size > 0) StopTextRefStackUsage(); uint panel_height = this->height_summary; if (this->detailed_msg != INVALID_STRING_ID) panel_height += this->height_detailed + WidgetDimensions::scaled.vsep_wide; + if (this->extra_msg != INVALID_STRING_ID) panel_height += this->height_extra + WidgetDimensions::scaled.vsep_wide; size->height = std::max(size->height, panel_height); break; @@ -280,13 +285,24 @@ public: if (this->detailed_msg == INVALID_STRING_ID) { DrawStringMultiLine(r, this->summary_msg, TC_FROMSTRING, SA_CENTER); - } else { + } else if (this->extra_msg == INVALID_STRING_ID) { /* Extra space when message is shorter than company face window */ int extra = (r.Height() - this->height_summary - this->height_detailed - WidgetDimensions::scaled.vsep_wide) / 2; /* Note: NewGRF supplied error message often do not start with a colour code, so default to white. */ DrawStringMultiLine(r.WithHeight(this->height_summary + extra, false), this->summary_msg, TC_WHITE, SA_CENTER); DrawStringMultiLine(r.WithHeight(this->height_detailed + extra, true), this->detailed_msg, TC_WHITE, SA_CENTER); + } else { + /* Extra space when message is shorter than company face window */ + int extra = (r.Height() - this->height_summary - this->height_detailed - this->height_extra - (WidgetDimensions::scaled.vsep_wide * 2)) / 3; + + /* Note: NewGRF supplied error message often do not start with a colour code, so default to white. */ + Rect top_section = r.WithHeight(this->height_summary + extra, false); + Rect bottom_section = r.WithHeight(this->height_extra + extra, true); + Rect middle_section = { top_section.left, top_section.bottom, top_section.right, bottom_section.top }; + DrawStringMultiLine(top_section, this->summary_msg, TC_WHITE, SA_CENTER); + DrawStringMultiLine(middle_section, this->detailed_msg, TC_WHITE, SA_CENTER); + DrawStringMultiLine(bottom_section, this->extra_msg, TC_WHITE, SA_CENTER); } if (this->textref_stack_size > 0) StopTextRefStackUsage(); @@ -363,6 +379,19 @@ void UnshowCriticalError() /** * Display an error message in a window. + * Note: CommandCost errors are always severity level WL_INFO. + * @param summary_msg General error message showed in first line. Must be valid. + * @param x World X position (TileVirtX) of the error location. Set both x and y to 0 to just center the message when there is no related error tile. + * @param y World Y position (TileVirtY) of the error location. Set both x and y to 0 to just center the message when there is no related error tile. + * @param cc CommandCost containing the optional detailed and extra error messages shown in the second and third lines (can be INVALID_STRING_ID) and TextRefStack info. + */ +void ShowErrorMessage(StringID summary_msg, int x, int y, CommandCost cc) +{ + ShowErrorMessage(summary_msg, cc.GetErrorMessage(), WL_INFO, x, y, cc.GetTextRefStackGRF(), cc.GetTextRefStackSize(), cc.GetTextRefStack(), cc.GetExtraErrorMessage()); +} + +/** + * Display an error message in a window. * @param summary_msg General error message showed in first line. Must be valid. * @param detailed_msg Detailed error message showed in second line. Can be INVALID_STRING_ID. * @param wl Message severity. @@ -371,8 +400,9 @@ void UnshowCriticalError() * @param textref_stack_grffile NewGRF providing the #TextRefStack for the error message. * @param textref_stack_size Number of uint32 values to put on the #TextRefStack for the error message; 0 if the #TextRefStack shall not be used. * @param textref_stack Values to put on the #TextRefStack. + * @param extra_msg Extra error message shown in third line. Can be INVALID_STRING_ID. */ -void ShowErrorMessage(StringID summary_msg, StringID detailed_msg, WarningLevel wl, int x, int y, const GRFFile *textref_stack_grffile, uint textref_stack_size, const uint32 *textref_stack) +void ShowErrorMessage(StringID summary_msg, StringID detailed_msg, WarningLevel wl, int x, int y, const GRFFile *textref_stack_grffile, uint textref_stack_size, const uint32 *textref_stack, StringID extra_msg) { assert(textref_stack_size == 0 || (textref_stack_grffile != nullptr && textref_stack != nullptr)); if (summary_msg == STR_NULL) summary_msg = STR_EMPTY; @@ -388,6 +418,10 @@ void ShowErrorMessage(StringID summary_m b += seprintf(b, lastof(buf), " "); GetString(b, detailed_msg, lastof(buf)); } + if (extra_msg != INVALID_STRING_ID) { + b += seprintf(b, lastof(buf), " "); + GetString(b, extra_msg, lastof(buf)); + } if (textref_stack_size > 0) StopTextRefStackUsage(); @@ -399,7 +433,7 @@ void ShowErrorMessage(StringID summary_m if (_game_mode == GM_BOOTSTRAP) return; if (_settings_client.gui.errmsg_duration == 0 && !no_timeout) return; - ErrorMessageData data(summary_msg, detailed_msg, no_timeout ? 0 : _settings_client.gui.errmsg_duration, x, y, textref_stack_grffile, textref_stack_size, textref_stack); + ErrorMessageData data(summary_msg, detailed_msg, no_timeout ? 0 : _settings_client.gui.errmsg_duration, x, y, textref_stack_grffile, textref_stack_size, textref_stack, extra_msg); data.CopyOutDParams(); ErrmsgWindow *w = (ErrmsgWindow*)FindWindowById(WC_ERRMSG, 0); diff --git a/src/misc_cmd.cpp b/src/misc_cmd.cpp --- a/src/misc_cmd.cpp +++ b/src/misc_cmd.cpp @@ -222,6 +222,6 @@ CommandCost CmdChangeBankBalance(DoComma } /* This command doesn't cost anything for deity. */ - CommandCost zero_cost(expenses_type, 0); + CommandCost zero_cost(expenses_type, (Money)0); return zero_cost; }