diff --git a/src/command.cpp b/src/command.cpp --- a/src/command.cpp +++ b/src/command.cpp @@ -152,6 +152,8 @@ CommandProc CmdCompanyCtrl; CommandProc CmdCustomNewsItem; CommandProc CmdCreateGoal; CommandProc CmdRemoveGoal; +CommandProc CmdGoalQuestion; +CommandProc CmdGoalQuestionAnswer; CommandProc CmdLevelLand; @@ -290,6 +292,8 @@ static const Command _command_proc_table DEF_CMD(CmdCustomNewsItem, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_CUSTOM_NEWS_ITEM DEF_CMD(CmdCreateGoal, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_CREATE_GOAL DEF_CMD(CmdRemoveGoal, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_REMOVE_GOAL + DEF_CMD(CmdGoalQuestion, CMD_STR_CTRL | CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_GOAL_QUESTION + DEF_CMD(CmdGoalQuestionAnswer, CMD_DEITY, CMDT_OTHER_MANAGEMENT ), // CMD_GOAL_QUESTION_ANSWER DEF_CMD(CmdLevelLand, CMD_ALL_TILES | CMD_NO_TEST | CMD_AUTO, CMDT_LANDSCAPE_CONSTRUCTION), // CMD_LEVEL_LAND; test run might clear tiles multiple times, in execution that only happens once diff --git a/src/command_type.h b/src/command_type.h --- a/src/command_type.h +++ b/src/command_type.h @@ -266,6 +266,8 @@ enum Commands { CMD_CUSTOM_NEWS_ITEM, ///< create a custom news message CMD_CREATE_GOAL, ///< create a new goal CMD_REMOVE_GOAL, ///< remove a goal + CMD_GOAL_QUESTION, ///< ask a goal related question + CMD_GOAL_QUESTION_ANSWER, ///< answer(s) to CMD_GOAL_QUESTION CMD_LEVEL_LAND, ///< level land CMD_BUILD_LOCK, ///< build a lock diff --git a/src/game/game_instance.cpp b/src/game/game_instance.cpp --- a/src/game/game_instance.cpp +++ b/src/game/game_instance.cpp @@ -128,6 +128,7 @@ void GameInstance::RegisterAPI() SQGSEventCompanyMerger_Register(this->engine); SQGSEventCompanyNew_Register(this->engine); SQGSEventController_Register(this->engine); + SQGSEventGoalQuestionAnswer_Register(this->engine); SQGSEventIndustryClose_Register(this->engine); SQGSEventIndustryOpen_Register(this->engine); SQGSEventStationFirstVehicle_Register(this->engine); diff --git a/src/goal.cpp b/src/goal.cpp --- a/src/goal.cpp +++ b/src/goal.cpp @@ -26,6 +26,8 @@ #include "command_func.h" #include "company_base.h" #include "string_func.h" +#include "gui.h" +#include "network/network.h" #include "table/strings.h" @@ -118,3 +120,65 @@ CommandCost CmdRemoveGoal(TileIndex tile return CommandCost(); } + + +/** + * Ask a goal related question + * @param tile unused. + * @param flags type of operation + * @param p1 various bitstuffed elements + * - p1 = (bit 0 - 15) - Unique ID to use for this question. + * - p1 = (bit 16 - 23) - Company for which this question is. + * @param p2 Buttons of the question. + * @param text Text of the question. + * @return the cost of this operation or an error + */ +CommandCost CmdGoalQuestion(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + uint16 uniqueid = (GoalType)GB(p1, 0, 16); + CompanyID company = (CompanyID)GB(p1, 16, 8); + + if (_current_company != OWNER_DEITY) return CMD_ERROR; + if (StrEmpty(text)) return CMD_ERROR; + if (company != INVALID_COMPANY && !Company::IsValidID(company)) return CMD_ERROR; + if (CountBits(p2) < 1 || CountBits(p2) > 3) return CMD_ERROR; + + if (flags & DC_EXEC) { + if (company == _local_company || (company == INVALID_COMPANY && Company::IsValidID(_local_company))) ShowGoalQuestion(uniqueid, p2, text); + } + + return CommandCost(); +} + +/** + * Reply to a goal question. + * @param tile unused. + * @param flags type of operation + * @param p1 Unique ID to use for this question. + * @param p2 Button the company pressed + * @param text Text of the question. + * @return the cost of this operation or an error + */ +CommandCost CmdGoalQuestionAnswer(TileIndex tile, DoCommandFlag flags, uint32 p1, uint32 p2, const char *text) +{ + if (p1 > UINT16_MAX) return CMD_ERROR; + if (p2 >= GOAL_QUESTION_BUTTON_COUNT) return CMD_ERROR; + + if (_current_company == OWNER_DEITY) { + /* It has been requested to close this specific question on all clients */ + if (flags & DC_EXEC) DeleteWindowById(WC_GOAL_QUESTION, p1); + return CommandCost(); + } + + if (_networking && _local_company == _current_company) { + /* Somebody in the same company answered the question. Close the window */ + if (flags & DC_EXEC) DeleteWindowById(WC_GOAL_QUESTION, p1); + if (!_network_server) return CommandCost(); + } + + if (flags & DC_EXEC) { + Game::NewEvent(new ScriptEventGoalQuestionAnswer(p1, (ScriptCompany::CompanyID)(byte)_current_company, (ScriptGoal::QuestionButton)(1 << p2))); + } + + return CommandCost(); +} diff --git a/src/goal_gui.cpp b/src/goal_gui.cpp --- a/src/goal_gui.cpp +++ b/src/goal_gui.cpp @@ -21,6 +21,7 @@ #include "goal_base.h" #include "core/geometry_func.hpp" #include "company_func.h" +#include "command_func.h" #include "widgets/goal_widget.h" @@ -247,8 +248,131 @@ static const WindowDesc _goals_list_desc _nested_goals_list_widgets, lengthof(_nested_goals_list_widgets) ); - void ShowGoalsList() { AllocateWindowDescFront(&_goals_list_desc, 0); } + + + +struct GoalQuestionWindow : Window { + char *question; + int buttons; + int button[3]; + + GoalQuestionWindow(const WindowDesc *desc, WindowNumber window_number, uint32 button_mask, const char *question) : Window() + { + this->question = strdup(question); + + /* Figure out which buttons we have to enable */ + int bit; + int n = 0; + FOR_EACH_SET_BIT(bit, button_mask) { + if (bit >= GOAL_QUESTION_BUTTON_COUNT) break; + this->button[n++] = bit; + if (n == 3) break; + } + this->buttons = n; + assert(this->buttons > 0 && this->buttons < 4); + + this->CreateNestedTree(desc); + this->GetWidget(WID_GQ_BUTTONS)->SetDisplayedPlane(this->buttons - 1); + this->FinishInitNested(desc, window_number); + } + + ~GoalQuestionWindow() + { + free(this->question); + } + + virtual void SetStringParameters(int widget) const + { + switch (widget) { + case WID_GQ_BUTTON_1: + SetDParam(0, STR_GOAL_QUESTION_BUTTON_CANCEL + this->button[0]); + break; + + case WID_GQ_BUTTON_2: + SetDParam(0, STR_GOAL_QUESTION_BUTTON_CANCEL + this->button[1]); + break; + + case WID_GQ_BUTTON_3: + SetDParam(0, STR_GOAL_QUESTION_BUTTON_CANCEL + this->button[2]); + break; + } + } + + virtual void OnClick(Point pt, int widget, int click_count) + { + switch (widget) { + case WID_GQ_BUTTON_1: + DoCommandP(0, this->window_number, this->button[0], CMD_GOAL_QUESTION_ANSWER); + delete this; + break; + + case WID_GQ_BUTTON_2: + DoCommandP(0, this->window_number, this->button[1], CMD_GOAL_QUESTION_ANSWER); + delete this; + break; + + case WID_GQ_BUTTON_3: + DoCommandP(0, this->window_number, this->button[2], CMD_GOAL_QUESTION_ANSWER); + delete this; + break; + } + } + + virtual void UpdateWidgetSize(int widget, Dimension *size, const Dimension &padding, Dimension *fill, Dimension *resize) + { + if (widget != WID_GQ_QUESTION) return; + + SetDParamStr(0, this->question); + size->height = GetStringHeight(STR_JUST_RAW_STRING, size->width) + WD_PAR_VSEP_WIDE; + } + + virtual void DrawWidget(const Rect &r, int widget) const + { + if (widget != WID_GQ_QUESTION) return; + + SetDParamStr(0, this->question); + DrawStringMultiLine(r.left, r.right, r.top, UINT16_MAX, STR_JUST_RAW_STRING, TC_BLACK, SA_TOP | SA_HOR_CENTER); + } +}; + +static const NWidgetPart _nested_goal_question_widgets[] = { + NWidget(NWID_HORIZONTAL), + NWidget(WWT_CLOSEBOX, COLOUR_LIGHT_BLUE), + NWidget(WWT_CAPTION, COLOUR_LIGHT_BLUE), SetDataTip(STR_GOAL_QUESTION_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS), + EndContainer(), + NWidget(WWT_PANEL, COLOUR_LIGHT_BLUE), + NWidget(WWT_EMPTY, INVALID_COLOUR, WID_GQ_QUESTION), SetMinimalSize(300, 0), SetPadding(8, 8, 8, 8), SetFill(1, 0), + NWidget(NWID_SELECTION, INVALID_COLOUR, WID_GQ_BUTTONS), + NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(85, 10, 85), + NWidget(WWT_PUSHTXTBTN, COLOUR_LIGHT_BLUE, WID_GQ_BUTTON_1), SetDataTip(STR_BLACK_STRING, STR_NULL), SetFill(1, 0), + EndContainer(), + NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(65, 10, 65), + NWidget(WWT_PUSHTXTBTN, COLOUR_LIGHT_BLUE, WID_GQ_BUTTON_1), SetDataTip(STR_BLACK_STRING, STR_NULL), SetFill(1, 0), + NWidget(WWT_PUSHTXTBTN, COLOUR_LIGHT_BLUE, WID_GQ_BUTTON_2), SetDataTip(STR_BLACK_STRING, STR_NULL), SetFill(1, 0), + EndContainer(), + NWidget(NWID_HORIZONTAL, NC_EQUALSIZE), SetPIP(25, 10, 25), + NWidget(WWT_PUSHTXTBTN, COLOUR_LIGHT_BLUE, WID_GQ_BUTTON_1), SetDataTip(STR_BLACK_STRING, STR_NULL), SetFill(1, 0), + NWidget(WWT_PUSHTXTBTN, COLOUR_LIGHT_BLUE, WID_GQ_BUTTON_2), SetDataTip(STR_BLACK_STRING, STR_NULL), SetFill(1, 0), + NWidget(WWT_PUSHTXTBTN, COLOUR_LIGHT_BLUE, WID_GQ_BUTTON_3), SetDataTip(STR_BLACK_STRING, STR_NULL), SetFill(1, 0), + EndContainer(), + EndContainer(), + NWidget(NWID_SPACER), SetMinimalSize(0, 8), + EndContainer(), +}; + +static const WindowDesc _goal_question_list_desc( + WDP_CENTER, 0, 0, + WC_GOAL_QUESTION, WC_NONE, + WDF_CONSTRUCTION, + _nested_goal_question_widgets, lengthof(_nested_goal_question_widgets) +); + + +void ShowGoalQuestion(uint16 id, uint32 button_mask, const char *question) +{ + new GoalQuestionWindow(&_goal_question_list_desc, id, button_mask, question); +} diff --git a/src/goal_type.h b/src/goal_type.h --- a/src/goal_type.h +++ b/src/goal_type.h @@ -12,6 +12,10 @@ #ifndef GOAL_TYPE_H #define GOAL_TYPE_H +enum { + GOAL_QUESTION_BUTTON_COUNT = 18, ///< Amount of buttons available. +}; + /** Types of goal destinations */ enum GoalType { GT_NONE, ///< Destination is not linked diff --git a/src/gui.h b/src/gui.h --- a/src/gui.h +++ b/src/gui.h @@ -53,6 +53,7 @@ void ShowTownDirectory(); void ShowIndustryDirectory(); void ShowSubsidiesList(); void ShowGoalsList(); +void ShowGoalQuestion(uint16 id, uint32 button_mask, const char *question); void ShowEstimatedCostOrIncome(Money cost, int x, int y); diff --git a/src/lang/english.txt b/src/lang/english.txt --- a/src/lang/english.txt +++ b/src/lang/english.txt @@ -2669,6 +2669,29 @@ STR_GOALS_NONE STR_GOALS_COMPANY_TITLE :{BLACK}Company goals: STR_GOALS_TOOLTIP_CLICK_ON_SERVICE_TO_CENTER :{BLACK}Click on goal to centre main view on industry/town/tile. Ctrl+Click opens a new viewport on industry/town/tile location +# Goal question window +STR_GOAL_QUESTION_CAPTION :{WHITE}Question + +### Start of Goal Question button list +STR_GOAL_QUESTION_BUTTON_CANCEL :Cancel +STR_GOAL_QUESTION_BUTTON_OK :OK +STR_GOAL_QUESTION_BUTTON_NO :No +STR_GOAL_QUESTION_BUTTON_YES :Yes +STR_GOAL_QUESTION_BUTTON_DECLINE :Decline +STR_GOAL_QUESTION_BUTTON_ACCEPT :Accept +STR_GOAL_QUESTION_BUTTON_IGNORE :Ignore +STR_GOAL_QUESTION_BUTTON_RETRY :Retry +STR_GOAL_QUESTION_BUTTON_PREVIOUS :Previous +STR_GOAL_QUESTION_BUTTON_NEXT :Next +STR_GOAL_QUESTION_BUTTON_STOP :Stop +STR_GOAL_QUESTION_BUTTON_START :Start +STR_GOAL_QUESTION_BUTTON_GO :Go +STR_GOAL_QUESTION_BUTTON_CONTINUE :Continue +STR_GOAL_QUESTION_BUTTON_RESTART :Restart +STR_GOAL_QUESTION_BUTTON_POSTPONE :Postpone +STR_GOAL_QUESTION_BUTTON_SURRENDER :Surrender +STR_GOAL_QUESTION_BUTTON_CLOSE :Close + # Subsidies window STR_SUBSIDIES_CAPTION :{WHITE}Subsidies STR_SUBSIDIES_OFFERED_TITLE :{BLACK}Subsidies on offer for services taking: diff --git a/src/script/api/ai/ai_event.hpp.sq b/src/script/api/ai/ai_event.hpp.sq --- a/src/script/api/ai/ai_event.hpp.sq +++ b/src/script/api/ai/ai_event.hpp.sq @@ -47,6 +47,7 @@ void SQAIEvent_Register(Squirrel *engine SQAIEvent.DefSQConst(engine, ScriptEvent::ET_AIRCRAFT_DEST_TOO_FAR, "ET_AIRCRAFT_DEST_TOO_FAR"); SQAIEvent.DefSQConst(engine, ScriptEvent::ET_ADMIN_PORT, "ET_ADMIN_PORT"); SQAIEvent.DefSQConst(engine, ScriptEvent::ET_WINDOW_WIDGET_CLICK, "ET_WINDOW_WIDGET_CLICK"); + SQAIEvent.DefSQConst(engine, ScriptEvent::ET_GOAL_QUESTION_ANSWER, "ET_GOAL_QUESTION_ANSWER"); SQAIEvent.DefSQMethod(engine, &ScriptEvent::GetEventType, "GetEventType", 1, "x"); diff --git a/src/script/api/game/game_event.hpp.sq b/src/script/api/game/game_event.hpp.sq --- a/src/script/api/game/game_event.hpp.sq +++ b/src/script/api/game/game_event.hpp.sq @@ -47,6 +47,7 @@ void SQGSEvent_Register(Squirrel *engine SQGSEvent.DefSQConst(engine, ScriptEvent::ET_AIRCRAFT_DEST_TOO_FAR, "ET_AIRCRAFT_DEST_TOO_FAR"); SQGSEvent.DefSQConst(engine, ScriptEvent::ET_ADMIN_PORT, "ET_ADMIN_PORT"); SQGSEvent.DefSQConst(engine, ScriptEvent::ET_WINDOW_WIDGET_CLICK, "ET_WINDOW_WIDGET_CLICK"); + SQGSEvent.DefSQConst(engine, ScriptEvent::ET_GOAL_QUESTION_ANSWER, "ET_GOAL_QUESTION_ANSWER"); SQGSEvent.DefSQMethod(engine, &ScriptEvent::GetEventType, "GetEventType", 1, "x"); diff --git a/src/script/api/game/game_event_types.hpp.sq b/src/script/api/game/game_event_types.hpp.sq --- a/src/script/api/game/game_event_types.hpp.sq +++ b/src/script/api/game/game_event_types.hpp.sq @@ -249,3 +249,20 @@ void SQGSEventWindowWidgetClick_Register SQGSEventWindowWidgetClick.PostRegister(engine); } + + +template <> const char *GetClassName() { return "GSEventGoalQuestionAnswer"; } + +void SQGSEventGoalQuestionAnswer_Register(Squirrel *engine) +{ + DefSQClass SQGSEventGoalQuestionAnswer("GSEventGoalQuestionAnswer"); + SQGSEventGoalQuestionAnswer.PreRegister(engine, "GSEvent"); + + SQGSEventGoalQuestionAnswer.DefSQStaticMethod(engine, &ScriptEventGoalQuestionAnswer::Convert, "Convert", 2, ".x"); + + SQGSEventGoalQuestionAnswer.DefSQMethod(engine, &ScriptEventGoalQuestionAnswer::GetUniqueID, "GetUniqueID", 1, "x"); + SQGSEventGoalQuestionAnswer.DefSQMethod(engine, &ScriptEventGoalQuestionAnswer::GetCompany, "GetCompany", 1, "x"); + SQGSEventGoalQuestionAnswer.DefSQMethod(engine, &ScriptEventGoalQuestionAnswer::GetButton, "GetButton", 1, "x"); + + SQGSEventGoalQuestionAnswer.PostRegister(engine); +} diff --git a/src/script/api/game/game_goal.hpp.sq b/src/script/api/game/game_goal.hpp.sq --- a/src/script/api/game/game_goal.hpp.sq +++ b/src/script/api/game/game_goal.hpp.sq @@ -21,16 +21,36 @@ void SQGSGoal_Register(Squirrel *engine) SQGSGoal.PreRegister(engine); SQGSGoal.AddConstructor(engine, "x"); - SQGSGoal.DefSQConst(engine, ScriptGoal::GOAL_INVALID, "GOAL_INVALID"); - SQGSGoal.DefSQConst(engine, ScriptGoal::GT_NONE, "GT_NONE"); - SQGSGoal.DefSQConst(engine, ScriptGoal::GT_TILE, "GT_TILE"); - SQGSGoal.DefSQConst(engine, ScriptGoal::GT_INDUSTRY, "GT_INDUSTRY"); - SQGSGoal.DefSQConst(engine, ScriptGoal::GT_TOWN, "GT_TOWN"); - SQGSGoal.DefSQConst(engine, ScriptGoal::GT_COMPANY, "GT_COMPANY"); + SQGSGoal.DefSQConst(engine, ScriptGoal::GOAL_INVALID, "GOAL_INVALID"); + SQGSGoal.DefSQConst(engine, ScriptGoal::GT_NONE, "GT_NONE"); + SQGSGoal.DefSQConst(engine, ScriptGoal::GT_TILE, "GT_TILE"); + SQGSGoal.DefSQConst(engine, ScriptGoal::GT_INDUSTRY, "GT_INDUSTRY"); + SQGSGoal.DefSQConst(engine, ScriptGoal::GT_TOWN, "GT_TOWN"); + SQGSGoal.DefSQConst(engine, ScriptGoal::GT_COMPANY, "GT_COMPANY"); + SQGSGoal.DefSQConst(engine, ScriptGoal::BUTTON_CANCEL, "BUTTON_CANCEL"); + SQGSGoal.DefSQConst(engine, ScriptGoal::BUTTON_OK, "BUTTON_OK"); + SQGSGoal.DefSQConst(engine, ScriptGoal::BUTTON_NO, "BUTTON_NO"); + SQGSGoal.DefSQConst(engine, ScriptGoal::BUTTON_YES, "BUTTON_YES"); + SQGSGoal.DefSQConst(engine, ScriptGoal::BUTTON_DECLINE, "BUTTON_DECLINE"); + SQGSGoal.DefSQConst(engine, ScriptGoal::BUTTON_ACCEPT, "BUTTON_ACCEPT"); + SQGSGoal.DefSQConst(engine, ScriptGoal::BUTTON_IGNORE, "BUTTON_IGNORE"); + SQGSGoal.DefSQConst(engine, ScriptGoal::BUTTON_RETRY, "BUTTON_RETRY"); + SQGSGoal.DefSQConst(engine, ScriptGoal::BUTTON_PREVIOUS, "BUTTON_PREVIOUS"); + SQGSGoal.DefSQConst(engine, ScriptGoal::BUTTON_NEXT, "BUTTON_NEXT"); + SQGSGoal.DefSQConst(engine, ScriptGoal::BUTTON_STOP, "BUTTON_STOP"); + SQGSGoal.DefSQConst(engine, ScriptGoal::BUTTON_START, "BUTTON_START"); + SQGSGoal.DefSQConst(engine, ScriptGoal::BUTTON_GO, "BUTTON_GO"); + SQGSGoal.DefSQConst(engine, ScriptGoal::BUTTON_CONTINUE, "BUTTON_CONTINUE"); + SQGSGoal.DefSQConst(engine, ScriptGoal::BUTTON_RESTART, "BUTTON_RESTART"); + SQGSGoal.DefSQConst(engine, ScriptGoal::BUTTON_POSTPONE, "BUTTON_POSTPONE"); + SQGSGoal.DefSQConst(engine, ScriptGoal::BUTTON_SURRENDER, "BUTTON_SURRENDER"); + SQGSGoal.DefSQConst(engine, ScriptGoal::BUTTON_CLOSE, "BUTTON_CLOSE"); - SQGSGoal.DefSQStaticMethod(engine, &ScriptGoal::IsValidGoal, "IsValidGoal", 2, ".i"); - SQGSGoal.DefSQStaticMethod(engine, &ScriptGoal::New, "New", 5, ".i.ii"); - SQGSGoal.DefSQStaticMethod(engine, &ScriptGoal::Remove, "Remove", 2, ".i"); + SQGSGoal.DefSQStaticMethod(engine, &ScriptGoal::IsValidGoal, "IsValidGoal", 2, ".i"); + SQGSGoal.DefSQStaticMethod(engine, &ScriptGoal::New, "New", 5, ".i.ii"); + SQGSGoal.DefSQStaticMethod(engine, &ScriptGoal::Remove, "Remove", 2, ".i"); + SQGSGoal.DefSQStaticMethod(engine, &ScriptGoal::Question, "Question", 5, ".ii.i"); + SQGSGoal.DefSQStaticMethod(engine, &ScriptGoal::CloseQuestion, "CloseQuestion", 2, ".i"); SQGSGoal.PostRegister(engine); } diff --git a/src/script/api/script_event.hpp b/src/script/api/script_event.hpp --- a/src/script/api/script_event.hpp +++ b/src/script/api/script_event.hpp @@ -52,6 +52,7 @@ public: ET_AIRCRAFT_DEST_TOO_FAR, ET_ADMIN_PORT, ET_WINDOW_WIDGET_CLICK, + ET_GOAL_QUESTION_ANSWER, }; /** diff --git a/src/script/api/script_event_types.hpp b/src/script/api/script_event_types.hpp --- a/src/script/api/script_event_types.hpp +++ b/src/script/api/script_event_types.hpp @@ -14,6 +14,7 @@ #include "script_event.hpp" #include "script_company.hpp" +#include "script_goal.hpp" #include "script_window.hpp" /** @@ -932,4 +933,53 @@ private: uint8 widget; ///< Widget of the click. }; +/** + * Event Goal Question Answer, where you receive the answer given to your questions. + * @note It is possible that you get more than 1 answer from the same company + * (due to lag). Please keep this in mind while handling this event. + * @api game + */ +class ScriptEventGoalQuestionAnswer : public ScriptEvent { +public: + /** + * @param uniqueid The uniqueID you have given this question. + * @param company The company that is replying. + * @param button The button the company pressed. + */ + ScriptEventGoalQuestionAnswer(uint16 uniqueid, ScriptCompany::CompanyID company, ScriptGoal::QuestionButton button) : + ScriptEvent(ET_GOAL_QUESTION_ANSWER), + uniqueid(uniqueid), + company(company), + button(button) + {} + + /** + * Convert an ScriptEvent to the real instance. + * @param instance The instance to convert. + * @return The converted instance. + */ + static ScriptEventGoalQuestionAnswer *Convert(ScriptEvent *instance) { return (ScriptEventGoalQuestionAnswer *)instance; } + + /** + * Get the unique id of the question. + */ + uint16 GetUniqueID() { return this->uniqueid; } + + /** + * Get the company that pressed a button. + */ + ScriptCompany::CompanyID GetCompany() { return this->company; } + + /** + * Get the button that got pressed. + */ + ScriptGoal::QuestionButton GetButton() { return this->button; } + +private: + uint16 uniqueid; ///< The uniqueid of the question. + ScriptCompany::CompanyID company; ///< The company given the answer. + ScriptGoal::QuestionButton button; ///< The button he pressed. +}; + + #endif /* SCRIPT_EVENT_TYPES_HPP */ diff --git a/src/script/api/script_goal.cpp b/src/script/api/script_goal.cpp --- a/src/script/api/script_goal.cpp +++ b/src/script/api/script_goal.cpp @@ -32,6 +32,7 @@ { CCountedPtr counter(goal); + EnforcePrecondition(GOAL_INVALID, ScriptObject::GetCompany() == OWNER_DEITY); EnforcePrecondition(GOAL_INVALID, goal != NULL); EnforcePrecondition(GOAL_INVALID, !StrEmpty(goal->GetEncodedText())); EnforcePrecondition(GOAL_INVALID, company == ScriptCompany::COMPANY_INVALID || ScriptCompany::ResolveCompanyID(company) != ScriptCompany::COMPANY_INVALID); @@ -48,7 +49,31 @@ /* static */ bool ScriptGoal::Remove(GoalID goal_id) { + EnforcePrecondition(false, ScriptObject::GetCompany() == OWNER_DEITY); EnforcePrecondition(false, IsValidGoal(goal_id)); return ScriptObject::DoCommand(0, goal_id, 0, CMD_REMOVE_GOAL); } + +/* static */ bool ScriptGoal::Question(uint16 uniqueid, ScriptCompany::CompanyID company, Text *question, int buttons) +{ + CCountedPtr counter(question); + + EnforcePrecondition(false, ScriptObject::GetCompany() == OWNER_DEITY); + EnforcePrecondition(false, question != NULL); + EnforcePrecondition(false, !StrEmpty(question->GetEncodedText())); + EnforcePrecondition(false, company == ScriptCompany::COMPANY_INVALID || ScriptCompany::ResolveCompanyID(company) != ScriptCompany::COMPANY_INVALID); + EnforcePrecondition(false, CountBits(buttons) >= 1 && CountBits(buttons) <= 3); + + uint8 c = company; + if (company == ScriptCompany::COMPANY_INVALID) c = INVALID_COMPANY; + + return ScriptObject::DoCommand(0, uniqueid | (c << 16), buttons, CMD_GOAL_QUESTION, question->GetEncodedText()); +} + +/* static */ bool ScriptGoal::CloseQuestion(uint16 uniqueid) +{ + EnforcePrecondition(false, ScriptObject::GetCompany() == OWNER_DEITY); + + return ScriptObject::DoCommand(0, uniqueid, 0, CMD_GOAL_QUESTION_ANSWER); +} diff --git a/src/script/api/script_goal.hpp b/src/script/api/script_goal.hpp --- a/src/script/api/script_goal.hpp +++ b/src/script/api/script_goal.hpp @@ -42,6 +42,28 @@ public: GT_COMPANY = ::GT_COMPANY, ///< Destination is a company. }; + 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. + }; + /** * Check whether this is a valid goalID. * @param goal_id The GoalID to check. @@ -56,6 +78,7 @@ public: * @param type The type of the goal. * @param destination The destination of the #type type. * @return The new GoalID, or GOAL_INVALID if it failed. + * @pre No ScriptCompanyMode may be in scope. * @pre goal != NULL && len(goal) != 0. * @pre company == COMPANY_INVALID || ResolveCompanyID(company) != COMPANY_INVALID. */ @@ -65,9 +88,38 @@ public: * Remove a goal from the list. * @param goal_id The goal to remove. * @return True if the action succeeded. + * @pre No ScriptCompanyMode may be in scope. * @pre IsValidGoal(goal_id). */ static bool Remove(GoalID goal_id); + + /** + * Ask a question. + * @param uniqueid Your unique id to distinguish results of multiple questions in the returning event. + * @param company The company to ask the question, or ScriptCompany::COMPANY_INVALID for all. + * @param question The question to ask (can be either a raw string, or a ScriptText object). + * @param buttons Any combinations (at least 1, up to 3) of buttons defined in QuestionButton. Like BUTTON_YES + BUTTON_NO. + * @return True if the action succeeded. + * @pre No ScriptCompanyMode may be in scope. + * @pre question != NULL && len(question) != 0. + * @pre company == COMPANY_INVALID || ResolveCompanyID(company) != COMPANY_INVALID. + * @pre CountBits(buttons) >= 1 && CountBits(buttons) <= 3. + * @note Replies to the question are given by you via the event ScriptEvent_GoalQuestionAnswer. + * @note There is no guarantee you ever get a reply on your question. + */ + static bool Question(uint16 uniqueid, ScriptCompany::CompanyID company, Text *question, int buttons); + + /** + * Close the question on all clients. + * @param uniqueid The uniqueid of the question you want to close. + * @return True if the action succeeded. + * @pre No ScriptCompanyMode may be in scope. + * @note If you send a question to a single company, and get a reply for them, + * the question is already closed on all clients. Only use this function if + * you want to timeout a question, or if you send the question to all + * companies, but you are only interested in the reply of the first. + */ + static bool CloseQuestion(uint16 uniqueid); }; #endif /* SCRIPT_GOAL_HPP */ diff --git a/src/script/api/template/template_event_types.hpp.sq b/src/script/api/template/template_event_types.hpp.sq --- a/src/script/api/template/template_event_types.hpp.sq +++ b/src/script/api/template/template_event_types.hpp.sq @@ -230,3 +230,12 @@ namespace SQConvert { template <> inline const ScriptEventWindowWidgetClick &GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(ScriptEventWindowWidgetClick *)instance; } template <> inline int Return(HSQUIRRELVM vm, ScriptEventWindowWidgetClick *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "EventWindowWidgetClick", res, NULL, DefSQDestructorCallback, true); return 1; } } // namespace SQConvert + +namespace SQConvert { + /* Allow ScriptEventGoalQuestionAnswer to be used as Squirrel parameter */ + template <> inline ScriptEventGoalQuestionAnswer *GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (ScriptEventGoalQuestionAnswer *)instance; } + template <> inline ScriptEventGoalQuestionAnswer &GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(ScriptEventGoalQuestionAnswer *)instance; } + template <> inline const ScriptEventGoalQuestionAnswer *GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (ScriptEventGoalQuestionAnswer *)instance; } + template <> inline const ScriptEventGoalQuestionAnswer &GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(ScriptEventGoalQuestionAnswer *)instance; } + template <> inline int Return(HSQUIRRELVM vm, ScriptEventGoalQuestionAnswer *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "EventGoalQuestionAnswer", res, NULL, DefSQDestructorCallback, true); return 1; } +} // namespace SQConvert diff --git a/src/script/api/template/template_goal.hpp.sq b/src/script/api/template/template_goal.hpp.sq --- a/src/script/api/template/template_goal.hpp.sq +++ b/src/script/api/template/template_goal.hpp.sq @@ -17,6 +17,8 @@ namespace SQConvert { template <> inline int Return(HSQUIRRELVM vm, ScriptGoal::GoalID res) { sq_pushinteger(vm, (int32)res); return 1; } template <> inline ScriptGoal::GoalType GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (ScriptGoal::GoalType)tmp; } template <> inline int Return(HSQUIRRELVM vm, ScriptGoal::GoalType res) { sq_pushinteger(vm, (int32)res); return 1; } + template <> inline ScriptGoal::QuestionButton GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (ScriptGoal::QuestionButton)tmp; } + template <> inline int Return(HSQUIRRELVM vm, ScriptGoal::QuestionButton res) { sq_pushinteger(vm, (int32)res); return 1; } /* Allow ScriptGoal to be used as Squirrel parameter */ template <> inline ScriptGoal *GetParam(ForceType, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return (ScriptGoal *)instance; } diff --git a/src/widgets/goal_widget.h b/src/widgets/goal_widget.h --- a/src/widgets/goal_widget.h +++ b/src/widgets/goal_widget.h @@ -19,4 +19,13 @@ enum GoalListWidgets { WID_GL_SCROLLBAR, ///< Scrollbar of the panel. }; +/** Widgets of the #GoalQuestionWindow class. */ +enum GoalQuestionWidgets { + WID_GQ_QUESTION, ///< Question text. + WID_GQ_BUTTONS, ///< Buttons selection (between 2 or 3). + WID_GQ_BUTTON_1, ///< First button. + WID_GQ_BUTTON_2, ///< Second button. + WID_GQ_BUTTON_3, ///< Third button. +}; + #endif /* WIDGETS_GOAL_WIDGET_H */ diff --git a/src/window_type.h b/src/window_type.h --- a/src/window_type.h +++ b/src/window_type.h @@ -126,6 +126,13 @@ enum WindowClass { */ WC_CONFIRM_POPUP_QUERY, + /** + * Popup with a set of buttons, designed to ask the user a question + * from a GameScript. %Window numbers: + * - 0 = #GoalQuestionWidgets + */ + WC_GOAL_QUESTION, + /** * Saveload window; %Window numbers: