From fcd33b25a7c474a3d468389036dc69a5784da962 Mon Sep 17 00:00:00 2001 From: Peter Nelson Date: Sun, 14 Jun 2026 17:32:27 +0100 Subject: [PATCH] Codechange: add GoalQuestionButton scoped enum and EnumBitSet (#15723) --- src/goal.cpp | 18 +++++------ src/goal_cmd.h | 4 +-- src/goal_gui.cpp | 57 +++++++++++++--------------------- src/goal_gui.h | 2 +- src/goal_type.h | 28 +++++++++++++++-- src/script/api/script_goal.cpp | 6 ++-- src/script/api/script_goal.hpp | 36 ++++++++++----------- 7 files changed, 79 insertions(+), 72 deletions(-) diff --git a/src/goal.cpp b/src/goal.cpp index e109facf76..b09799d3e6 100644 --- a/src/goal.cpp +++ b/src/goal.cpp @@ -235,21 +235,18 @@ CommandCost CmdSetGoalCompleted(DoCommandFlags flags, GoalID goal, bool complete * @param uniqueid Unique ID to use for this question. * @param target Company or client for which this question is. * @param is_client Question target: false - company, true - client. - * @param button_mask Buttons of the question. + * @param buttons Buttons of the question. * @param type Question type. * @param text Text of the question. * @return the cost of this operation or an error */ -CommandCost CmdGoalQuestion(DoCommandFlags flags, uint16_t uniqueid, uint32_t target, bool is_client, uint32_t button_mask, GoalQuestionType type, const EncodedString &text) +CommandCost CmdGoalQuestion(DoCommandFlags flags, uint16_t uniqueid, uint32_t target, bool is_client, GoalQuestionButtons buttons, GoalQuestionType type, const EncodedString &text) { static_assert(sizeof(uint32_t) >= sizeof(CompanyID)); CompanyID company = (CompanyID)target; static_assert(sizeof(uint32_t) >= sizeof(ClientID)); ClientID client = (ClientID)target; - static_assert(GOAL_QUESTION_BUTTON_COUNT < 29); - button_mask &= (1U << GOAL_QUESTION_BUTTON_COUNT) - 1; - if (_current_company != OWNER_DEITY) return CMD_ERROR; if (text.empty()) return CMD_ERROR; if (is_client) { @@ -261,7 +258,8 @@ CommandCost CmdGoalQuestion(DoCommandFlags flags, uint16_t uniqueid, uint32_t ta if (company != CompanyID::Invalid() && !Company::IsValidID(company)) return CMD_ERROR; } uint min_buttons = (type == GoalQuestionType::Question ? 1 : 0); - if (CountBits(button_mask) < min_buttons || CountBits(button_mask) > 3) return CMD_ERROR; + if (!buttons.IsValid()) return CMD_ERROR; + if (buttons.Count() < min_buttons || buttons.Count() > 3) return CMD_ERROR; if (type >= GoalQuestionType::End) return CMD_ERROR; if (flags.Test(DoCommandFlag::Execute)) { @@ -271,7 +269,7 @@ CommandCost CmdGoalQuestion(DoCommandFlags flags, uint16_t uniqueid, uint32_t ta if (company == CompanyID::Invalid() && !Company::IsValidID(_local_company)) return CommandCost(); if (company != CompanyID::Invalid() && company != _local_company) return CommandCost(); } - ShowGoalQuestion(uniqueid, type, button_mask, text); + ShowGoalQuestion(uniqueid, type, buttons, text); } return CommandCost(); @@ -284,9 +282,9 @@ CommandCost CmdGoalQuestion(DoCommandFlags flags, uint16_t uniqueid, uint32_t ta * @param button Button the company pressed * @return the cost of this operation or an error */ -CommandCost CmdGoalQuestionAnswer(DoCommandFlags flags, uint16_t uniqueid, uint8_t button) +CommandCost CmdGoalQuestionAnswer(DoCommandFlags flags, uint16_t uniqueid, GoalQuestionButton button) { - if (button >= GOAL_QUESTION_BUTTON_COUNT) return CMD_ERROR; + if (button >= GoalQuestionButton::End) return CMD_ERROR; if (_current_company == OWNER_DEITY) { /* It has been requested to close this specific question on all clients */ @@ -301,7 +299,7 @@ CommandCost CmdGoalQuestionAnswer(DoCommandFlags flags, uint16_t uniqueid, uint8 } if (flags.Test(DoCommandFlag::Execute)) { - Game::NewEvent(new ScriptEventGoalQuestionAnswer(uniqueid, _current_company, (ScriptGoal::QuestionButton)(1 << button))); + Game::NewEvent(new ScriptEventGoalQuestionAnswer(uniqueid, _current_company, static_cast(GoalQuestionButtons{button}.base()))); } return CommandCost(); diff --git a/src/goal_cmd.h b/src/goal_cmd.h index 8c0d3d8349..093199932f 100644 --- a/src/goal_cmd.h +++ b/src/goal_cmd.h @@ -19,8 +19,8 @@ CommandCost CmdSetGoalDestination(DoCommandFlags flags, GoalID goal, GoalType ty CommandCost CmdSetGoalText(DoCommandFlags flags, GoalID goal, const EncodedString &text); CommandCost CmdSetGoalProgress(DoCommandFlags flags, GoalID goal, const EncodedString &text); CommandCost CmdSetGoalCompleted(DoCommandFlags flags, GoalID goal, bool completed); -CommandCost CmdGoalQuestion(DoCommandFlags flags, uint16_t uniqueid, uint32_t target, bool is_client, uint32_t button_mask, GoalQuestionType type, const EncodedString &text); -CommandCost CmdGoalQuestionAnswer(DoCommandFlags flags, uint16_t uniqueid, uint8_t button); +CommandCost CmdGoalQuestion(DoCommandFlags flags, uint16_t uniqueid, uint32_t target, bool is_client, GoalQuestionButtons buttons, GoalQuestionType type, const EncodedString &text); +CommandCost CmdGoalQuestionAnswer(DoCommandFlags flags, uint16_t uniqueid, GoalQuestionButton button); DEF_CMD_TRAIT(Commands::CreateGoal, CmdCreateGoal, CommandFlags({CommandFlag::Deity, CommandFlag::StrCtrl}), CommandType::OtherManagement) DEF_CMD_TRAIT(Commands::RemoveGoal, CmdRemoveGoal, CommandFlag::Deity, CommandType::OtherManagement) diff --git a/src/goal_gui.cpp b/src/goal_gui.cpp index ee49384389..b846de92f2 100644 --- a/src/goal_gui.cpp +++ b/src/goal_gui.cpp @@ -318,46 +318,39 @@ void ShowGoalsList(CompanyID company) /** Ask a question about a goal. */ struct GoalQuestionWindow : public Window { - EncodedString question{}; ///< Question to ask (private copy). - int buttons = 0; ///< Number of valid buttons in #button. - std::array button{}; ///< Buttons to display. - TextColour colour{}; ///< Colour of the question text. + EncodedString question; ///< Question to ask (private copy). + GoalQuestionButtons buttons; ///< Buttons to display. + TextColour colour; ///< Colour of the question text. - GoalQuestionWindow(WindowDesc &desc, WindowNumber window_number, TextColour colour, uint32_t button_mask, const EncodedString &question) : Window(desc), colour(colour) + /** + * Construct a new Goal Question Window. + * @param desc Window description. + * @param window_number Number for the window. + * @param colour Colour of the question text. + * @param buttons Buttons to display. + * @param question Question to ask. + */ + GoalQuestionWindow(WindowDesc &desc, WindowNumber window_number, TextColour colour, GoalQuestionButtons buttons, const EncodedString &question) + : Window(desc), question(question), buttons(buttons), colour(colour) { - this->question = question; - - /* Figure out which buttons we have to enable. */ - int n = 0; - for (uint bit : SetBitIterator(button_mask)) { - if (bit >= GOAL_QUESTION_BUTTON_COUNT) break; - this->button[n++] = bit; - if (n == 3) break; - } - this->buttons = n; - assert(this->buttons < 4); + assert(this->buttons.Count() < 4); this->CreateNestedTree(); - if (this->buttons == 0) { + if (this->buttons.None()) { this->GetWidget(WID_GQ_BUTTONS)->SetDisplayedPlane(SZSP_HORIZONTAL); } else { - this->GetWidget(WID_GQ_BUTTONS)->SetDisplayedPlane(this->buttons - 1); + this->GetWidget(WID_GQ_BUTTONS)->SetDisplayedPlane(this->buttons.Count() - 1); } this->FinishInitNested(window_number); } - std::string GetWidgetString(WidgetID widget, StringID stringid) const override { switch (widget) { case WID_GQ_BUTTON_1: - return GetString(STR_GOAL_QUESTION_BUTTON_CANCEL + this->button[0]); - case WID_GQ_BUTTON_2: - return GetString(STR_GOAL_QUESTION_BUTTON_CANCEL + this->button[1]); - case WID_GQ_BUTTON_3: - return GetString(STR_GOAL_QUESTION_BUTTON_CANCEL + this->button[2]); + return GetString(STR_GOAL_QUESTION_BUTTON_CANCEL + to_underlying(this->buttons.GetNthSetBit(widget - WID_GQ_BUTTON_1).value_or(GoalQuestionButton::Cancel))); default: return this->Window::GetWidgetString(widget, stringid); @@ -368,17 +361,9 @@ struct GoalQuestionWindow : public Window { { switch (widget) { case WID_GQ_BUTTON_1: - Command::Post(this->window_number, this->button[0]); - this->Close(); - break; - case WID_GQ_BUTTON_2: - Command::Post(this->window_number, this->button[1]); - this->Close(); - break; - case WID_GQ_BUTTON_3: - Command::Post(this->window_number, this->button[2]); + Command::Post(this->window_number, this->buttons.GetNthSetBit(widget - WID_GQ_BUTTON_1).value_or(GoalQuestionButton::Cancel)); this->Close(); break; } @@ -471,11 +456,11 @@ static EnumIndexArray _goal * Display a goal question. * @param id Window number to use. * @param type Type of question. - * @param button_mask Buttons to display. + * @param buttons Buttons to display. * @param question Question to ask. */ -void ShowGoalQuestion(uint16_t id, GoalQuestionType type, uint32_t button_mask, const EncodedString &question) +void ShowGoalQuestion(uint16_t id, GoalQuestionType type, GoalQuestionButtons buttons, const EncodedString &question) { assert(type < GoalQuestionType::End); - new GoalQuestionWindow(_goal_question_list_desc[type], id, type == GoalQuestionType::Error ? TextColour::White : TextColour::Black, button_mask, question); + new GoalQuestionWindow(_goal_question_list_desc[type], id, type == GoalQuestionType::Error ? TextColour::White : TextColour::Black, buttons, question); } diff --git a/src/goal_gui.h b/src/goal_gui.h index 0be3a8ee9a..abf789dde4 100644 --- a/src/goal_gui.h +++ b/src/goal_gui.h @@ -15,6 +15,6 @@ #include "strings_type.h" void ShowGoalsList(CompanyID company); -void ShowGoalQuestion(uint16_t id, GoalQuestionType type, uint32_t button_mask, const EncodedString &question); +void ShowGoalQuestion(uint16_t id, GoalQuestionType type, GoalQuestionButtons buttons, const EncodedString &question); #endif /* GOAL_TYPE_H */ diff --git a/src/goal_type.h b/src/goal_type.h index 0d84d4968d..6b6c078f3b 100644 --- a/src/goal_type.h +++ b/src/goal_type.h @@ -12,8 +12,6 @@ #include "core/pool_type.hpp" -static const uint32_t GOAL_QUESTION_BUTTON_COUNT = 18; ///< Amount of buttons available. - /** Types of goal questions. */ enum class GoalQuestionType : uint8_t { Question = 0, ///< Asking a simple question; title: Question. @@ -23,6 +21,32 @@ enum class GoalQuestionType : uint8_t { End, ///< End marker. }; +/** Types of buttons that can be in the question window. */ +enum class GoalQuestionButton : uint8_t { + Cancel, ///< Cancel button. + Ok, ///< OK button. + No, ///< No button. + Yes, ///< Yes button. + Decline, ///< Decline button. + Accept, ///< Accept button. + Ignore, ///< Ignore button. + Retry, ///< Retry button. + Previous, ///< Previous button. + Next, ///< Next button. + Stop, ///< Stop button. + Start, ///< Start button. + Go, ///< Go button. + Continue, ///< Continue button. + Restart, ///< Restart button. + Postpone, ///< Postpone button. + Surrender, ///< Surrender button. + Close, ///< Close button. + End, ///< End marker. +}; + +/** Bitset of \c GoalQuestionButton elements. */ +using GoalQuestionButtons = EnumBitSet; + /** Types of goal destinations */ enum class GoalType : uint8_t { None, ///< Destination is not linked diff --git a/src/script/api/script_goal.cpp b/src/script/api/script_goal.cpp index fcf959d52d..163d6d3ecb 100644 --- a/src/script/api/script_goal.cpp +++ b/src/script/api/script_goal.cpp @@ -135,11 +135,11 @@ EnforcePreconditionEncodedText(false, text); uint min_buttons = (type == QT_QUESTION ? 1 : 0); EnforcePrecondition(false, CountBits(buttons) >= min_buttons && CountBits(buttons) <= 3); - EnforcePrecondition(false, buttons >= 0 && buttons < (1 << ::GOAL_QUESTION_BUTTON_COUNT)); + EnforcePrecondition(false, buttons >= 0 && static_cast<::GoalQuestionButtons>(buttons).IsValid()); EnforcePrecondition(false, static_cast<::GoalQuestionType>(type) < ::GoalQuestionType::End); EnforcePrecondition(false, uniqueid >= 0 && uniqueid <= UINT16_MAX); - return ScriptObject::Command::Do(uniqueid, target, is_client, buttons, static_cast<::GoalQuestionType>(type), text); + return ScriptObject::Command::Do(uniqueid, target, is_client, static_cast<::GoalQuestionButtons>(buttons), static_cast<::GoalQuestionType>(type), text); } /* static */ bool ScriptGoal::Question(SQInteger uniqueid, ScriptCompany::CompanyID company, Text *question, QuestionType type, SQInteger buttons) @@ -160,5 +160,5 @@ EnforceDeityMode(false); EnforcePrecondition(false, uniqueid >= 0 && uniqueid <= UINT16_MAX); - return ScriptObject::Command::Do(uniqueid, 0); + return ScriptObject::Command::Do(uniqueid, ::GoalQuestionButton::Cancel); } diff --git a/src/script/api/script_goal.hpp b/src/script/api/script_goal.hpp index 802cb9ef93..32deace1c3 100644 --- a/src/script/api/script_goal.hpp +++ b/src/script/api/script_goal.hpp @@ -56,24 +56,24 @@ public: */ enum QuestionButton { /* Note: these values represent part of the string list starting with STR_GOAL_QUESTION_BUTTON_CANCEL */ - BUTTON_CANCEL = (1 << 0), ///< Cancel button. - BUTTON_OK = (1 << 1), ///< OK button. - BUTTON_NO = (1 << 2), ///< No button. - BUTTON_YES = (1 << 3), ///< Yes button. - BUTTON_DECLINE = (1 << 4), ///< Decline button. - BUTTON_ACCEPT = (1 << 5), ///< Accept button. - BUTTON_IGNORE = (1 << 6), ///< Ignore button. - BUTTON_RETRY = (1 << 7), ///< Retry button. - BUTTON_PREVIOUS = (1 << 8), ///< Previous button. - BUTTON_NEXT = (1 << 9), ///< Next button. - BUTTON_STOP = (1 << 10), ///< Stop button. - BUTTON_START = (1 << 11), ///< Start button. - BUTTON_GO = (1 << 12), ///< Go button. - BUTTON_CONTINUE = (1 << 13), ///< Continue button. - BUTTON_RESTART = (1 << 14), ///< Restart button. - BUTTON_POSTPONE = (1 << 15), ///< Postpone button. - BUTTON_SURRENDER = (1 << 16), ///< Surrender button. - BUTTON_CLOSE = (1 << 17), ///< Close button. + BUTTON_CANCEL = ::GoalQuestionButtons{::GoalQuestionButton::Cancel}.base(), ///< Cancel button. + BUTTON_OK = ::GoalQuestionButtons{::GoalQuestionButton::Ok}.base(), ///< OK button. + BUTTON_NO = ::GoalQuestionButtons{::GoalQuestionButton::No}.base(), ///< No button. + BUTTON_YES = ::GoalQuestionButtons{::GoalQuestionButton::Yes}.base(), ///< Yes button. + BUTTON_DECLINE = ::GoalQuestionButtons{::GoalQuestionButton::Decline}.base(), ///< Decline button. + BUTTON_ACCEPT = ::GoalQuestionButtons{::GoalQuestionButton::Accept}.base(), ///< Accept button. + BUTTON_IGNORE = ::GoalQuestionButtons{::GoalQuestionButton::Ignore}.base(), ///< Ignore button. + BUTTON_RETRY = ::GoalQuestionButtons{::GoalQuestionButton::Retry}.base(), ///< Retry button. + BUTTON_PREVIOUS = ::GoalQuestionButtons{::GoalQuestionButton::Previous}.base(), ///< Previous button. + BUTTON_NEXT = ::GoalQuestionButtons{::GoalQuestionButton::Next}.base(), ///< Next button. + BUTTON_STOP = ::GoalQuestionButtons{::GoalQuestionButton::Stop}.base(), ///< Stop button. + BUTTON_START = ::GoalQuestionButtons{::GoalQuestionButton::Start}.base(), ///< Start button. + BUTTON_GO = ::GoalQuestionButtons{::GoalQuestionButton::Go}.base(), ///< Go button. + BUTTON_CONTINUE = ::GoalQuestionButtons{::GoalQuestionButton::Continue}.base(), ///< Continue button. + BUTTON_RESTART = ::GoalQuestionButtons{::GoalQuestionButton::Restart}.base(), ///< Restart button. + BUTTON_POSTPONE = ::GoalQuestionButtons{::GoalQuestionButton::Postpone}.base(), ///< Postpone button. + BUTTON_SURRENDER = ::GoalQuestionButtons{::GoalQuestionButton::Surrender}.base(), ///< Surrender button. + BUTTON_CLOSE = ::GoalQuestionButtons{::GoalQuestionButton::Close}.base(), ///< Close button. }; /**