changeset 18882:3b99345af1b2 draft

(svn r23731) -Add: add GSGoal::Question(), to ask a question to a(ll) company(ies). It can contain random text, and at most 3 buttons from a collection of 17
author truebrain <truebrain@openttd.org>
date Tue, 03 Jan 2012 16:36:24 +0000
parents d6e321794bf5
children 569906f583e3
files src/command.cpp src/command_type.h src/game/game_instance.cpp src/goal.cpp src/goal_gui.cpp src/goal_type.h src/gui.h src/lang/english.txt src/script/api/ai/ai_event.hpp.sq src/script/api/game/game_event.hpp.sq src/script/api/game/game_event_types.hpp.sq src/script/api/game/game_goal.hpp.sq src/script/api/script_event.hpp src/script/api/script_event_types.hpp src/script/api/script_goal.cpp src/script/api/script_goal.hpp src/script/api/template/template_event_types.hpp.sq src/script/api/template/template_goal.hpp.sq src/widgets/goal_widget.h src/window_type.h
diffstat 20 files changed, 427 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/src/command.cpp
+++ b/src/command.cpp
@@ -152,6 +152,8 @@
 CommandProc CmdCustomNewsItem;
 CommandProc CmdCreateGoal;
 CommandProc CmdRemoveGoal;
+CommandProc CmdGoalQuestion;
+CommandProc CmdGoalQuestionAnswer;
 
 CommandProc CmdLevelLand;
 
@@ -290,6 +292,8 @@
 	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
 
--- a/src/command_type.h
+++ b/src/command_type.h
@@ -266,6 +266,8 @@
 	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
--- a/src/game/game_instance.cpp
+++ b/src/game/game_instance.cpp
@@ -128,6 +128,7 @@
 	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);
--- 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 @@
 
 	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();
+}
--- 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 @@
 	_nested_goals_list_widgets, lengthof(_nested_goals_list_widgets)
 );
 
-
 void ShowGoalsList()
 {
 	AllocateWindowDescFront<GoalListWindow>(&_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<NWidgetStacked>(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);
+}
--- 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
--- a/src/gui.h
+++ b/src/gui.h
@@ -53,6 +53,7 @@
 void ShowIndustryDirectory();
 void ShowSubsidiesList();
 void ShowGoalsList();
+void ShowGoalQuestion(uint16 id, uint32 button_mask, const char *question);
 
 void ShowEstimatedCostOrIncome(Money cost, int x, int y);
 
--- a/src/lang/english.txt
+++ b/src/lang/english.txt
@@ -2669,6 +2669,29 @@
 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:
--- a/src/script/api/ai/ai_event.hpp.sq
+++ b/src/script/api/ai/ai_event.hpp.sq
@@ -47,6 +47,7 @@
 	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");
 
--- a/src/script/api/game/game_event.hpp.sq
+++ b/src/script/api/game/game_event.hpp.sq
@@ -47,6 +47,7 @@
 	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");
 
--- a/src/script/api/game/game_event_types.hpp.sq
+++ b/src/script/api/game/game_event_types.hpp.sq
@@ -249,3 +249,20 @@
 
 	SQGSEventWindowWidgetClick.PostRegister(engine);
 }
+
+
+template <> const char *GetClassName<ScriptEventGoalQuestionAnswer, ST_GS>() { return "GSEventGoalQuestionAnswer"; }
+
+void SQGSEventGoalQuestionAnswer_Register(Squirrel *engine)
+{
+	DefSQClass<ScriptEventGoalQuestionAnswer, ST_GS> 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);
+}
--- a/src/script/api/game/game_goal.hpp.sq
+++ b/src/script/api/game/game_goal.hpp.sq
@@ -21,16 +21,36 @@
 	SQGSGoal.PreRegister(engine);
 	SQGSGoal.AddConstructor<void (ScriptGoal::*)(), 1>(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);
 }
--- a/src/script/api/script_event.hpp
+++ b/src/script/api/script_event.hpp
@@ -52,6 +52,7 @@
 		ET_AIRCRAFT_DEST_TOO_FAR,
 		ET_ADMIN_PORT,
 		ET_WINDOW_WIDGET_CLICK,
