(svn r12519) [NoAI] -Add: added AILog with Info(), Warning(), and Error() noai
authortruebrain
Mon, 31 Mar 2008 18:33:33 +0000
branchnoai
changeset 9851 a5f5a7cf2b61
parent 9850 d3228c6a1376
child 9852 ef26d6828e27
(svn r12519) [NoAI] -Add: added AILog with Info(), Warning(), and Error()
[NoAI] -Add: redirect AI outputs to the AI Debug GUI to show it per AI, in a clear way ingame (no more need for stderr viewing)
NOTE: it still does output to stderr, but on an other DEBUG() level (depending on the message).
bin/ai/regression/regression.txt
bin/ai/regression/run.sh
bin/ai/wrightai/main.nut
source.list
src/ai/ai_gui.cpp
src/ai/ai_squirrel.cpp
src/ai/api/ai_controller.cpp
src/ai/api/ai_controller.hpp
src/ai/api/ai_log.cpp
src/ai/api/ai_log.hpp
src/ai/api/ai_log.hpp.sq
src/ai/api/ai_object.cpp
src/ai/api/ai_object.hpp
--- a/bin/ai/regression/regression.txt	Mon Mar 31 17:20:44 2008 +0000
+++ b/bin/ai/regression/regression.txt	Mon Mar 31 18:33:33 2008 +0000
@@ -6309,3 +6309,4 @@
       GetFromIndustryID: 65535
       GetToIndustryID:   21
   IsEventWaiting:        false
+ERROR: We've got a suicidal AI for player 1
--- a/bin/ai/regression/run.sh	Mon Mar 31 17:20:44 2008 +0000
+++ b/bin/ai/regression/run.sh	Mon Mar 31 18:33:33 2008 +0000
@@ -18,7 +18,7 @@
 if [ -n "$gdb" ]; then
 	$gdb ./openttd -a regression -x -c ai/regression/regression.cfg $params -g ai/regression/regression.sav
 else
-	./openttd -a regression -x -c ai/regression/regression.cfg $params -g ai/regression/regression.sav 2>tmp.stderr | awk '{ gsub("0x\\(nil\\)", "0x00000000", $0); print $0; }' > tmp.regression
+	./openttd -a regression -x -c ai/regression/regression.cfg $params -g ai/regression/regression.sav -d ai=2 2>&1 | awk '{ gsub("0x\\(nil\\)", "0x00000000", $0); gsub("^dbg: \\[ai\\]", "", $0); gsub("^ ", "ERROR: ", $0); gsub("ERROR: \\[1\\] ", "", $0); print $0; }' > tmp.regression
 fi
 
 if [ -z "$gdb" ]; then
@@ -30,16 +30,10 @@
 		echo "$res"
 	fi
 	echo ""
-	stderr="`cat tmp.stderr`"
-	if [ -n "$stderr" ]; then
-		echo "OpenTTD gave this on stderr:"
-		echo "$stderr"
-	fi
-	echo ""
 	echo "Regression test done"
 fi
 
-rm -f ai/regression/main.nut tmp.stderr
+rm -f ai/regression/main.nut
 
 if [ "$1" != "-k" ]; then
 	rm -f tmp.regression
--- a/bin/ai/wrightai/main.nut	Mon Mar 31 17:20:44 2008 +0000
+++ b/bin/ai/wrightai/main.nut	Mon Mar 31 18:33:33 2008 +0000
@@ -37,7 +37,7 @@
 
 	local loan = money - AICompany.GetBankBalance(AICompany.MY_COMPANY) + AICompany.GetLoanInterval() + AICompany.GetLoanAmount();
 	loan = loan - loan % AICompany.GetLoanInterval();
-	print(this.name + ": [INFO] Need a loan to get " + money + ": " + loan);
+	AILog.Info("Need a loan to get " + money + ": " + loan);
 	AICompany.SetLoanAmount(loan);
 }
 
@@ -52,7 +52,7 @@
 	/* Get enough money to work with */
 	this.GetMoney(150000);
 
-	print(this.name + ": [INFO] Trying to build an airport route");
+	AILog.Info("Trying to build an airport route");
 
 	local tile_1 = this.FindSuitableAirportSpot(airport_type, 0);
 	if (tile_1 < 0) return -1;
@@ -64,13 +64,13 @@
 
 	/* Build the airports for real */
 	if (!AIAirport.BuildAirport(tile_1, airport_type)) {
-		print(this.name + ": [ERROR] Although the testing told us we could build 2 airports, it still failed on the first airport at tile " + tile_1 + ".");
+		AILog.Error("Although the testing told us we could build 2 airports, it still failed on the first airport at tile " + tile_1 + ".");
 		this.towns_used.RemoveValue(tile_1);
 		this.towns_used.RemoveValue(tile_2);
 		return -3;
 	}
 	if (!AIAirport.BuildAirport(tile_2, airport_type)) {
-		print(this.name + ": [ERROR] Although the testing told us we could build 2 airports, it still failed on the second airport at tile " + tile_2 + ".");
+		AILog.Error("Although the testing told us we could build 2 airports, it still failed on the second airport at tile " + tile_2 + ".");
 		AIAirport.RemoveAirport(tile_1);
 		this.towns_used.RemoveValue(tile_1);
 		this.towns_used.RemoveValue(tile_2);
@@ -86,7 +86,7 @@
 		return ret;
 	}
 
-	print(this.name + ": [INFO] Done building a route");
+	AILog.Info("Done building a route");
 	return ret;
 }
 
@@ -116,12 +116,12 @@
 	engine = engine_list.Begin();
 
 	if (!AIEngine.IsValidEngine(engine)) {
-		print(this.name + ": [ERROR] Couldn't find a suitable engine");
+		AILog.Error("Couldn't find a suitable engine");
 		return -5;
 	}
 	local vehicle = AIVehicle.BuildVehicle(hangar, engine);
 	if (!AIVehicle.IsValidVehicle(vehicle)) {
-		print(this.name + ": [ERROR] Couldn't build the aircraft");
+		AILog.Error("Couldn't build the aircraft");
 		return -6;
 	}
 	/* Send him on his way */
@@ -132,7 +132,7 @@
 	this.route_1.AddItem(vehicle, tile_1);
 	this.route_2.AddItem(vehicle, tile_2);
 
-	print(this.name + ": [INFO] Done building an aircraft");
+	AILog.Info("Done building an aircraft");
 
 	return 0;
 }
@@ -199,7 +199,7 @@
 			if (good_tile == 0) continue;
 		}
 
-		print(this.name + ": [INFO] Found a good spot for an airport in town " + town + " at tile " + tile);
+		AILog.Info("Found a good spot for an airport in town " + town + " at tile " + tile);
 
 		/* Make the town as used, so we don't use it again */
 		this.towns_used.AddItem(town, tile);
@@ -207,7 +207,7 @@
 		return tile;
 	}
 
-	print(this.name + ": [INFO] Couldn't find a suitable town to build an airport in");
+	AILog.Info("Couldn't find a suitable town to build an airport in");
 	return -1;
 }
 
@@ -225,7 +225,7 @@
 		if (profit < 10000 && AIVehicle.GetProfitThisYear(i) < 10000) {
 			/* Send the vehicle to depot if we didn't do so yet */
 			if (!vehicle_to_depot.rawin(i) || vehicle_to_depot.rawget(i) != true) {
-				print(this.name + ": [INFO] Sending " + i + " to depot as profit is: " + profit + " / " + AIVehicle.GetProfitThisYear(i));
+				AILog.Info("Sending " + i + " to depot as profit is: " + profit + " / " + AIVehicle.GetProfitThisYear(i));
 				AIVehicle.SendVehicleToDepot(i);
 				vehicle_to_depot.rawset(i, true);
 			}
@@ -233,7 +233,7 @@
 		/* Try to sell it over and over till it really is in the depot */
 		if (vehicle_to_depot.rawin(i) && vehicle_to_depot.rawget(i) == true) {
 			if (AIVehicle.SellVehicle(i)) {
-				print(this.name + ": [INFO] Selling " + i + " as it finally is in a depot.");
+				AILog.Info("Selling " + i + " as it finally is in a depot.");
 				/* Check if we are the last one serving those airports; else sell the airports */
 				local list2 = AIVehicleList_Station(AIStation.GetStationID(this.route_1.GetValue(i)));
 				if (list2.Count() == 0) this.SellAirports(i);
@@ -266,7 +266,7 @@
 		/* Do not build a new vehicle if we bought a new one in the last DISTANCE days */
 		if (list2.Count() != 0) continue;
 
-		print(this.name + ": [INFO] Station " + i + " (" + AIStation.GetLocation(i) + ") has too many cargo, adding a new vehicle for the route.");
+		AILog.Info("Station " + i + " (" + AIStation.GetLocation(i) + ") has too many cargo, adding a new vehicle for the route.");
 
 		/* Make sure we have enough money */
 		this.GetMoney(50000);
@@ -281,7 +281,7 @@
   */
 function WrightAI::SellAirports(i) {
 	/* Remove the airports */
-	print(this.name + ": [INFO] Removing airports as nobody serves them anymore.");
+	AILog.Info("Removing airports as nobody serves them anymore.");
 	AIAirport.RemoveAirport(this.route_1.GetValue(i));
 	AIAirport.RemoveAirport(this.route_2.GetValue(i));
 	/* Free the towns_used entries */
@@ -300,7 +300,7 @@
 			case AIEvent.AI_ET_CRASHED_VEHICLE:
 				local ec = AIEventVehicleCrash.Convert(e);
 				local v = ec.GetVehicleID();
-				print(this.name + ": [INFO] We have a crashed vehicle (" + v + "), buying a new one as replacement");
+				AILog.Info("We have a crashed vehicle (" + v + "), buying a new one as replacement");
 				this.BuildAircraft(this.route_1.GetValue(v), this.route_2.GetValue(v));
 				this.route_1.RemoveItem(v);
 				this.route_2.RemoveItem(v);
@@ -326,7 +326,7 @@
 	}
 	this.name = AICompany.GetCompanyName(AICompany.MY_COMPANY);
 	/* Say hello to the user */
-	print(this.name + ": Welcome to WrightAI. I will be building airports all day long.");
+	AILog.Info("Welcome to WrightAI. I will be building airports all day long.");
 
 	/* We start with almost no loan, and we take a loan when we want to build something */
 	AICompany.SetLoanAmount(AICompany.GetLoanInterval());
@@ -350,7 +350,7 @@
 			else if (ret < 0 && ticker == 0) {
 				/* The AI failed to build a first airport and is deemed */
 				AICompany.SetCompanyName("FailedWrightAI");
-				print(this.name + ": Failed to build first airport route, now giving up building. Repaying loan. Have a nice day!");
+				AILog.Error("Failed to build first airport route, now giving up building. Repaying loan. Have a nice day!");
 				AICompany.SetLoanAmount(0);
 				return;
 			}
--- a/source.list	Mon Mar 31 17:20:44 2008 +0000
+++ b/source.list	Mon Mar 31 18:33:33 2008 +0000
@@ -488,6 +488,7 @@
 ai/api/ai_industry.hpp
 ai/api/ai_industrylist.hpp
 ai/api/ai_list.hpp
+ai/api/ai_log.hpp
 ai/api/ai_map.hpp
 ai/api/ai_marine.hpp
 ai/api/ai_object.hpp
@@ -528,6 +529,7 @@
 ai/api/ai_industry.cpp
 ai/api/ai_industrylist.cpp
 ai/api/ai_list.cpp
+ai/api/ai_log.cpp
 ai/api/ai_map.cpp
 ai/api/ai_marine.cpp
 ai/api/ai_object.cpp
--- a/src/ai/ai_gui.cpp	Mon Mar 31 17:20:44 2008 +0000
+++ b/src/ai/ai_gui.cpp	Mon Mar 31 18:33:33 2008 +0000
@@ -6,6 +6,7 @@
 #include "../openttd.h"
 #include "../gui.h"
 #include "../window_gui.h"
+#include "../player_func.h"
 #include "../player_base.h"
 #include "../player_gui.h"
 #include "../economy_func.h"
@@ -18,6 +19,8 @@
 #include "../gfx_func.h"
 #include "../debug.h"
 #include "ai_factory.hpp"
+#include "api/ai_object.hpp"
+#include "api/ai_log.hpp"
 
 #include "table/strings.h"
 #include "../table/sprites.h"
@@ -112,6 +115,37 @@
 			AIFactoryBase *factory = AI_GetPlayerFactory(_ai_debug_player);
 			DoDrawString(factory->GetAIName(), 7, 34, TC_BLACK);
 
+			PlayerID old_cp = _current_player;
+			_current_player = _ai_debug_player;
+
+			if (AIObject::GetLogPointer() == NULL) {
+				_current_player = old_cp;
+				break;
+			}
+			AILog::LogData *log = (AILog::LogData *)AIObject::GetLogPointer();
+
+			for (int i = 0; i < log->count; i++) {
+				uint pos = (log->count + log->pos - i) % log->count;
+				if (log->type[pos] == 0) break;
+
+				int y = 12 * i;
+				if (y >= w->widget[AID_WIDGET_LOG_PANEL].bottom - w->widget[AID_WIDGET_LOG_PANEL].top - 12) break;
+
+				uint colour;
+				switch (log->type[pos]) {
+					case AILog::LOG_SQ_INFO:  colour = TC_BLACK;  break;
+					case AILog::LOG_SQ_ERROR: colour = TC_RED;    break;
+					case AILog::LOG_INFO:     colour = TC_BLACK;  break;
+					case AILog::LOG_WARNING:  colour = TC_YELLOW; break;
+					case AILog::LOG_ERROR:    colour = TC_RED;    break;
+					default:                  colour = TC_BLACK;  break;
+				}
+
+				DoDrawStringTruncated(log->lines[pos], 7, w->widget[AID_WIDGET_LOG_PANEL].top + 6 + y, colour, w->widget[AID_WIDGET_LOG_PANEL].right - w->widget[AID_WIDGET_LOG_PANEL].left - 14);
+			}
+
+			_current_player = old_cp;
+
 			break;
 		}
 
--- a/src/ai/ai_squirrel.cpp	Mon Mar 31 17:20:44 2008 +0000
+++ b/src/ai/ai_squirrel.cpp	Mon Mar 31 18:33:33 2008 +0000
@@ -43,6 +43,7 @@
 #include "api/ai_industry.hpp.sq"
 #include "api/ai_industrylist.hpp.sq"
 #include "api/ai_list.hpp.sq"
+#include "api/ai_log.hpp.sq"
 #include "api/ai_map.hpp.sq"
 #include "api/ai_marine.hpp.sq"
 #include "api/ai_order.hpp.sq"
@@ -238,6 +239,7 @@
 	SQAIIndustryList_CargoAccepting_Register(this->engine);
 	SQAIIndustryList_CargoProducing_Register(this->engine);
 	SQAIList_Register(this->engine);
+	SQAILog_Register(this->engine);
 	SQAIMap_Register(this->engine);
 	SQAIMarine_Register(this->engine);
 	SQAIOrder_Register(this->engine);
--- a/src/ai/api/ai_controller.cpp	Mon Mar 31 17:20:44 2008 +0000
+++ b/src/ai/api/ai_controller.cpp	Mon Mar 31 18:33:33 2008 +0000
@@ -3,6 +3,8 @@
 /** @file ai_controller.cpp Implementation of AIControler. */
 
 #include "ai_controller.hpp"
+#include "ai_object.hpp"
+#include "ai_log.hpp"
 #include "../../stdafx.h"
 #include "../../openttd.h"
 #include "../../player_func.h"
@@ -15,6 +17,5 @@
 
 /* static */ void AIController::Print(bool error_msg, const char *message)
 {
-	if (error_msg) fprintf(stderr, "%s", message);
-	else printf("%s", message);
+	AILog::Log(error_msg ? AILog::LOG_SQ_ERROR : AILog::LOG_SQ_INFO, message);
 }
--- a/src/ai/api/ai_controller.hpp	Mon Mar 31 17:20:44 2008 +0000
+++ b/src/ai/api/ai_controller.hpp	Mon Mar 31 18:33:33 2008 +0000
@@ -72,9 +72,11 @@
 	static void Sleep(uint ticks);
 
 	/**
-	 * Print a message to the AI log.
-	 * @param error_msg If true, it is marked as error message.
-	 * @param message The message you want to log.
+	 * When Squirrel triggers a print, this function is called.
+	 *  Squirrel calls this when 'print' is used, or when the script made an error.
+	 * @param error_msg If true, it is a Squirrel error message.
+	 * @param message The message Squirrel logged.
+	 * @note Use AILog.Info/Warning/Error instead of 'print'.
 	 */
 	static void Print(bool error_msg, const char *message);
 };
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ai/api/ai_log.cpp	Mon Mar 31 18:33:33 2008 +0000
@@ -0,0 +1,68 @@
+/* $Id$ */
+
+/** @file ai_log.cpp Implementation of AILog. */
+
+#include "ai_log.hpp"
+#include "../../core/alloc_func.hpp"
+#include "../../player_func.h"
+#include "../../debug.h"
+
+/* static */ void AILog::Info(const char *message)
+{
+	AILog::Log(LOG_INFO, message);
+}
+
+/* static */ void AILog::Warning(const char *message)
+{
+	AILog::Log(LOG_WARNING, message);
+}
+
+/* static */ void AILog::Error(const char *message)
+{
+	AILog::Log(LOG_ERROR, message);
+}
+
+/* static */ void AILog::Log(AILog::AILogType level, const char *message)
+{
+	if (AIObject::GetLogPointer() == NULL) {
+		AIObject::GetLogPointer() = new LogData();
+		LogData *log = (LogData *)AIObject::GetLogPointer();
+
+		log->lines = CallocT<char *>(80);
+		log->type = CallocT<AILog::AILogType>(80);
+		log->count = 80;
+		log->pos = log->count;
+	}
+	LogData *log = (LogData *)AIObject::GetLogPointer();
+
+	/* Go to the next log-line */
+	log->pos = (log->pos + 1) % log->count;
+
+	/* Free last message, and write new message */
+	free(log->lines[log->pos]);
+	log->lines[log->pos] = strdup(message);
+	log->type[log->pos] = level;
+
+	/* Cut string after first \n */
+	char *p;
+	while ((p = strchr(log->lines[log->pos], '\n')) != NULL) {
+		*p = '\0';
+		break;
+	}
+
+	/* Also still print to debug window */
+	DEBUG(ai, level, "[%d] %s", (uint)_current_player, log->lines[log->pos]);
+}
+
+/* static */ void AILog::FreeLogPointer()
+{
+	LogData *log = (LogData *)AIObject::GetLogPointer();
+
+	for (int i = 0; i < log->count; i++) {
+		free(log->lines[i]);
+	}
+
+	free(log->lines);
+	free(log->type);
+	delete log;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ai/api/ai_log.hpp	Mon Mar 31 18:33:33 2008 +0000
@@ -0,0 +1,71 @@
+/* $Id$ */
+
+/** @file ai_log.hpp Everything to handle and issue log messages. */
+
+#ifndef AI_LOG_HPP
+#define AI_LOG_HPP
+
+#include "ai_object.hpp"
+
+/**
+ * Class that handles all log related functions.
+ */
+class AILog : public AIObject {
+	/* AIController needs access to Enum and Log, in order to keep the flow from
+	 *  OpenTTD core to NoAI API clear and simple. */
+	friend class AIController;
+
+public:
+	static const char *GetClassName() { return "AILog"; }
+
+	/**
+	 * Log levels; The value is also feed to DEBUG() lvl.
+	 *  This has no use for you, as AI writer.
+	 */
+	enum AILogType {
+		LOG_SQ_ERROR = 0, //!< Squirrel printed an error.
+		LOG_ERROR = 1,    //!< User printed an error.
+		LOG_SQ_INFO = 2,  //!< Squirrel printed some info.
+		LOG_WARNING = 3,  //!< User printed some warning.
+		LOG_INFO = 4,     //!< User printed some info.
+	};
+
+	struct LogData {
+		char **lines;
+		AILog::AILogType *type;
+		int count;
+		int pos;
+	};
+
+	/**
+	 * Print an Info message to the logs.
+	 * @param message The message to log.
+	 */
+	static void Info(const char *message);
+
+	/**
+	 * Print a Warning message to the logs.
+	 * @param message The message to log.
+	 */
+	static void Warning(const char *message);
+
+	/**
+	 * Print an Error message to the logs.
+	 * @param message The message to log.
+	 */
+	static void Error(const char *message);
+
+	/**
+	 * Free the log pointer.
+	 * @note DO NOT CALL YOURSELF; leave it to the internal AI programming.
+	 */
+	static void FreeLogPointer();
+
+private:
+	/**
+	 * Internal command to log the message in a common way.
+	 */
+	static void Log(AILog::AILogType level, const char *message);
+};
+
+#endif /* AI_LOG_HPP */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ai/api/ai_log.hpp.sq	Mon Mar 31 18:33:33 2008 +0000
@@ -0,0 +1,34 @@
+#include "ai_log.hpp"
+
+namespace SQConvert {
+	/* Allow enums to be used as Squirrel parameters */
+	template <> AILog::AILogType GetParam(ForceType<AILog::AILogType>, HSQUIRRELVM vm, int index) { SQInteger tmp; sq_getinteger(vm, index, &tmp); return (AILog::AILogType)tmp; }
+	template <> int Return<AILog::AILogType>(HSQUIRRELVM vm, AILog::AILogType res) { sq_pushinteger(vm, (int32)res); return 1; }
+
+	/* Allow AILog to be used as Squirrel parameter */
+	template <> AILog *GetParam(ForceType<AILog *>, HSQUIRRELVM vm, int index) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return  (AILog *)instance; }
+	template <> AILog &GetParam(ForceType<AILog &>, HSQUIRRELVM vm, int index) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AILog *)instance; }
+	template <> const AILog *GetParam(ForceType<const AILog *>, HSQUIRRELVM vm, int index) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return  (AILog *)instance; }
+	template <> const AILog &GetParam(ForceType<const AILog &>, HSQUIRRELVM vm, int index) { SQUserPointer instance; sq_getinstanceup(vm, index, &instance, 0); return *(AILog *)instance; }
+	template <> int Return<AILog *>(HSQUIRRELVM vm, AILog *res) { if (res == NULL) { sq_pushnull(vm); return 1; } res->AddRef(); Squirrel::CreateClassInstanceVM(vm, "AILog", res, NULL, DefSQDestructorCallback<AILog>); return 1; }
+}; // namespace SQConvert
+
+void SQAILog_Register(Squirrel *engine) {
+	DefSQClass <AILog> SQAILog("AILog");
+	SQAILog.PreRegister(engine);
+	SQAILog.AddConstructor<void (AILog::*)(), 1>(engine, "x");
+
+	SQAILog.DefSQConst(engine, AILog::LOG_SQ_ERROR, "LOG_SQ_ERROR");
+	SQAILog.DefSQConst(engine, AILog::LOG_ERROR,    "LOG_ERROR");
+	SQAILog.DefSQConst(engine, AILog::LOG_SQ_INFO,  "LOG_SQ_INFO");
+	SQAILog.DefSQConst(engine, AILog::LOG_WARNING,  "LOG_WARNING");
+	SQAILog.DefSQConst(engine, AILog::LOG_INFO,     "LOG_INFO");
+
+	SQAILog.DefSQStaticMethod(engine, &AILog::GetClassName,   "GetClassName",   1, "x");
+	SQAILog.DefSQStaticMethod(engine, &AILog::Info,           "Info",           2, "xs");
+	SQAILog.DefSQStaticMethod(engine, &AILog::Warning,        "Warning",        2, "xs");
+	SQAILog.DefSQStaticMethod(engine, &AILog::Error,          "Error",          2, "xs");
+	SQAILog.DefSQStaticMethod(engine, &AILog::FreeLogPointer, "FreeLogPointer", 1, "x");
+
+	SQAILog.PostRegister(engine);
+}
--- a/src/ai/api/ai_object.cpp	Mon Mar 31 17:20:44 2008 +0000
+++ b/src/ai/api/ai_object.cpp	Mon Mar 31 18:33:33 2008 +0000
@@ -3,6 +3,7 @@
 /** @file ai_object.cpp Implementation of AIObject. */
 
 #include "ai_object.hpp"
+#include "ai_log.hpp"
 #include "table/strings.h"
 #include "../../command_func.h"
 #include "../../network/network.h"
@@ -99,6 +100,11 @@
 	return AIObject::GetDoCommandStruct(_current_player)->event_data;
 }
 
+void *&AIObject::GetLogPointer()
+{
+	return AIObject::GetDoCommandStruct(_current_player)->log_data;
+}
+
 AIObject::AIDoCommandStruct *AIObject::GetDoCommandStruct(PlayerID player)
 {
 	/* Storage for data on per-AI level */
@@ -122,6 +128,8 @@
 	command_struct->costs = CommandCost();
 	if (command_struct->event_data != NULL) AIEventController::FreeEventPointer();
 	command_struct->event_data = NULL;
+	if (command_struct->log_data != NULL) AILog::FreeLogPointer();
+	command_struct->log_data = NULL;
 }
 
 bool AIObject::DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint procc, bool water_protection)
--- a/src/ai/api/ai_object.hpp	Mon Mar 31 17:20:44 2008 +0000
+++ b/src/ai/api/ai_object.hpp	Mon Mar 31 18:33:33 2008 +0000
@@ -45,6 +45,7 @@
 		VehicleID new_vehicle_id;
 		SignID new_sign_id;
 		void *event_data;
+		void *log_data;
 	};
 
 	/**
@@ -156,6 +157,12 @@
 	 * @note NEVER use this yourself in your AI!
 	 */
 	static void ResetInternalPlayerData();
+
+	/**
+	 * Get the pointer to store log message in.
+	 * @note NEVER use this yourself in your AI!
+	 */
+	static void *&GetLogPointer();
 };
 
 #endif /* AI_OBJECT_HPP */