summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ai/ai.c91
-rw-r--r--ai/ai.h2
-rw-r--r--ai/ai_event.h7
-rw-r--r--command.c12
4 files changed, 107 insertions, 5 deletions
diff --git a/ai/ai.c b/ai/ai.c
index df457f662..759a764f4 100644
--- a/ai/ai.c
+++ b/ai/ai.c
@@ -15,6 +15,13 @@
#include "ai_event.h"
#undef DEF_EVENTS
+/* Here we keep track of all commands that are called via AI_DoCommandChecked,
+ * in special we save the unique_id here. Now this id is given back
+ * when the command fails or succeeds and is detected as added in this storage. */
+static AICommand *command_uid[MAX_PLAYERS];
+static AICommand *command_uid_tail[MAX_PLAYERS];
+static uint uids[MAX_PLAYERS];
+
/**
* Dequeues commands put in the queue via AI_PutCommandInQueue.
*/
@@ -122,6 +129,72 @@ int32 AI_DoCommand(uint tile, uint32 p1, uint32 p2, uint32 flags, uint procc)
return res;
}
+int32 AI_DoCommandChecked(uint tile, uint32 p1, uint32 p2, uint32 flags, uint procc)
+{
+ AICommand *new;
+ uint unique_id = uids[_current_player]++;
+ int32 res;
+
+ res = AI_DoCommand(tile, p1, p2, flags & ~DC_EXEC, procc);
+ if (CmdFailed(res))
+ return -1;
+
+ /* Save the command and his things, together with the unique_id */
+ new = malloc(sizeof(AICommand));
+ new->tile = tile;
+ new->p1 = p1;
+ new->p2 = p2;
+ new->procc = procc;
+ new->next = NULL;
+ new->dp[0] = unique_id;
+
+ /* Add it to the back of the list */
+ if (command_uid_tail[_current_player] == NULL)
+ command_uid_tail[_current_player] = new;
+ else
+ command_uid_tail[_current_player]->next = new;
+
+ if (command_uid[_current_player] == NULL)
+ command_uid[_current_player] = new;
+
+ AI_DoCommand(tile, p1, p2, flags, procc);
+ return unique_id;
+}
+
+/**
+ * A command is executed for real, and is giving us his result (failed yes/no). Inform the AI with it via
+ * an event.Z
+ */
+void AI_CommandResult(uint32 cmd, uint32 p1, uint32 p2, TileIndex tile, bool succeeded)
+{
+ AICommand *command = command_uid[_current_player];
+
+ if (command == NULL)
+ return;
+
+ if (command->procc != (cmd & 0xFF) || command->p1 != p1 || command->p2 != p2 || command->tile != tile) {
+ /* If we come here, we see a command that isn't at top in our list. This is okay, if the command isn't
+ * anywhere else in our list, else we have a big problem.. so check for that. If it isn't in our list,
+ * it is okay, else, drop the game.
+ * Why do we have a big problem in the case it is in our list? Simply, we have a command sooner in
+ * our list that wasn't triggered to be failed or succeeded, so it is sitting there without an
+ * event, so the script will never know if it succeeded yes/no, so it can hang.. this is VERY bad
+ * and should never ever happen. */
+ while (command != NULL && (command->procc != (cmd & 0xFF) || command->p1 != p1 || command->p2 != p2 || command->tile != tile)) {
+ command = command->next;
+ }
+ assert(command == NULL);
+ return;
+ }
+
+ command_uid[_current_player] = command_uid[_current_player]->next;
+ if (command_uid[_current_player] == NULL)
+ command_uid_tail[_current_player] = NULL;
+
+ ai_event(_current_player, succeeded ? ottd_Event_CommandSucceeded : ottd_Event_CommandFailed, tile, command->dp[0]);
+ free(command);
+}
+
/**
* Run 1 tick of the AI. Don't overdo it, keep it realistic.
*/
@@ -249,7 +322,7 @@ void AI_LoadAIControl(void)
*/
void AI_PrintErrorStack(gpmi_err_stack_t *entry, char *string)
{
- DEBUG(ai, 0)(string);
+ DEBUG(ai, 0)("%s", string);
}
#endif /* GPMI */
@@ -305,6 +378,17 @@ void AI_PlayerDied(PlayerID player)
/* Called if this AI died */
_ai_player[player].active = false;
+ if (command_uid[player] != NULL) {
+ while (command_uid[player] != NULL) {
+ AICommand *command = command_uid[player];
+ command_uid[player] = command->next;
+ free(command);
+ }
+
+ command_uid[player] = NULL;
+ command_uid_tail[player] = NULL;
+ }
+
#ifdef GPMI
if (_ai_player[player].module != NULL)
gpmi_mod_unload(_ai_player[player].module);
@@ -326,7 +410,10 @@ void AI_Initialize(void)
AI_Uninitialize();
memset(&_ai, 0, sizeof(_ai));
- memset(&_ai_player, 0, sizeof(_ai_player));
+ memset(_ai_player, 0, sizeof(_ai_player));
+ memset(uids, 0, sizeof(uids));
+ memset(command_uid, 0, sizeof(command_uid));
+ memset(command_uid_tail, 0, sizeof(command_uid_tail));
_ai.network_client = tmp_ai_network_client;
_ai.network_playas = OWNER_SPECTATOR;
diff --git a/ai/ai.h b/ai/ai.h
index bb56ed27d..c68eb40b4 100644
--- a/ai/ai.h
+++ b/ai/ai.h
@@ -57,6 +57,8 @@ void AI_RunGameLoop(void);
void AI_Initialize(void);
void AI_Uninitialize(void);
int32 AI_DoCommand(uint tile, uint32 p1, uint32 p2, uint32 flags, uint procc);
+int32 AI_DoCommandChecked(uint tile, uint32 p1, uint32 p2, uint32 flags, uint procc);
+void AI_CommandResult(uint32 cmd, uint32 p1, uint32 p2, TileIndex tile, bool failed);
/** Is it allowed to start a new AI.
* This function checks some boundries to see if we should launch a new AI.
diff --git a/ai/ai_event.h b/ai/ai_event.h
index 006f7b740..684ce54b5 100644
--- a/ai/ai_event.h
+++ b/ai/ai_event.h
@@ -9,9 +9,9 @@
/* This is how we call events (with safety-check) to GPMI */
/* XXX -- This macro (vararg macro) isn't supported on old GCCs, but GPMI
* is using them too, so it isn't a real problem, yet */
-# define ai_event(player, params...) \
+# define ai_event(player, event, params...) \
if (player < MAX_PLAYERS && _ai_player[player].module != NULL) \
- gpmi_event(_ai_player[player].module, params)
+ gpmi_event(_ai_player[player].module, event, params)
#else
/* If GPMI isn't loaded, don't do a thing with the events (for now at least)
@@ -40,6 +40,9 @@
#endif /* DEF_EVENTS */
/* ------------ All available events -------------- */
+DEF_EVENTS int ottd_Event_CommandFailed INITIAL_SET; // (tile, unique_id)
+DEF_EVENTS int ottd_Event_CommandSucceeded INITIAL_SET; // (tile, unique_id)
+
DEF_EVENTS int ottd_Event_BuildStation INITIAL_SET; // (station_index, station_tile)
DEF_EVENTS int ottd_Event_BuildRoadStation INITIAL_SET; // (station_index, station_tile)
diff --git a/command.c b/command.c
index b99ea82eb..5b32156a8 100644
--- a/command.c
+++ b/command.c
@@ -10,6 +10,7 @@
#include "player.h"
#include "network.h"
#include "variables.h"
+#include "ai/ai.h"
const char* _cmd_text = NULL;
@@ -476,10 +477,16 @@ bool DoCommandP(TileIndex tile, uint32 p1, uint32 p2, CommandCallback *callback,
res = proc(x,y, flags, p1, p2);
if (CmdFailed(res)) {
if (res & 0xFFFF) _error_message = res & 0xFFFF;
+ /* Trigger an event special for the AI, so it knows the build has failed
+ * Because the commands are always delayed, this is the only way. */
+ AI_CommandResult(cmd, p1, p2, tile, false);
goto show_error;
}
// no money? Only check if notest is off
- if (!notest && res != 0 && !CheckPlayerHasMoney(res)) goto show_error;
+ if (!notest && res != 0 && !CheckPlayerHasMoney(res)) {
+ AI_CommandResult(cmd, p1, p2, tile, false);
+ goto show_error;
+ }
}
#ifdef ENABLE_NETWORK
@@ -514,10 +521,13 @@ bool DoCommandP(TileIndex tile, uint32 p1, uint32 p2, CommandCallback *callback,
} else {
if (CmdFailed(res2)) {
if (res2 & 0xFFFF) _error_message = res2 & 0xFFFF;
+ AI_CommandResult(cmd, p1, p2, tile, false);
goto show_error;
}
}
+ AI_CommandResult(cmd, p1, p2, tile, true);
+
SubtractMoneyFromPlayer(res2);
if (IsLocalPlayer() && _game_mode != GM_EDITOR) {