+		ET_GOAL_QUESTION_ANSWER,
 	};
 
 	/**
--- 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 @@
 	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 */
--- a/src/script/api/script_goal.cpp
+++ b/src/script/api/script_goal.cpp
@@ -32,6 +32,7 @@
 {
 	CCountedPtr<Text> 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<Text> 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);
+}
--- a/src/script/api/script_goal.hpp
+++ b/src/script/api/script_goal.hpp
@@ -42,6 +42,28 @@
 		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 @@
 	 * @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 @@
 	 * 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 */
--- a/src/script/api/template/template_event_types.hpp.sq
+++ b/src/script/api/template/template_event_types.hpp.sq
@@ -230,3 +230,12 @@
 	template <> inline const ScriptEventWindowWidgetClick &GetParam(ForceType<const ScriptEventWindowWidgetClick &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(ScriptEventWindowWidgetClick *)instance; }
 	template <> inline int Return<ScriptEventWindowWidgetClick *>(HSQUIRRELVM vm, ScriptEventWindowWidgetClick *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "EventWindowWidgetClick", res, NULL, DefSQDestructorCallback<ScriptEventWindowWidgetClick>, true); return 1; }
 } // namespace SQConvert
+
+namespace SQConvert {
+	/* Allow ScriptEventGoalQuestionAnswer to be used as Squirrel parameter */
+	template <> inline ScriptEventGoalQuestionAnswer *GetParam(ForceType<ScriptEventGoalQuestionAnswer *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return  (ScriptEventGoalQuestionAnswer *)instance; }
+	template <> inline ScriptEventGoalQuestionAnswer &GetParam(ForceType<ScriptEventGoalQuestionAnswer &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(ScriptEventGoalQuestionAnswer *)instance; }
+	template <> inline const ScriptEventGoalQuestionAnswer *GetParam(ForceType<const ScriptEventGoalQuestionAnswer *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return  (ScriptEventGoalQuestionAnswer *)instance; }
+	template <> inline const ScriptEventGoalQuestionAnswer &GetParam(ForceType<const ScriptEventGoalQuestionAnswer &>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(ScriptEventGoalQuestionAnswer *)instance; }
+	template <> inline int Return<ScriptEventGoalQuestionAnswer *>(HSQUIRRELVM vm, ScriptEventGoalQuestionAnswer *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "EventGoalQuestionAnswer", res, NULL, DefSQDestructorCallback<ScriptEventGoalQuestionAnswer>, true); return 1; }
+} // namespace SQConvert
--- a/src/script/api/template/template_goal.hpp.sq
+++ b/src/script/api/template/template_goal.hpp.sq
@@ -17,6 +17,8 @@
 	template <> inline int Return<ScriptGoal::GoalID>(HSQUIRRELVM vm, ScriptGoal::GoalID res) { sq_pushinteger(vm, (int32)res); return 1; }
 	template <> inline ScriptGoal::GoalType GetParam(ForceType<ScriptGoal::GoalType>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (ScriptGoal::GoalType)tmp; }
 	template <> inline int Return<ScriptGoal::GoalType>(HSQUIRRELVM vm, ScriptGoal::GoalType res) { sq_pushinteger(vm, (int32)res); return 1; }
+	template <> inline ScriptGoal::QuestionButton GetParam(ForceType<ScriptGoal::QuestionButton>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (ScriptGoal::QuestionButton)tmp; }
+	template <> inline int Return<ScriptGoal::QuestionButton>(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<ScriptGoal *>, HSQUIRRELVM vm, int index, SQAutoFreePointers *ptr) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return  (ScriptGoal *)instance; }
--- a/src/widgets/goal_widget.h
+++ b/src/widgets/goal_widget.h
@@ -19,4 +19,13 @@
 	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 */
--- a/src/window_type.h
+++ b/src/window_type.h
@@ -126,6 +126,13 @@
 	 */
 	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: