summaryrefslogtreecommitdiff
path: root/src/ai
diff options
context:
space:
mode:
Diffstat (limited to 'src/ai')
-rw-r--r--src/ai/ai.hpp27
-rw-r--r--src/ai/ai_core.cpp27
-rw-r--r--src/ai/ai_gui.cpp68
3 files changed, 102 insertions, 20 deletions
diff --git a/src/ai/ai.hpp b/src/ai/ai.hpp
index abe3f48ae..d3381fb8a 100644
--- a/src/ai/ai.hpp
+++ b/src/ai/ai.hpp
@@ -68,13 +68,30 @@ public:
static void Stop(CompanyID company);
/**
- * Suspend an AI for the reminder of the current tick. If the AI is
- * in a state when it cannot be suspended, it will continue to run
- * until it can be suspended.
- * @param company The company for which the AI should be suspended.
+ * Suspend the AI and then pause execution of the script. The script
+ * will not be resumed from its suspended state until the script has
+ * been unpaused.
+ * @param company The company for which the AI should be paused.
* @pre Company::IsValidAiID(company)
*/
- static void Suspend(CompanyID company);
+ static void Pause(CompanyID company);
+
+ /**
+ * Resume execution of the AI. This function will not actually execute
+ * the script, but set a flag so that the script is executed my the usual
+ * mechanism that executes the script.
+ * @param company The company for which the AI should be unpaused.
+ * @pre Company::IsValidAiID(company)
+ */
+ static void Unpause(CompanyID company);
+
+ /**
+ * Checks if the AI is paused.
+ * @param company The company for which to check if the AI is paused.
+ * @pre Company::IsValidAiID(company)
+ * @return true if the AI is paused, otherwise false.
+ */
+ static bool IsPaused(CompanyID company);
/**
* Kill any and all AIs we manage.
diff --git a/src/ai/ai_core.cpp b/src/ai/ai_core.cpp
index e2f04622b..ab8510f09 100644
--- a/src/ai/ai_core.cpp
+++ b/src/ai/ai_core.cpp
@@ -112,14 +112,35 @@
DeleteWindowById(WC_AI_SETTINGS, company);
}
-/* static */ void AI::Suspend(CompanyID company)
+/* static */ void AI::Pause(CompanyID company)
{
- if (_networking && !_network_server) return;
+ /* The reason why dedicated servers are forbidden to execute this
+ * command is not because it is unsafe, but because there is no way
+ * for the server owner to unpause the script again. */
+ if (_network_dedicated) return;
+
+ Backup<CompanyByte> cur_company(_current_company, company, FILE_LINE);
+ Company::Get(company)->ai_instance->Pause();
+
+ cur_company.Restore();
+}
+
+/* static */ void AI::Unpause(CompanyID company)
+{
+ Backup<CompanyByte> cur_company(_current_company, company, FILE_LINE);
+ Company::Get(company)->ai_instance->Unpause();
+ cur_company.Restore();
+}
+
+/* static */ bool AI::IsPaused(CompanyID company)
+{
Backup<CompanyByte> cur_company(_current_company, company, FILE_LINE);
- Company::Get(company)->ai_instance->Suspend();
+ bool paused = Company::Get(company)->ai_instance->IsPaused();
cur_company.Restore();
+
+ return paused;
}
/* static */ void AI::KillAll()
diff --git a/src/ai/ai_gui.cpp b/src/ai/ai_gui.cpp
index 6f591038d..2eea88eec 100644
--- a/src/ai/ai_gui.cpp
+++ b/src/ai/ai_gui.cpp
@@ -961,11 +961,15 @@ void ShowAIConfigWindow()
* state of the script. (dead or alive)
* @param button the button to update.
* @param dead true if the script is dead, otherwise false.
+ * @param paused true if the script is paused, otherwise false.
* @return true if the colour was changed and the window need to be marked as dirty.
*/
-static bool SetScriptButtonColour(NWidgetCore &button, bool dead)
+static bool SetScriptButtonColour(NWidgetCore &button, bool dead, bool paused)
{
- Colours colour = dead ? COLOUR_RED : COLOUR_GREY;
+ /* Dead scripts are indicated with red background and
+ * paused scripts are indicated with yellow background. */
+ Colours colour = dead ? COLOUR_RED :
+ (paused ? COLOUR_YELLOW : COLOUR_GREY);
if (button.colour != colour) {
button.colour = colour;
return true;
@@ -1035,8 +1039,10 @@ struct AIDebugWindow : public QueryStringBaseWindow {
/* Restore button state from static class variables */
if (ai_debug_company == OWNER_DEITY) {
this->LowerWidget(WID_AID_SCRIPT_GAME);
+ this->SetWidgetDisabledState(WID_AID_CONTINUE_BTN, !Game::IsPaused());
} else if (ai_debug_company != INVALID_COMPANY) {
this->LowerWidget(ai_debug_company + WID_AID_COMPANY_BUTTON_START);
+ this->SetWidgetDisabledState(WID_AID_CONTINUE_BTN, !AI::IsPaused(ai_debug_company));
}
this->SetWidgetLoweredState(WID_AID_BREAK_STR_ON_OFF_BTN, this->break_check_enabled);
this->SetWidgetLoweredState(WID_AID_MATCH_CASE_BTN, this->case_sensitive_break_check);
@@ -1107,11 +1113,12 @@ struct AIDebugWindow : public QueryStringBaseWindow {
dirty = true;
}
- /* Mark dead AIs by red background. */
+ /* Mark dead/paused AIs by setting the background colour. */
bool dead = valid && Company::Get(i)->ai_instance->IsDead();
+ bool paused = valid && Company::Get(i)->ai_instance->IsPaused();
/* Re-paint if the button was updated.
* (note that it is intentional that SetScriptButtonColour is always called) */
- dirty = SetScriptButtonColour(*button, dead) || dirty;
+ dirty = SetScriptButtonColour(*button, dead, paused) || dirty;
/* Do we need a repaint? */
if (dirty) this->SetDirty();
@@ -1125,8 +1132,9 @@ struct AIDebugWindow : public QueryStringBaseWindow {
/* Set button colour for Game Script. */
GameInstance *game = Game::GetInstance();
bool dead = game != NULL && game->IsDead();
+ bool paused = game != NULL && game->IsPaused();
NWidgetCore *button = this->GetWidget<NWidgetCore>(WID_AID_SCRIPT_GAME);
- if (SetScriptButtonColour(*button, dead)) {
+ if (SetScriptButtonColour(*button, dead, paused)) {
/* Re-paint if the button was updated. */
this->SetWidgetDirty(WID_AID_SCRIPT_GAME);
}
@@ -1243,10 +1251,13 @@ struct AIDebugWindow : public QueryStringBaseWindow {
if (ai_debug_company == OWNER_DEITY) {
this->LowerWidget(WID_AID_SCRIPT_GAME);
+ this->SetWidgetDisabledState(WID_AID_CONTINUE_BTN, !Game::IsPaused());
} else {
this->LowerWidget(ai_debug_company + WID_AID_COMPANY_BUTTON_START);
+ this->SetWidgetDisabledState(WID_AID_CONTINUE_BTN, !AI::IsPaused(ai_debug_company));
}
+ this->highlight_row = -1; // The highlight of one AI make little sense for another AI.
this->autoscroll = true;
this->last_vscroll_pos = this->vscroll->GetPosition();
this->SetDirty();
@@ -1292,8 +1303,35 @@ struct AIDebugWindow : public QueryStringBaseWindow {
break;
case WID_AID_CONTINUE_BTN:
- /* Unpause */
- DoCommandP(0, PM_PAUSED_NORMAL, 0, CMD_PAUSE);
+ /* Unpause current AI / game script and mark the corresponding script button dirty. */
+ if (ai_debug_company == OWNER_DEITY) {
+ Game::Unpause();
+ this->SetWidgetDirty(WID_AID_SCRIPT_GAME);
+ } else {
+ AI::Unpause(ai_debug_company);
+ this->SetWidgetDirty(WID_AID_COMPANY_BUTTON_START + ai_debug_company);
+ }
+
+ /* If the last AI/Game Script is unpaused, unpause the game too. */
+ if ((_pause_mode & PM_PAUSED_NORMAL) == PM_PAUSED_NORMAL) {
+ bool all_unpaused = !Game::IsPaused();
+ if (all_unpaused) {
+ Company *c;
+ FOR_ALL_COMPANIES(c) {
+ if (c->is_ai && AI::IsPaused(c->index)) {
+ all_unpaused = false;
+ break;
+ }
+ }
+ if (all_unpaused) {
+ /* All scripts have been unpaused => unpause the game. */
+ DoCommandP(0, PM_PAUSED_NORMAL, 0, CMD_PAUSE);
+ }
+ }
+ }
+
+ this->highlight_row = -1;
+ this->SetWidgetDirty(WID_AID_LOG_PANEL);
this->DisableWidget(WID_AID_CONTINUE_BTN);
this->RaiseWidget(WID_AID_CONTINUE_BTN); // Disabled widgets don't raise themself
break;
@@ -1334,9 +1372,8 @@ struct AIDebugWindow : public QueryStringBaseWindow {
if (gui_scope && data == -2) {
/* The continue button should be disabled when the game is unpaused and
- * it was previously paused by the break string ( = a line in the log
- * was highlighted )*/
- if ((_pause_mode & PM_PAUSED_NORMAL) == PM_UNPAUSED && this->highlight_row != -1) {
+ * it was previously paused. */
+ if ((_pause_mode & PM_PAUSED_NORMAL) == PM_UNPAUSED && !this->IsWidgetDisabled(WID_AID_CONTINUE_BTN)) {
this->DisableWidget(WID_AID_CONTINUE_BTN);
this->SetWidgetDirty(WID_AID_CONTINUE_BTN);
this->SetWidgetDirty(WID_AID_LOG_PANEL);
@@ -1346,7 +1383,7 @@ struct AIDebugWindow : public QueryStringBaseWindow {
/* If the log message is related to the active company tab, check the break string.
* This needs to be done in gameloop-scope, so the AI is suspended immediately. */
- if (ai_debug_company != OWNER_DEITY && !gui_scope && data == ai_debug_company && this->break_check_enabled && !this->break_string_filter.IsEmpty()) {
+ if (!gui_scope && data == ai_debug_company && this->break_check_enabled && !this->break_string_filter.IsEmpty()) {
/* Get the log instance of the active company */
ScriptLog::LogData *log = this->GetLogPointer();
@@ -1354,7 +1391,14 @@ struct AIDebugWindow : public QueryStringBaseWindow {
this->break_string_filter.ResetState();
this->break_string_filter.AddLine(log->lines[log->pos]);
if (this->break_string_filter.GetState()) {
- AI::Suspend(ai_debug_company);
+ /* Pause execution of script. */
+ if (ai_debug_company == OWNER_DEITY) {
+ Game::Pause();
+ } else {
+ AI::Pause(ai_debug_company);
+ }
+
+ /* Pause the game. */
if ((_pause_mode & PM_PAUSED_NORMAL) == PM_UNPAUSED) {
DoCommandP(0, PM_PAUSED_NORMAL, 1, CMD_PAUSE);
}