changeset 18516:3b52341f7976 draft

(svn r23360) -Codechange: move AIInstance to ScriptInstance, making it reusable by other script API instances
author truebrain <truebrain@openttd.org>
date Tue, 29 Nov 2011 23:21:33 +0000
parents 9c33ff14d653
children 3dc74f255217
files bin/ai/regression/regression.txt projects/openttd_vs100.vcxproj projects/openttd_vs100.vcxproj.filters projects/openttd_vs80.vcproj projects/openttd_vs90.vcproj source.list src/ai/ai_instance.cpp src/ai/ai_instance.hpp src/ai/api/squirrel_export.sh src/script/api/script_bridge.cpp src/script/api/script_controller.hpp src/script/api/script_group.cpp src/script/api/script_object.cpp src/script/api/script_object.hpp src/script/api/script_order.cpp src/script/api/script_sign.cpp src/script/api/script_tunnel.cpp src/script/api/script_vehicle.cpp src/script/script_instance.cpp src/script/script_instance.hpp src/script/script_suspend.hpp src/script/squirrel.hpp
diffstat 22 files changed, 919 insertions(+), 812 deletions(-) [+]
line wrap: on
line diff
--- a/bin/ai/regression/regression.txt
+++ b/bin/ai/regression/regression.txt
@@ -8852,4 +8852,4 @@
   -1          >  2147483647:   false
   -2147483648 >  2147483647:   false
    13725      > -2147483648:   true
-ERROR: The AI died unexpectedly.
+ERROR: The script died unexpectedly.
--- a/projects/openttd_vs100.vcxproj
+++ b/projects/openttd_vs100.vcxproj
@@ -791,6 +791,8 @@
     <ClInclude Include="..\src\script\script_fatalerror.hpp" />
     <ClCompile Include="..\src\script\script_info.cpp" />
     <ClInclude Include="..\src\script\script_info.hpp" />
+    <ClCompile Include="..\src\script\script_instance.cpp" />
+    <ClInclude Include="..\src\script\script_instance.hpp" />
     <ClCompile Include="..\src\script\script_scanner.cpp" />
     <ClInclude Include="..\src\script\script_scanner.hpp" />
     <ClInclude Include="..\src\script\script_storage.hpp" />
--- a/projects/openttd_vs100.vcxproj.filters
+++ b/projects/openttd_vs100.vcxproj.filters
@@ -1596,6 +1596,12 @@
     <ClInclude Include="..\src\script\script_info.hpp">
       <Filter>Script</Filter>
     </ClInclude>
+    <ClCompile Include="..\src\script\script_instance.cpp">
+      <Filter>Script</Filter>
+    </ClCompile>
+    <ClInclude Include="..\src\script\script_instance.hpp">
+      <Filter>Script</Filter>
+    </ClInclude>
     <ClCompile Include="..\src\script\script_scanner.cpp">
       <Filter>Script</Filter>
     </ClCompile>
--- a/projects/openttd_vs80.vcproj
+++ b/projects/openttd_vs80.vcproj
@@ -2471,6 +2471,14 @@
 				>
 			</File>
 			<File
+				RelativePath=".\..\src\script\script_instance.cpp"
+				>
+			</File>
+			<File
+				RelativePath=".\..\src\script\script_instance.hpp"
+				>
+			</File>
+			<File
 				RelativePath=".\..\src\script\script_scanner.cpp"
 				>
 			</File>
--- a/projects/openttd_vs90.vcproj
+++ b/projects/openttd_vs90.vcproj
@@ -2468,6 +2468,14 @@
 				>
 			</File>
 			<File
+				RelativePath=".\..\src\script\script_instance.cpp"
+				>
+			</File>
+			<File
+				RelativePath=".\..\src\script\script_instance.hpp"
+				>
+			</File>
+			<File
 				RelativePath=".\..\src\script\script_scanner.cpp"
 				>
 			</File>
--- a/source.list
+++ b/source.list
@@ -556,6 +556,8 @@
 script/script_fatalerror.hpp
 script/script_info.cpp
 script/script_info.hpp
+script/script_instance.cpp
+script/script_instance.hpp
 script/script_scanner.cpp
 script/script_scanner.hpp
 script/script_storage.hpp
--- a/src/ai/ai_instance.cpp
+++ b/src/ai/ai_instance.cpp
@@ -80,105 +80,25 @@
 #include "../company_func.h"
 #include "../fileio_func.h"
 
-/** The maximum number of operations for saving or loading the data of an AI. */
-static const int MAX_SL_OPS          = 100000;
-/** The maximum number of operations for initial start of an AI. */
-static const int MAX_CONSTRUCTOR_OPS = 100000;
-
-ScriptStorage::~ScriptStorage()
-{
-	/* Free our pointers */
-	if (event_data != NULL) ScriptEventController::FreeEventPointer();
-	if (log_data != NULL) ScriptLog::FreeLogPointer();
-}
-
-/**
- * Callback called by squirrel when an AI uses "print" and for error messages.
- * @param error_msg Is this an error message?
- * @param message The actual message text.
- */
-static void PrintFunc(bool error_msg, const SQChar *message)
-{
-	/* Convert to OpenTTD internal capable string */
-	ScriptController::Print(error_msg, SQ2OTTD(message));
-}
-
 AIInstance::AIInstance() :
-	controller(NULL),
-	storage(NULL),
-	engine(NULL),
-	instance(NULL),
-	is_started(false),
-	is_dead(false),
-	is_save_data_on_stack(false),
-	suspend(0),
-	callback(NULL)
-{
-	this->storage = new ScriptStorage();
-	this->engine  = new Squirrel("AI");
-	this->engine->SetPrintFunction(&PrintFunc);
-}
+	ScriptInstance("AI")
+{}
 
 void AIInstance::Initialize(AIInfo *info)
 {
-	ScriptObject::ActiveInstance active(this);
-
-	this->controller = new ScriptController();
+	this->versionAPI = info->GetAPIVersion();
 
 	/* Register the AIController (including the "import" command) */
 	SQAIController_Register(this->engine);
 
-	/* Register the API functions and classes */
-	this->RegisterAPI();
-
-	if (!this->LoadCompatibilityScripts(info->GetAPIVersion())) {
-		this->Died();
-		return;
-	}
-
-	try {
-		ScriptObject::SetAllowDoCommand(false);
-		/* Load and execute the script for this AI */
-		const char *main_script = info->GetMainScript();
-		if (strcmp(main_script, "%_dummy") == 0) {
-			extern void AI_CreateAIDummy(HSQUIRRELVM vm);
-			AI_CreateAIDummy(this->engine->GetVM());
-		} else if (!this->engine->LoadScript(main_script) || this->engine->IsSuspended()) {
-			if (this->engine->IsSuspended()) ScriptLog::Error("This AI took too long to load script. AI is not started.");
-			this->Died();
-			return;
-		}
-
-		/* Create the main-class */
-		this->instance = MallocT<SQObject>(1);
-		if (!this->engine->CreateClassInstance(info->GetInstanceName(), this->controller, this->instance)) {
-			this->Died();
-			return;
-		}
-		ScriptObject::SetAllowDoCommand(true);
-	} catch (Script_FatalError e) {
-		this->is_dead = true;
-		this->engine->ThrowError(e.GetErrorMessage());
-		this->engine->ResumeError();
-		this->Died();
-	}
-}
-
-AIInstance::~AIInstance()
-{
-	ScriptObject::ActiveInstance active(this);
-
-	if (instance != NULL) this->engine->ReleaseObject(this->instance);
-	if (engine != NULL) delete this->engine;
-	delete this->storage;
-	delete this->controller;
-	free(this->instance);
+	ScriptInstance::Initialize(info->GetMainScript(), info->GetInstanceName());
 }
 
 void AIInstance::RegisterAPI()
 {
+	ScriptInstance::RegisterAPI();
+
 /* Register all classes */
-	squirrel_register_std(this->engine);
 	SQAIList_Register(this->engine);
 	SQAIAccounting_Register(this->engine);
 	SQAIAirport_Register(this->engine);
@@ -266,7 +186,7 @@
 	SQAIWaypointList_Register(this->engine);
 	SQAIWaypointList_Vehicle_Register(this->engine);
 
-	this->engine->SetGlobalPointer(this->engine);
+	if (!this->LoadCompatibilityScripts(this->versionAPI)) this->Died();
 }
 
 bool AIInstance::LoadCompatibilityScripts(const char *api_version)
@@ -291,21 +211,9 @@
 	return true;
 }
 
-void AIInstance::Continue()
-{
-	assert(this->suspend < 0);
-	this->suspend = -this->suspend - 1;
-}
-
 void AIInstance::Died()
 {
-	DEBUG(ai, 0, "The AI died unexpectedly.");
-	this->is_dead = true;
-
-	if (this->instance != NULL) this->engine->ReleaseObject(this->instance);
-	delete this->engine;
-	this->instance = NULL;
-	this->engine = NULL;
+	ScriptInstance::Died();
 
 	ShowAIDebugWindow(_current_company);
 
@@ -319,517 +227,3 @@
 		}
 	}
 }
-
-void AIInstance::GameLoop()
-{
-	ScriptObject::ActiveInstance active(this);
-
-	if (this->IsDead()) return;
-	if (this->engine->HasScriptCrashed()) {
-		/* The script crashed during saving, kill it here. */
-		this->Died();
-		return;
-	}
-	this->controller->ticks++;
-
-	if (this->suspend   < -1) this->suspend++; // Multiplayer suspend, increase up to -1.
-	if (this->suspend   < 0)  return;          // Multiplayer suspend, wait for Continue().
-	if (--this->suspend > 0)  return;          // Singleplayer suspend, decrease to 0.
-
-	/* If there is a callback to call, call that first */
-	if (this->callback != NULL) {
-		if (this->is_save_data_on_stack) {
-			sq_poptop(this->engine->GetVM());
-			this->is_save_data_on_stack = false;
-		}
-		try {
-			this->callback(this);
-		} catch (Script_Suspend e) {
-			this->suspend  = e.GetSuspendTime();
-			this->callback = e.GetSuspendCallback();
-
-			return;
-		}
-	}
-
-	this->suspend  = 0;
-	this->callback = NULL;
-
-	if (!this->is_started) {
-		try {
-			ScriptObject::SetAllowDoCommand(false);
-			/* Run the constructor if it exists. Don't allow any DoCommands in it. */
-			if (this->engine->MethodExists(*this->instance, "constructor")) {
-				if (!this->engine->CallMethod(*this->instance, "constructor", MAX_CONSTRUCTOR_OPS) || this->engine->IsSuspended()) {
-					if (this->engine->IsSuspended()) ScriptLog::Error("This AI took too long to initialize. AI is not started.");
-					this->Died();
-					return;
-				}
-			}
-			if (!this->CallLoad() || this->engine->IsSuspended()) {
-				if (this->engine->IsSuspended()) ScriptLog::Error("This AI took too long in the Load function. AI is not started.");
-				this->Died();
-				return;
-			}
-			ScriptObject::SetAllowDoCommand(true);
-			/* Start the AI by calling Start() */
-			if (!this->engine->CallMethod(*this->instance, "Start",  _settings_game.ai.ai_max_opcode_till_suspend) || !this->engine->IsSuspended()) this->Died();
-		} catch (Script_Suspend e) {
-			this->suspend  = e.GetSuspendTime();
-			this->callback = e.GetSuspendCallback();
-		} catch (Script_FatalError e) {
-			this->is_dead = true;
-			this->engine->ThrowError(e.GetErrorMessage());
-			this->engine->ResumeError();
-			this->Died();
-		}
-
-		this->is_started = true;
-		return;
-	}
-	if (this->is_save_data_on_stack) {
-		sq_poptop(this->engine->GetVM());
-		this->is_save_data_on_stack = false;
-	}
-
-	/* Continue the VM */
-	try {
-		if (!this->engine->Resume(_settings_game.ai.ai_max_opcode_till_suspend)) this->Died();
-	} catch (Script_Suspend e) {
-		this->suspend  = e.GetSuspendTime();
-		this->callback = e.GetSuspendCallback();
-	} catch (Script_FatalError e) {
-		this->is_dead = true;
-		this->engine->ThrowError(e.GetErrorMessage());
-		this->engine->ResumeError();
-		this->Died();
-	}
-}
-
-void AIInstance::CollectGarbage() const
-{
-	if (this->is_started && !this->IsDead()) this->engine->CollectGarbage();
-}
-
-/* static */ void AIInstance::DoCommandReturn(AIInstance *instance)
-{
-	instance->engine->InsertResult(ScriptObject::GetLastCommandRes());
-}
-
-/* static */ void AIInstance::DoCommandReturnVehicleID(AIInstance *instance)
-{
-	instance->engine->InsertResult(ScriptObject::GetNewVehicleID());
-}
-
-/* static */ void AIInstance::DoCommandReturnSignID(AIInstance *instance)
-{
-	instance->engine->InsertResult(ScriptObject::GetNewSignID());
-}
-
-/* static */ void AIInstance::DoCommandReturnGroupID(AIInstance *instance)
-{
-	instance->engine->InsertResult(ScriptObject::GetNewGroupID());
-}
-
-ScriptStorage *AIInstance::GetStorage()
-{
-	return this->storage;
-}
-
-void *AIInstance::GetLogPointer()
-{
-	ScriptObject::ActiveInstance active(this);
-
-	return ScriptObject::GetLogPointer();
-}
-
-/*
- * All data is stored in the following format:
- * First 1 byte indicating if there is a data blob at all.
- * 1 byte indicating the type of data.
- * The data itself, this differs per type:
- *  - integer: a binary representation of the integer (int32).
- *  - string:  First one byte with the string length, then a 0-terminated char
- *             array. The string can't be longer than 255 bytes (including
- *             terminating '\0').
- *  - array:   All data-elements of the array are saved recursive in this
- *             format, and ended with an element of the type
- *             SQSL_ARRAY_TABLE_END.
- *  - table:   All key/value pairs are saved in this format (first key 1, then
- *             value 1, then key 2, etc.). All keys and values can have an
- *             arbitrary type (as long as it is supported by the save function
- *             of course). The table is ended with an element of the type
- *             SQSL_ARRAY_TABLE_END.
- *  - bool:    A single byte with value 1 representing true and 0 false.
- *  - null:    No data.
- */
-
-/** The type of the data that follows in the savegame. */
-enum SQSaveLoadType {
-	SQSL_INT             = 0x00, ///< The following data is an integer.
-	SQSL_STRING          = 0x01, ///< The following data is an string.
-	SQSL_ARRAY           = 0x02, ///< The following data is an array.
-	SQSL_TABLE           = 0x03, ///< The following data is an table.
-	SQSL_BOOL            = 0x04, ///< The following data is a boolean.
-	SQSL_NULL            = 0x05, ///< A null variable.
-	SQSL_ARRAY_TABLE_END = 0xFF, ///< Marks the end of an array or table, no data follows.
-};
-
-static byte _ai_sl_byte; ///< Used as source/target by the AI saveload code to store/load a single byte.
-
-/** SaveLoad array that saves/loads exactly one byte. */
-static const SaveLoad _ai_byte[] = {
-	SLEG_VAR(_ai_sl_byte, SLE_UINT8),
-	SLE_END()
-};
-
-static const uint AISAVE_MAX_DEPTH = 25; ///< The maximum recursive depth for items stored in the savegame.
-
-/* static */ bool AIInstance::SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test)
-{
-	if (max_depth == 0) {
-		ScriptLog::Error("Savedata can only be nested to 25 deep. No data saved.");
-		return false;
-	}
-
-	switch (sq_gettype(vm, index)) {
-		case OT_INTEGER: {
-			if (!test) {
-				_ai_sl_byte = SQSL_INT;
-				SlObject(NULL, _ai_byte);
-			}
-			SQInteger res;
-			sq_getinteger(vm, index, &res);
-			if (!test) {
-				int value = (int)res;
-				SlArray(&value, 1, SLE_INT32);
-			}
-			return true;
-		}
-
-		case OT_STRING: {
-			if (!test) {
-				_ai_sl_byte = SQSL_STRING;
-				SlObject(NULL, _ai_byte);
-			}
-			const SQChar *res;
-			sq_getstring(vm, index, &res);
-			/* @bug if a string longer than 512 characters is given to SQ2OTTD, the
-			 *  internal buffer overflows. */
-			const char *buf = SQ2OTTD(res);
-			size_t len = strlen(buf) + 1;
-			if (len >= 255) {
-				ScriptLog::Error("Maximum string length is 254 chars. No data saved.");
-				return false;
-			}
-			if (!test) {
-				_ai_sl_byte = (byte)len;
-				SlObject(NULL, _ai_byte);
-				SlArray(const_cast<char *>(buf), len, SLE_CHAR);
-			}
-			return true;
-		}
-
-		case OT_ARRAY: {
-			if (!test) {
-				_ai_sl_byte = SQSL_ARRAY;
-				SlObject(NULL, _ai_byte);
-			}
-			sq_pushnull(vm);
-			while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
-				/* Store the value */
-				bool res = SaveObject(vm, -1, max_depth - 1, test);
-				sq_pop(vm, 2);
-				if (!res) {
-					sq_pop(vm, 1);
-					return false;
-				}
-			}
-			sq_pop(vm, 1);
-			if (!test) {
-				_ai_sl_byte = SQSL_ARRAY_TABLE_END;
-				SlObject(NULL, _ai_byte);
-			}
-			return true;
-		}
-
-		case OT_TABLE: {
-			if (!test) {
-				_ai_sl_byte = SQSL_TABLE;
-				SlObject(NULL, _ai_byte);
-			}
-			sq_pushnull(vm);
-			while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
-				/* Store the key + value */
-				bool res = SaveObject(vm, -2, max_depth - 1, test) && SaveObject(vm, -1, max_depth - 1, test);
-				sq_pop(vm, 2);
-				if (!res) {
-					sq_pop(vm, 1);
-					return false;
-				}
-			}
-			sq_pop(vm, 1);
-			if (!test) {
-				_ai_sl_byte = SQSL_ARRAY_TABLE_END;
-				SlObject(NULL, _ai_byte);
-			}
-			return true;
-		}
-
-		case OT_BOOL: {
-			if (!test) {
-				_ai_sl_byte = SQSL_BOOL;
-				SlObject(NULL, _ai_byte);
-			}
-			SQBool res;
-			sq_getbool(vm, index, &res);
-			if (!test) {
-				_ai_sl_byte = res ? 1 : 0;
-				SlObject(NULL, _ai_byte);
-			}
-			return true;
-		}
-
-		case OT_NULL: {
-			if (!test) {
-				_ai_sl_byte = SQSL_NULL;
-				SlObject(NULL, _ai_byte);
-			}
-			return true;
-		}
-
-		default:
-			ScriptLog::Error("You tried to save an unsupported type. No data saved.");
-			return false;
-	}
-}
-
-/* static */ void AIInstance::SaveEmpty()
-{
-	_ai_sl_byte = 0;
-	SlObject(NULL, _ai_byte);
-}
-
-void AIInstance::Save()
-{
-	ScriptObject::ActiveInstance active(this);
-
-	/* Don't save data if the AI didn't start yet or if it crashed. */
-	if (this->engine == NULL || this->engine->HasScriptCrashed()) {
-		SaveEmpty();
-		return;
-	}
-
-	HSQUIRRELVM vm = this->engine->GetVM();
-	if (this->is_save_data_on_stack) {
-		_ai_sl_byte = 1;
-		SlObject(NULL, _ai_byte);
-		/* Save the data that was just loaded. */
-		SaveObject(vm, -1, AISAVE_MAX_DEPTH, false);
-	} else if (!this->is_started) {
-		SaveEmpty();
-		return;
-	} else if (this->engine->MethodExists(*this->instance, "Save")) {
-		HSQOBJECT savedata;
-		/* We don't want to be interrupted during the save function. */
-		bool backup_allow = ScriptObject::GetAllowDoCommand();
-		ScriptObject::SetAllowDoCommand(false);
-		try {
-			if (!this->engine->CallMethod(*this->instance, "Save", &savedata, MAX_SL_OPS)) {
-				/* The script crashed in the Save function. We can't kill
-				 * it here, but do so in the next AI tick. */
-				SaveEmpty();
-				this->engine->CrashOccurred();
-				return;
-			}
-		} catch (Script_FatalError e) {
-			/* If we don't mark the AI as dead here cleaning up the squirrel
-			 * stack could throw Script_FatalError again. */
-			this->is_dead = true;
-			this->engine->ThrowError(e.GetErrorMessage());
-			this->engine->ResumeError();
-			SaveEmpty();
-			/* We can't kill the AI here, so mark it as crashed (not dead) and
-			 * kill it in the next AI tick. */
-			this->is_dead = false;
-			this->engine->CrashOccurred();
-			return;
-		}
-		ScriptObject::SetAllowDoCommand(backup_allow);
-
-		if (!sq_istable(savedata)) {
-			ScriptLog::Error(this->engine->IsSuspended() ? "This AI took too long to Save." : "Save function should return a table.");
-			SaveEmpty();
-			this->engine->CrashOccurred();
-			return;
-		}
-		sq_pushobject(vm, savedata);
-		if (SaveObject(vm, -1, AISAVE_MAX_DEPTH, true)) {
-			_ai_sl_byte = 1;
-			SlObject(NULL, _ai_byte);
-			SaveObject(vm, -1, AISAVE_MAX_DEPTH, false);
-			this->is_save_data_on_stack = true;
-		} else {
-			SaveEmpty();
-			this->engine->CrashOccurred();
-		}
-	} else {
-		ScriptLog::Warning("Save function is not implemented");
-		_ai_sl_byte = 0;
-		SlObject(NULL, _ai_byte);
-	}
-}
-
-void AIInstance::Suspend()
-{
-	HSQUIRRELVM vm = this->engine->GetVM();
-	Squirrel::DecreaseOps(vm, _settings_game.ai.ai_max_opcode_till_suspend);
-}
-
-/* static */ bool AIInstance::LoadObjects(HSQUIRRELVM vm)
-{
-	SlObject(NULL, _ai_byte);
-	switch (_ai_sl_byte) {
-		case SQSL_INT: {
-			int value;
-			SlArray(&value, 1, SLE_INT32);
-			if (vm != NULL) sq_pushinteger(vm, (SQInteger)value);
-			return true;
-		}
-
-		case SQSL_STRING: {
-			SlObject(NULL, _ai_byte);
-			static char buf[256];
-			SlArray(buf, _ai_sl_byte, SLE_CHAR);
-			if (vm != NULL) sq_pushstring(vm, OTTD2SQ(buf), -1);
-			return true;
-		}
-
-		case SQSL_ARRAY: {
-			if (vm != NULL) sq_newarray(vm, 0);
-			while (LoadObjects(vm)) {
-				if (vm != NULL) sq_arrayappend(vm, -2);
-				/* The value is popped from the stack by squirrel. */
-			}
-			return true;
-		}
-
-		case SQSL_TABLE: {
-			if (vm != NULL) sq_newtable(vm);
-			while (LoadObjects(vm)) {
-				LoadObjects(vm);
-				if (vm != NULL) sq_rawset(vm, -3);
-				/* The key (-2) and value (-1) are popped from the stack by squirrel. */
-			}
-			return true;
-		}
-
-		case SQSL_BOOL: {
-			SlObject(NULL, _ai_byte);
-			if (vm != NULL) sq_pushinteger(vm, (SQBool)(_ai_sl_byte != 0));
-			return true;
-		}
-
-		case SQSL_NULL: {
-			if (vm != NULL) sq_pushnull(vm);
-			return true;
-		}
-
-		case SQSL_ARRAY_TABLE_END: {
-			return false;
-		}
-
-		default: NOT_REACHED();
-	}
-}
-
-/* static */ void AIInstance::LoadEmpty()
-{
-	SlObject(NULL, _ai_byte);
-	/* Check if there was anything saved at all. */
-	if (_ai_sl_byte == 0) return;
-
-	LoadObjects(NULL);
-}
-
-void AIInstance::Load(int version)
-{
-	ScriptObject::ActiveInstance active(this);
-
-	if (this->engine == NULL || version == -1) {
-		LoadEmpty();
-		return;
-	}
-	HSQUIRRELVM vm = this->engine->GetVM();
-
-	SlObject(NULL, _ai_byte);
-	/* Check if there was anything saved at all. */
-	if (_ai_sl_byte == 0) return;
-
-	sq_pushinteger(vm, version);
-	LoadObjects(vm);
-	this->is_save_data_on_stack = true;
-}
-
-bool AIInstance::CallLoad()
-{
-	HSQUIRRELVM vm = this->engine->GetVM();
-	/* Is there save data that we should load? */
-	if (!this->is_save_data_on_stack) return true;
-	/* Whatever happens, after CallLoad the savegame data is removed from the stack. */
-	this->is_save_data_on_stack = false;
-
-	if (!this->engine->MethodExists(*this->instance, "Load")) {
-		ScriptLog::Warning("Loading failed: there was data for the AI to load, but the AI does not have a Load() function.");
-
-		/* Pop the savegame data and version. */
-		sq_pop(vm, 2);
-		return true;
-	}
-
-	/* Go to the instance-root */
-	sq_pushobject(vm, *this->instance);
-	/* Find the function-name inside the script */
-	sq_pushstring(vm, OTTD2SQ("Load"), -1);
-	/* Change the "Load" string in a function pointer */
-	sq_get(vm, -2);
-	/* Push the main instance as "this" object */
-	sq_pushobject(vm, *this->instance);
-	/* Push the version data and savegame data as arguments */
-	sq_push(vm, -5);
-	sq_push(vm, -5);
-
-	/* Call the AI load function. sq_call removes the arguments (but not the
-	 * function pointer) from the stack. */
-	if (SQ_FAILED(sq_call(vm, 3, SQFalse, SQFalse, MAX_SL_OPS))) return false;
-
-	/* Pop 1) The version, 2) the savegame data, 3) the object instance, 4) the function pointer. */
-	sq_pop(vm, 4);
-	return true;
-}
-
-SQInteger AIInstance::GetOpsTillSuspend()
-{
-	return this->engine->GetOpsTillSuspend();
-}
-
-void AIInstance::DoCommandCallback(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
-{
-	ScriptObject::ActiveInstance active(this);
-
-	ScriptObject::SetLastCommandRes(result.Succeeded());
-
-	if (result.Failed()) {
-		ScriptObject::SetLastError(ScriptError::StringToError(result.GetErrorMessage()));
-	} else {
-		ScriptObject::IncreaseDoCommandCosts(result.GetCost());
-		ScriptObject::SetLastCost(result.GetCost());
-	}
-}
-
-void AIInstance::InsertEvent(class ScriptEvent *event)
-{
-	ScriptObject::ActiveInstance active(this);
-
-	ScriptEventController::InsertEvent(event);
-}
--- a/src/ai/ai_instance.hpp
+++ b/src/ai/ai_instance.hpp
@@ -13,19 +13,12 @@
 #define AI_INSTANCE_HPP
 
 #include <squirrel.h>
-#include "../script/script_suspend.hpp"
+#include "../script/script_instance.hpp"
 
 /** Runtime information about an AI like a pointer to the squirrel vm and the current state. */
-class AIInstance {
+class AIInstance : public ScriptInstance {
 public:
-	friend class ScriptObject;
-	friend class ScriptController;
-
-	/**
-	 * Create a new AI.
-	 */
 	AIInstance();
-	~AIInstance();
 
 	/**
 	 * Initialize the AI and prepare it for its first run.
@@ -33,164 +26,16 @@
 	 */
 	void Initialize(class AIInfo *info);
 
-	/**
-	 * An AI in multiplayer waits for the server to handle his DoCommand.
-	 *  It keeps waiting for this until this function is called.
-	 */
-	void Continue();
-
-	/**
-	 * Run the GameLoop of an AI.
-	 */
-	void GameLoop();
-
-	/**
-	 * Let the VM collect any garbage.
-	 */
-	void CollectGarbage() const;
-
-	/**
-	 * Get the storage of this AI.
-	 */
-	class ScriptStorage *GetStorage();
-
-	/**
-	 * Get the log pointer of this AI.
-	 */
-	void *GetLogPointer();
-
-	/**
-	 * Return a true/false reply for a DoCommand.
-	 */
-	static void DoCommandReturn(AIInstance *instance);
-
-	/**
-	 * Return a VehicleID reply for a DoCommand.
-	 */
-	static void DoCommandReturnVehicleID(AIInstance *instance);
-
-	/**
-	 * Return a SignID reply for a DoCommand.
-	 */
-	static void DoCommandReturnSignID(AIInstance *instance);
-
-	/**
-	 * Return a GroupID reply for a DoCommand.
-	 */
-	static void DoCommandReturnGroupID(AIInstance *instance);
-
-	/**
-	 * Get the controller attached to the instance.
-	 */
-	class ScriptController *GetController() { return controller; }
-
-	/**
-	 * Return the "this AI died" value
-	 */
-	inline bool IsDead() const { return this->is_dead; }
-
-	/**
-	 * Call the AI Save function and save all data in the savegame.
-	 */
-	void Save();
+private:
+	const char *versionAPI; ///< Current API used by this script.
 
-	/**
-	 * Don't save any data in the savegame.
-	 */
-	static void SaveEmpty();
-
-	/**
-	 * Load data from a savegame and store it on the stack.
-	 * @param version The version of the AI when saving, or -1 if this was
-	 *  not the original AI saving the game.
-	 */
-	void Load(int version);
-
-	/**
-	 * Load and discard data from a savegame.
-	 */
-	static void LoadEmpty();
-
-	/**
-	 * Reduces the number of opcodes the AI have left to zero. Unless
-	 * the AI is in a state where it cannot suspend it will be suspended
-	 * for the reminder of the current tick. This function is safe to
-	 * call from within a function called by the AI.
-	 */
-	void Suspend();
-
-	/**
-	 * Get the number of operations the AI can execute before being suspended.
-	 * This function is safe to call from within a function called by the AI.
-	 * @return The number of operations to execute.
-	 */
-	SQInteger GetOpsTillSuspend();
-
-	/**
-	 * DoCommand callback function for all commands executed by AIs.
-	 * @param result The result of the command.
-	 * @param tile The tile on which the command was executed.
-	 * @param p1 p1 as given to DoCommandPInternal.
-	 * @param p2 p2 as given to DoCommandPInternal.
-	 */
-	void DoCommandCallback(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2);
-
-	/**
-	 * Insert an event for this AI.
-	 * @param event The event to insert.
-	 */
-	void InsertEvent(class ScriptEvent *event);
-
-private:
-	class ScriptController *controller;   ///< The AI main class.
-	class ScriptStorage *storage;         ///< Some global information for each running AI.
-	class Squirrel *engine;               ///< A wrapper around the squirrel vm.
-	SQObject *instance;                   ///< Squirrel-pointer to the AI main class.
-
-	bool is_started;                      ///< Is the AIs constructor executed?
-	bool is_dead;                         ///< True if the AI has been stopped.
-	bool is_save_data_on_stack;           ///< Is the save data still on the squirrel stack?
-	int suspend;                          ///< The amount of ticks to suspend this AI before it's allowed to continue.
-	Script_SuspendCallbackProc *callback; ///< Callback that should be called in the next tick the AI runs.
-
-	/**
-	 * Register all API functions to the VM.
-	 */
-	void RegisterAPI();
+	/* virtual */ void RegisterAPI();
+	/* virtual */ void Died();
 
 	/**
 	 * Load squirrel scripts to emulate an older API.
 	 */
 	bool LoadCompatibilityScripts(const char *api_version);
-
-	/**
-	 * Tell the AI it died.
-	 */
-	void Died();
-
-	/**
-	 * Call the AI Load function if it exists and data was loaded
-	 *  from a savegame.
-	 */
-	bool CallLoad();
-
-	/**
-	 * Save one object (int / string / array / table) to the savegame.
-	 * @param vm The virtual machine to get all the data from.
-	 * @param index The index on the squirrel stack of the element to save.
-	 * @param max_depth The maximum depth recursive arrays / tables will be stored
-	 *   with before an error is returned.
-	 * @param test If true, don't really store the data but only check if it is
-	 *   valid.
-	 * @return True if the saving was successful.
-	 */
-	static bool SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test);
-
-	/**
-	 * Load all objects from a savegame.
-	 * @return True if the loading was successful.
-	 */
-	static bool LoadObjects(HSQUIRRELVM vm);
 };
 
 #endif /* AI_INSTANCE_HPP */
--- a/src/ai/api/squirrel_export.sh
+++ b/src/ai/api/squirrel_export.sh
@@ -91,7 +91,6 @@
 /\/\* Register all classes \*\// {
 	print \$0
 	gsub(\"^.*/\", \"\")
-	print \"	squirrel_register_std(this->engine);\" \$0
 	# List needs to be registered with squirrel before all List subclasses.
 	print \"	SQ${apiuc}List_Register(this->engine);\" \$0
 	split(\"`grep '^void SQ'${apiuc}'.*_Register(Squirrel \*engine)$' *.hpp.sq | grep -v 'SQ'${apiuc}'List_Register' | sed 's/^.*void //;s/Squirrel \*/this->/;s/$/;/;s/_Register/0000Register/g;' | sort | sed 's/0000Register/_Register/g' | tr -d '\r' | tr '\n' ' '`\", regs, \" \")
--- a/src/script/api/script_bridge.cpp
+++ b/src/script/api/script_bridge.cpp
@@ -39,10 +39,10 @@
  * Helper function to connect a just built bridge to nearby roads.
  * @param instance The AI we have to built the road for.
  */
-static void _DoCommandReturnBuildBridge2(class AIInstance *instance)
+static void _DoCommandReturnBuildBridge2(class ScriptInstance *instance)
 {
 	if (!ScriptBridge::_BuildBridgeRoad2()) {
-		AIInstance::DoCommandReturn(instance);
+		ScriptInstance::DoCommandReturn(instance);
 		return;
 	}
 
@@ -55,10 +55,10 @@
  * Helper function to connect a just built bridge to nearby roads.
  * @param instance The AI we have to built the road for.
  */
-static void _DoCommandReturnBuildBridge1(class AIInstance *instance)
+static void _DoCommandReturnBuildBridge1(class ScriptInstance *instance)
 {
 	if (!ScriptBridge::_BuildBridgeRoad1()) {
-		AIInstance::DoCommandReturn(instance);
+		ScriptInstance::DoCommandReturn(instance);
 		return;
 	}
 
--- a/src/script/api/script_controller.hpp
+++ b/src/script/api/script_controller.hpp
@@ -22,7 +22,7 @@
  */
 class ScriptController {
 	friend class AIScanner;
-	friend class AIInstance;
+	friend class ScriptInstance;
 
 public:
 	/**
--- a/src/script/api/script_group.cpp
+++ b/src/script/api/script_group.cpp
@@ -29,7 +29,7 @@
 
 /* static */ ScriptGroup::GroupID ScriptGroup::CreateGroup(ScriptVehicle::VehicleType vehicle_type)
 {
-	if (!ScriptObject::DoCommand(0, (::VehicleType)vehicle_type, 0, CMD_CREATE_GROUP, NULL, &AIInstance::DoCommandReturnGroupID)) return GROUP_INVALID;
+	if (!ScriptObject::DoCommand(0, (::VehicleType)vehicle_type, 0, CMD_CREATE_GROUP, NULL, &ScriptInstance::DoCommandReturnGroupID)) return GROUP_INVALID;
 
 	/* In case of test-mode, we return GroupID 0 */
 	return (ScriptGroup::GroupID)0;
--- a/src/script/api/script_object.cpp
+++ b/src/script/api/script_object.cpp
@@ -22,7 +22,7 @@
 #include "script_error.hpp"
 
 /**
- * Get the storage associated with the current AIInstance.
+ * Get the storage associated with the current ScriptInstance.
  * @return The storage.
  */
 static ScriptStorage *GetStorage()
@@ -31,9 +31,9 @@
 }
 
 
-/* static */ AIInstance *ScriptObject::ActiveInstance::active = NULL;
+/* static */ ScriptInstance *ScriptObject::ActiveInstance::active = NULL;
 
-ScriptObject::ActiveInstance::ActiveInstance(AIInstance *instance)
+ScriptObject::ActiveInstance::ActiveInstance(ScriptInstance *instance)
 {
 	this->last_active = ScriptObject::ActiveInstance::active;
 	ScriptObject::ActiveInstance::active = instance;
@@ -44,7 +44,7 @@
 	ScriptObject::ActiveInstance::active = this->last_active;
 }
 
-/* static */ AIInstance *ScriptObject::GetActiveInstance()
+/* static */ ScriptInstance *ScriptObject::GetActiveInstance()
 {
 	assert(ScriptObject::ActiveInstance::active != NULL);
 	return ScriptObject::ActiveInstance::active;
@@ -225,14 +225,14 @@
 	return GetStorage()->callback_value[index];
 }
 
-/* static */ bool ScriptObject::DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint cmd, const char *text, AISuspendCallbackProc *callback)
+/* static */ bool ScriptObject::DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint cmd, const char *text, Script_SuspendCallbackProc *callback)
 {
 	if (!ScriptObject::CanSuspend()) {
 		throw Script_FatalError("You are not allowed to execute any DoCommand (even indirect) in your constructor, Save(), Load(), and any valuator.");
 	}
 
 	/* Set the default callback to return a true/false result of the DoCommand */
-	if (callback == NULL) callback = &AIInstance::DoCommandReturn;
+	if (callback == NULL) callback = &ScriptInstance::DoCommandReturn;
 
 	/* Are we only interested in the estimate costs? */
 	bool estimate_only = GetDoCommandMode() != NULL && !GetDoCommandMode()();
--- a/src/script/api/script_object.hpp
+++ b/src/script/api/script_object.hpp
@@ -17,11 +17,7 @@
 #include "../../rail_type.h"
 
 #include "script_types.hpp"
-
-/**
- * The callback function when an AI suspends.
- */
-typedef void (AISuspendCallbackProc)(class AIInstance *instance);
+#include "../script_suspend.hpp"
 
 /**
  * The callback function for Mode-classes.
@@ -30,12 +26,12 @@
 
 /**
  * Uper-parent object of all API classes. You should never use this class in
- *   your AI, as it doesn't publish any public functions. It is used
+ *   your script, as it doesn't publish any public functions. It is used
  *   internally to have a common place to handle general things, like internal
  *   command processing, and command-validation checks.
  */
 class ScriptObject : public SimpleCountedObject {
-friend class AIInstance;
+friend class ScriptInstance;
 #ifndef DOXYGEN_AI_DOCS
 protected:
 	/**
@@ -47,12 +43,12 @@
 	class ActiveInstance {
 	friend class ScriptObject;
 	public:
-		ActiveInstance(AIInstance *instance);
+		ActiveInstance(ScriptInstance *instance);
 		~ActiveInstance();
 	private:
-		AIInstance *last_active;    ///< The active instance before we go instantiated.
+		ScriptInstance *last_active;    ///< The active instance before we go instantiated.
 
-		static AIInstance *active;  ///< The global current active instance.
+		static ScriptInstance *active;  ///< The global current active instance.
 	};
 
 public:
@@ -66,13 +62,13 @@
 	 * Get the currently active instance.
 	 * @return The instance.
 	 */
-	static class AIInstance *GetActiveInstance();
+	static class ScriptInstance *GetActiveInstance();
 
 protected:
 	/**
 	 * Executes a raw DoCommand for the AI.
 	 */
-	static bool DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint cmd, const char *text = NULL, AISuspendCallbackProc *callback = NULL);
+	static bool DoCommand(TileIndex tile, uint32 p1, uint32 p2, uint cmd, const char *text = NULL, Script_SuspendCallbackProc *callback = NULL);
 
 	/**
 	 * Sets the DoCommand costs counter to a value.
--- a/src/script/api/script_order.cpp
+++ b/src/script/api/script_order.cpp
@@ -546,10 +546,10 @@
  * between the wanted and the current order.
  * @param instance The AI we are doing the callback for.
  */
-static void _DoCommandReturnSetOrderFlags(class AIInstance *instance)
+static void _DoCommandReturnSetOrderFlags(class ScriptInstance *instance)
 {
 	ScriptObject::SetLastCommandRes(ScriptOrder::_SetOrderFlags());
-	AIInstance::DoCommandReturn(instance);
+	ScriptInstance::DoCommandReturn(instance);
 }
 
 /* static */ bool ScriptOrder::_SetOrderFlags()
--- a/src/script/api/script_sign.cpp
+++ b/src/script/api/script_sign.cpp
@@ -69,7 +69,7 @@
 	EnforcePrecondition(INVALID_SIGN, !::StrEmpty(text));
 	EnforcePreconditionCustomError(false, ::Utf8StringLength(text) < MAX_LENGTH_SIGN_NAME_CHARS, ScriptError::ERR_PRECONDITION_STRING_TOO_LONG);
 
-	if (!ScriptObject::DoCommand(location, 0, 0, CMD_PLACE_SIGN, text, &AIInstance::DoCommandReturnSignID)) return INVALID_SIGN;
+	if (!ScriptObject::DoCommand(location, 0, 0, CMD_PLACE_SIGN, text, &ScriptInstance::DoCommandReturnSignID)) return INVALID_SIGN;
 
 	/* In case of test-mode, we return SignID 0 */
 	return 0;
--- a/src/script/api/script_tunnel.cpp
+++ b/src/script/api/script_tunnel.cpp
@@ -50,10 +50,10 @@
  * Helper function to connect a just built tunnel to nearby roads.
  * @param instance The AI we have to built the road for.
  */
-static void _DoCommandReturnBuildTunnel2(class AIInstance *instance)
+static void _DoCommandReturnBuildTunnel2(class ScriptInstance *instance)
 {
 	if (!ScriptTunnel::_BuildTunnelRoad2()) {
-		AIInstance::DoCommandReturn(instance);
+		ScriptInstance::DoCommandReturn(instance);
 		return;
 	}
 
@@ -66,10 +66,10 @@
  * Helper function to connect a just built tunnel to nearby roads.
  * @param instance The AI we have to built the road for.
  */
-static void _DoCommandReturnBuildTunnel1(class AIInstance *instance)
+static void _DoCommandReturnBuildTunnel1(class ScriptInstance *instance)
 {
 	if (!ScriptTunnel::_BuildTunnelRoad1()) {
-		AIInstance::DoCommandReturn(instance);
+		ScriptInstance::DoCommandReturn(instance);
 		return;
 	}
 
--- a/src/script/api/script_vehicle.cpp
+++ b/src/script/api/script_vehicle.cpp
@@ -60,7 +60,7 @@
 
 	EnforcePreconditionCustomError(VEHICLE_INVALID, !ScriptGameSettings::IsDisabledVehicleType((ScriptVehicle::VehicleType)type), ScriptVehicle::ERR_VEHICLE_BUILD_DISABLED);
 
-	if (!ScriptObject::DoCommand(depot, engine_id, 0, ::GetCmdBuildVeh(type), NULL, &AIInstance::DoCommandReturnVehicleID)) return VEHICLE_INVALID;
+	if (!ScriptObject::DoCommand(depot, engine_id, 0, ::GetCmdBuildVeh(type), NULL, &ScriptInstance::DoCommandReturnVehicleID)) return VEHICLE_INVALID;
 
 	/* In case of test-mode, we return VehicleID 0 */
 	return 0;
@@ -70,7 +70,7 @@
 {
 	EnforcePrecondition(false, IsValidVehicle(vehicle_id));
 
-	if (!ScriptObject::DoCommand(depot, vehicle_id, share_orders, CMD_CLONE_VEHICLE, NULL, &AIInstance::DoCommandReturnVehicleID)) return VEHICLE_INVALID;
+	if (!ScriptObject::DoCommand(depot, vehicle_id, share_orders, CMD_CLONE_VEHICLE, NULL, &ScriptInstance::DoCommandReturnVehicleID)) return VEHICLE_INVALID;
 
 	/* In case of test-mode, we return VehicleID 0 */
 	return 0;
new file mode 100644
--- /dev/null
+++ b/src/script/script_instance.cpp
@@ -0,0 +1,653 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** @file script_instance.cpp Implementation of ScriptInstance. */
+
+#include "../stdafx.h"
+#include "../debug.h"
+#include "../saveload/saveload.h"
+#include "../gui.h"
+
+#include "../script/squirrel_class.hpp"
+
+#include "script_fatalerror.hpp"
+#include "script_storage.hpp"
+#include "script_instance.hpp"
+
+#include "api/script_controller.hpp"
+#include "api/script_error.hpp"
+#include "api/script_event.hpp"
+#include "api/script_log.hpp"
+
+#include "../company_base.h"
+#include "../company_func.h"
+#include "../fileio_func.h"
+
+/** The maximum number of operations for saving or loading the data of a script. */
+static const int MAX_SL_OPS          = 100000;
+/** The maximum number of operations for initial start of a script. */
+static const int MAX_CONSTRUCTOR_OPS = 100000;
+
+ScriptStorage::~ScriptStorage()
+{
+	/* Free our pointers */
+	if (event_data != NULL) ScriptEventController::FreeEventPointer();
+	if (log_data != NULL) ScriptLog::FreeLogPointer();
+}
+
+/**
+ * Callback called by squirrel when a script uses "print" and for error messages.
+ * @param error_msg Is this an error message?
+ * @param message The actual message text.
+ */
+static void PrintFunc(bool error_msg, const SQChar *message)
+{
+	/* Convert to OpenTTD internal capable string */
+	ScriptController::Print(error_msg, SQ2OTTD(message));
+}
+
+ScriptInstance::ScriptInstance(const char *APIName) :
+	engine(NULL),
+	controller(NULL),
+	storage(NULL),
+	instance(NULL),
+	is_started(false),
+	is_dead(false),
+	is_save_data_on_stack(false),
+	suspend(0),
+	callback(NULL)
+{
+	this->storage = new ScriptStorage();
+	this->engine  = new Squirrel(APIName);
+	this->engine->SetPrintFunction(&PrintFunc);
+}
+
+void ScriptInstance::Initialize(const char *main_script, const char *instance_name)
+{
+	ScriptObject::ActiveInstance active(this);
+
+	this->controller = new ScriptController();
+
+	/* Register the API functions and classes */
+	this->engine->SetGlobalPointer(this->engine);
+	this->RegisterAPI();
+
+	try {
+		ScriptObject::SetAllowDoCommand(false);
+		/* Load and execute the script for this script */
+		if (strcmp(main_script, "%_dummy") == 0) {
+			extern void AI_CreateAIDummy(HSQUIRRELVM vm);
+			AI_CreateAIDummy(this->engine->GetVM());
+		} else if (!this->engine->LoadScript(main_script) || this->engine->IsSuspended()) {
+			if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long to load script. AI is not started.");
+			this->Died();
+			return;
+		}
+
+		/* Create the main-class */
+		this->instance = MallocT<SQObject>(1);
+		if (!this->engine->CreateClassInstance(instance_name, this->controller, this->instance)) {
+			this->Died();
+			return;
+		}
+		ScriptObject::SetAllowDoCommand(true);
+	} catch (Script_FatalError e) {
+		this->is_dead = true;
+		this->engine->ThrowError(e.GetErrorMessage());
+		this->engine->ResumeError();
+		this->Died();
+	}
+}
+
+void ScriptInstance::RegisterAPI()
+{
+	squirrel_register_std(this->engine);
+}
+
+ScriptInstance::~ScriptInstance()
+{
+	ScriptObject::ActiveInstance active(this);
+
+	if (instance != NULL) this->engine->ReleaseObject(this->instance);
+	if (engine != NULL) delete this->engine;
+	delete this->storage;
+	delete this->controller;
+	free(this->instance);
+}
+
+void ScriptInstance::Continue()
+{
+	assert(this->suspend < 0);
+	this->suspend = -this->suspend - 1;
+}
+
+void ScriptInstance::Died()
+{
+	DEBUG(ai, 0, "The script died unexpectedly.");
+	this->is_dead = true;
+
+	if (this->instance != NULL) this->engine->ReleaseObject(this->instance);
+	delete this->engine;
+	this->instance = NULL;
+	this->engine = NULL;
+}
+
+void ScriptInstance::GameLoop()
+{
+	ScriptObject::ActiveInstance active(this);
+
+	if (this->IsDead()) return;
+	if (this->engine->HasScriptCrashed()) {
+		/* The script crashed during saving, kill it here. */
+		this->Died();
+		return;
+	}
+	this->controller->ticks++;
+
+	if (this->suspend   < -1) this->suspend++; // Multiplayer suspend, increase up to -1.
+	if (this->suspend   < 0)  return;          // Multiplayer suspend, wait for Continue().
+	if (--this->suspend > 0)  return;          // Singleplayer suspend, decrease to 0.
+
+	/* If there is a callback to call, call that first */
+	if (this->callback != NULL) {
+		if (this->is_save_data_on_stack) {
+			sq_poptop(this->engine->GetVM());
+			this->is_save_data_on_stack = false;
+		}
+		try {
+			this->callback(this);
+		} catch (Script_Suspend e) {
+			this->suspend  = e.GetSuspendTime();
+			this->callback = e.GetSuspendCallback();
+
+			return;
+		}
+	}
+
+	this->suspend  = 0;
+	this->callback = NULL;
+
+	if (!this->is_started) {
+		try {
+			ScriptObject::SetAllowDoCommand(false);
+			/* Run the constructor if it exists. Don't allow any DoCommands in it. */
+			if (this->engine->MethodExists(*this->instance, "constructor")) {
+				if (!this->engine->CallMethod(*this->instance, "constructor", MAX_CONSTRUCTOR_OPS) || this->engine->IsSuspended()) {
+					if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long to initialize. Script is not started.");
+					this->Died();
+					return;
+				}
+			}
+			if (!this->CallLoad() || this->engine->IsSuspended()) {
+				if (this->engine->IsSuspended()) ScriptLog::Error("This script took too long in the Load function. Script is not started.");
+				this->Died();
+				return;
+			}
+			ScriptObject::SetAllowDoCommand(true);
+			/* Start the script by calling Start() */
+			if (!this->engine->CallMethod(*this->instance, "Start",  _settings_game.ai.ai_max_opcode_till_suspend) || !this->engine->IsSuspended()) this->Died();
+		} catch (Script_Suspend e) {
+			this->suspend  = e.GetSuspendTime();
+			this->callback = e.GetSuspendCallback();
+		} catch (Script_FatalError e) {
+			this->is_dead = true;
+			this->engine->ThrowError(e.GetErrorMessage());
+			this->engine->ResumeError();
+			this->Died();
+		}
+
+		this->is_started = true;
+		return;
+	}
+	if (this->is_save_data_on_stack) {
+		sq_poptop(this->engine->GetVM());
+		this->is_save_data_on_stack = false;
+	}
+
+	/* Continue the VM */
+	try {
+		if (!this->engine->Resume(_settings_game.ai.ai_max_opcode_till_suspend)) this->Died();
+	} catch (Script_Suspend e) {
+		this->suspend  = e.GetSuspendTime();
+		this->callback = e.GetSuspendCallback();
+	} catch (Script_FatalError e) {
+		this->is_dead = true;
+		this->engine->ThrowError(e.GetErrorMessage());
+		this->engine->ResumeError();
+		this->Died();
+	}
+}
+
+void ScriptInstance::CollectGarbage() const
+{
+	if (this->is_started && !this->IsDead()) this->engine->CollectGarbage();
+}
+
+/* static */ void ScriptInstance::DoCommandReturn(ScriptInstance *instance)
+{
+	instance->engine->InsertResult(ScriptObject::GetLastCommandRes());
+}
+
+/* static */ void ScriptInstance::DoCommandReturnVehicleID(ScriptInstance *instance)
+{
+	instance->engine->InsertResult(ScriptObject::GetNewVehicleID());
+}
+
+/* static */ void ScriptInstance::DoCommandReturnSignID(ScriptInstance *instance)
+{
+	instance->engine->InsertResult(ScriptObject::GetNewSignID());
+}
+
+/* static */ void ScriptInstance::DoCommandReturnGroupID(ScriptInstance *instance)
+{
+	instance->engine->InsertResult(ScriptObject::GetNewGroupID());
+}
+
+ScriptStorage *ScriptInstance::GetStorage()
+{
+	return this->storage;
+}
+
+void *ScriptInstance::GetLogPointer()
+{
+	ScriptObject::ActiveInstance active(this);
+
+	return ScriptObject::GetLogPointer();
+}
+
+/*
+ * All data is stored in the following format:
+ * First 1 byte indicating if there is a data blob at all.
+ * 1 byte indicating the type of data.
+ * The data itself, this differs per type:
+ *  - integer: a binary representation of the integer (int32).
+ *  - string:  First one byte with the string length, then a 0-terminated char
+ *             array. The string can't be longer than 255 bytes (including
+ *             terminating '\0').
+ *  - array:   All data-elements of the array are saved recursive in this
+ *             format, and ended with an element of the type
+ *             SQSL_ARRAY_TABLE_END.
+ *  - table:   All key/value pairs are saved in this format (first key 1, then
+ *             value 1, then key 2, etc.). All keys and values can have an
+ *             arbitrary type (as long as it is supported by the save function
+ *             of course). The table is ended with an element of the type
+ *             SQSL_ARRAY_TABLE_END.
+ *  - bool:    A single byte with value 1 representing true and 0 false.
+ *  - null:    No data.
+ */
+
+/** The type of the data that follows in the savegame. */
+enum SQSaveLoadType {
+	SQSL_INT             = 0x00, ///< The following data is an integer.
+	SQSL_STRING          = 0x01, ///< The following data is an string.
+	SQSL_ARRAY           = 0x02, ///< The following data is an array.
+	SQSL_TABLE           = 0x03, ///< The following data is an table.
+	SQSL_BOOL            = 0x04, ///< The following data is a boolean.
+	SQSL_NULL            = 0x05, ///< A null variable.
+	SQSL_ARRAY_TABLE_END = 0xFF, ///< Marks the end of an array or table, no data follows.
+};
+
+static byte _script_sl_byte; ///< Used as source/target by the script saveload code to store/load a single byte.
+
+/** SaveLoad array that saves/loads exactly one byte. */
+static const SaveLoad _script_byte[] = {
+	SLEG_VAR(_script_sl_byte, SLE_UINT8),
+	SLE_END()
+};
+
+static const uint SCRIPTSAVE_MAX_DEPTH = 25; ///< The maximum recursive depth for items stored in the savegame.
+
+/* static */ bool ScriptInstance::SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test)
+{
+	if (max_depth == 0) {
+		ScriptLog::Error("Savedata can only be nested to 25 deep. No data saved.");
+		return false;
+	}
+
+	switch (sq_gettype(vm, index)) {
+		case OT_INTEGER: {
+			if (!test) {
+				_script_sl_byte = SQSL_INT;
+				SlObject(NULL, _script_byte);
+			}
+			SQInteger res;
+			sq_getinteger(vm, index, &res);
+			if (!test) {
+				int value = (int)res;
+				SlArray(&value, 1, SLE_INT32);
+			}
+			return true;
+		}
+
+		case OT_STRING: {
+			if (!test) {
+				_script_sl_byte = SQSL_STRING;
+				SlObject(NULL, _script_byte);
+			}
+			const SQChar *res;
+			sq_getstring(vm, index, &res);
+			/* @bug if a string longer than 512 characters is given to SQ2OTTD, the
+			 *  internal buffer overflows. */
+			const char *buf = SQ2OTTD(res);
+			size_t len = strlen(buf) + 1;
+			if (len >= 255) {
+				ScriptLog::Error("Maximum string length is 254 chars. No data saved.");
+				return false;
+			}
+			if (!test) {
+				_script_sl_byte = (byte)len;
+				SlObject(NULL, _script_byte);
+				SlArray(const_cast<char *>(buf), len, SLE_CHAR);
+			}
+			return true;
+		}
+
+		case OT_ARRAY: {
+			if (!test) {
+				_script_sl_byte = SQSL_ARRAY;
+				SlObject(NULL, _script_byte);
+			}
+			sq_pushnull(vm);
+			while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
+				/* Store the value */
+				bool res = SaveObject(vm, -1, max_depth - 1, test);
+				sq_pop(vm, 2);
+				if (!res) {
+					sq_pop(vm, 1);
+					return false;
+				}
+			}
+			sq_pop(vm, 1);
+			if (!test) {
+				_script_sl_byte = SQSL_ARRAY_TABLE_END;
+				SlObject(NULL, _script_byte);
+			}
+			return true;
+		}
+
+		case OT_TABLE: {
+			if (!test) {
+				_script_sl_byte = SQSL_TABLE;
+				SlObject(NULL, _script_byte);
+			}
+			sq_pushnull(vm);
+			while (SQ_SUCCEEDED(sq_next(vm, index - 1))) {
+				/* Store the key + value */
+				bool res = SaveObject(vm, -2, max_depth - 1, test) && SaveObject(vm, -1, max_depth - 1, test);
+				sq_pop(vm, 2);
+				if (!res) {
+					sq_pop(vm, 1);
+					return false;
+				}
+			}
+			sq_pop(vm, 1);
+			if (!test) {
+				_script_sl_byte = SQSL_ARRAY_TABLE_END;
+				SlObject(NULL, _script_byte);
+			}
+			return true;
+		}
+
+		case OT_BOOL: {
+			if (!test) {
+				_script_sl_byte = SQSL_BOOL;
+				SlObject(NULL, _script_byte);
+			}
+			SQBool res;
+			sq_getbool(vm, index, &res);
+			if (!test) {
+				_script_sl_byte = res ? 1 : 0;
+				SlObject(NULL, _script_byte);
+			}
+			return true;
+		}
+
+		case OT_NULL: {
+			if (!test) {
+				_script_sl_byte = SQSL_NULL;
+				SlObject(NULL, _script_byte);
+			}
+			return true;
+		}
+
+		default:
+			ScriptLog::Error("You tried to save an unsupported type. No data saved.");
+			return false;
+	}
+}
+
+/* static */ void ScriptInstance::SaveEmpty()
+{
+	_script_sl_byte = 0;
+	SlObject(NULL, _script_byte);
+}
+
+void ScriptInstance::Save()
+{
+	ScriptObject::ActiveInstance active(this);
+
+	/* Don't save data if the script didn't start yet or if it crashed. */
+	if (this->engine == NULL || this->engine->HasScriptCrashed()) {
+		SaveEmpty();
+		return;
+	}
+
+	HSQUIRRELVM vm = this->engine->GetVM();
+	if (this->is_save_data_on_stack) {
+		_script_sl_byte = 1;
+		SlObject(NULL, _script_byte);
+		/* Save the data that was just loaded. */
+		SaveObject(vm, -1, SCRIPTSAVE_MAX_DEPTH, false);
+	} else if (!this->is_started) {
+		SaveEmpty();
+		return;
+	} else if (this->engine->MethodExists(*this->instance, "Save")) {
+		HSQOBJECT savedata;
+		/* We don't want to be interrupted during the save function. */
+		bool backup_allow = ScriptObject::GetAllowDoCommand();
+		ScriptObject::SetAllowDoCommand(false);
+		try {
+			if (!this->engine->CallMethod(*this->instance, "Save", &savedata, MAX_SL_OPS)) {
+				/* The script crashed in the Save function. We can't kill
+				 * it here, but do so in the next script tick. */
+				SaveEmpty();
+				this->engine->CrashOccurred();
+				return;
+			}
+		} catch (Script_FatalError e) {
+			/* If we don't mark the script as dead here cleaning up the squirrel
+			 * stack could throw Script_FatalError again. */
+			this->is_dead = true;
+			this->engine->ThrowError(e.GetErrorMessage());
+			this->engine->ResumeError();
+			SaveEmpty();
+			/* We can't kill the script here, so mark it as crashed (not dead) and
+			 * kill it in the next script tick. */
+			this->is_dead = false;
+			this->engine->CrashOccurred();
+			return;
+		}
+		ScriptObject::SetAllowDoCommand(backup_allow);
+
+		if (!sq_istable(savedata)) {
+			ScriptLog::Error(this->engine->IsSuspended() ? "This script took too long to Save." : "Save function should return a table.");
+			SaveEmpty();
+			this->engine->CrashOccurred();
+			return;
+		}
+		sq_pushobject(vm, savedata);
+		if (SaveObject(vm, -1, SCRIPTSAVE_MAX_DEPTH, true)) {
+			_script_sl_byte = 1;
+			SlObject(NULL, _script_byte);
+			SaveObject(vm, -1, SCRIPTSAVE_MAX_DEPTH, false);
+			this->is_save_data_on_stack = true;
+		} else {
+			SaveEmpty();
+			this->engine->CrashOccurred();
+		}
+	} else {
+		ScriptLog::Warning("Save function is not implemented");
+		_script_sl_byte = 0;
+		SlObject(NULL, _script_byte);
+	}
+}
+
+void ScriptInstance::Suspend()
+{
+	HSQUIRRELVM vm = this->engine->GetVM();
+	Squirrel::DecreaseOps(vm, _settings_game.ai.ai_max_opcode_till_suspend);
+}
+
+/* static */ bool ScriptInstance::LoadObjects(HSQUIRRELVM vm)
+{
+	SlObject(NULL, _script_byte);
+	switch (_script_sl_byte) {
+		case SQSL_INT: {
+			int value;
+			SlArray(&value, 1, SLE_INT32);
+			if (vm != NULL) sq_pushinteger(vm, (SQInteger)value);
+			return true;
+		}
+
+		case SQSL_STRING: {
+			SlObject(NULL, _script_byte);
+			static char buf[256];
+			SlArray(buf, _script_sl_byte, SLE_CHAR);
+			if (vm != NULL) sq_pushstring(vm, OTTD2SQ(buf), -1);
+			return true;
+		}
+
+		case SQSL_ARRAY: {
+			if (vm != NULL) sq_newarray(vm, 0);
+			while (LoadObjects(vm)) {
+				if (vm != NULL) sq_arrayappend(vm, -2);
+				/* The value is popped from the stack by squirrel. */
+			}
+			return true;
+		}
+
+		case SQSL_TABLE: {
+			if (vm != NULL) sq_newtable(vm);
+			while (LoadObjects(vm)) {
+				LoadObjects(vm);
+				if (vm != NULL) sq_rawset(vm, -3);
+				/* The key (-2) and value (-1) are popped from the stack by squirrel. */
+			}
+			return true;
+		}
+
+		case SQSL_BOOL: {
+			SlObject(NULL, _script_byte);
+			if (vm != NULL) sq_pushinteger(vm, (SQBool)(_script_sl_byte != 0));
+			return true;
+		}
+
+		case SQSL_NULL: {
+			if (vm != NULL) sq_pushnull(vm);
+			return true;
+		}
+
+		case SQSL_ARRAY_TABLE_END: {
+			return false;
+		}
+
+		default: NOT_REACHED();
+	}
+}
+
+/* static */ void ScriptInstance::LoadEmpty()
+{
+	SlObject(NULL, _script_byte);
+	/* Check if there was anything saved at all. */
+	if (_script_sl_byte == 0) return;
+
+	LoadObjects(NULL);
+}
+
+void ScriptInstance::Load(int version)
+{
+	ScriptObject::ActiveInstance active(this);
+
+	if (this->engine == NULL || version == -1) {
+		LoadEmpty();
+		return;
+	}
+	HSQUIRRELVM vm = this->engine->GetVM();
+
+	SlObject(NULL, _script_byte);
+	/* Check if there was anything saved at all. */
+	if (_script_sl_byte == 0) return;
+
+	sq_pushinteger(vm, version);
+	LoadObjects(vm);
+	this->is_save_data_on_stack = true;
+}
+
+bool ScriptInstance::CallLoad()
+{
+	HSQUIRRELVM vm = this->engine->GetVM();
+	/* Is there save data that we should load? */
+	if (!this->is_save_data_on_stack) return true;
+	/* Whatever happens, after CallLoad the savegame data is removed from the stack. */
+	this->is_save_data_on_stack = false;
+
+	if (!this->engine->MethodExists(*this->instance, "Load")) {
+		ScriptLog::Warning("Loading failed: there was data for the script to load, but the script does not have a Load() function.");
+
+		/* Pop the savegame data and version. */
+		sq_pop(vm, 2);
+		return true;
+	}
+
+	/* Go to the instance-root */
+	sq_pushobject(vm, *this->instance);
+	/* Find the function-name inside the script */
+	sq_pushstring(vm, OTTD2SQ("Load"), -1);
+	/* Change the "Load" string in a function pointer */
+	sq_get(vm, -2);
+	/* Push the main instance as "this" object */
+	sq_pushobject(vm, *this->instance);
+	/* Push the version data and savegame data as arguments */
+	sq_push(vm, -5);
+	sq_push(vm, -5);
+
+	/* Call the script load function. sq_call removes the arguments (but not the
+	 * function pointer) from the stack. */
+	if (SQ_FAILED(sq_call(vm, 3, SQFalse, SQFalse, MAX_SL_OPS))) return false;
+
+	/* Pop 1) The version, 2) the savegame data, 3) the object instance, 4) the function pointer. */
+	sq_pop(vm, 4);
+	return true;
+}
+
+SQInteger ScriptInstance::GetOpsTillSuspend()
+{
+	return this->engine->GetOpsTillSuspend();
+}
+
+void ScriptInstance::DoCommandCallback(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2)
+{
+	ScriptObject::ActiveInstance active(this);
+
+	ScriptObject::SetLastCommandRes(result.Succeeded());
+
+	if (result.Failed()) {
+		ScriptObject::SetLastError(ScriptError::StringToError(result.GetErrorMessage()));
+	} else {
+		ScriptObject::IncreaseDoCommandCosts(result.GetCost());
+		ScriptObject::SetLastCost(result.GetCost());
+	}
+}
+
+void ScriptInstance::InsertEvent(class ScriptEvent *event)
+{
+	ScriptObject::ActiveInstance active(this);
+
+	ScriptEventController::InsertEvent(event);
+}
new file mode 100644
--- /dev/null
+++ b/src/script/script_instance.hpp
@@ -0,0 +1,194 @@
+/* $Id$ */
+
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** @file script_instance.hpp The ScriptInstance tracks a script. */
+
+#ifndef SCRIPT_INSTANCE_HPP
+#define SCRIPT_INSTANCE_HPP
+
+#include <squirrel.h>
+#include "script_suspend.hpp"
+
+/** Runtime information about a script like a pointer to the squirrel vm and the current state. */
+class ScriptInstance {
+public:
+	friend class ScriptObject;
+	friend class ScriptController;
+
+	/**
+	 * Create a new script.
+	 */
+	ScriptInstance(const char *APIName);
+	virtual ~ScriptInstance();
+
+	/**
+	 * Initialize the script and prepare it for its first run.
+	 * @param main_script The name of the script to load.
+	 * @param instance_name The name of the instance out of the script to load.
+	 */
+	void Initialize(const char *main_script, const char *instance_name);
+
+	/**
+	 * A script in multiplayer waits for the server to handle his DoCommand.
+	 *  It keeps waiting for this until this function is called.
+	 */
+	void Continue();
+
+	/**
+	 * Run the GameLoop of a script.
+	 */
+	void GameLoop();
+
+	/**
+	 * Let the VM collect any garbage.
+	 */
+	void CollectGarbage() const;
+
+	/**
+	 * Get the storage of this script.
+	 */
+	class ScriptStorage *GetStorage();
+
+	/**
+	 * Get the log pointer of this script.
+	 */
+	void *GetLogPointer();
+
+	/**
+	 * Return a true/false reply for a DoCommand.
+	 */
+	static void DoCommandReturn(ScriptInstance *instance);
+
+	/**
+	 * Return a VehicleID reply for a DoCommand.
+	 */
+	static void DoCommandReturnVehicleID(ScriptInstance *instance);
+
+	/**
+	 * Return a SignID reply for a DoCommand.
+	 */
+	static void DoCommandReturnSignID(ScriptInstance *instance);
+
+	/**
+	 * Return a GroupID reply for a DoCommand.
+	 */
+	static void DoCommandReturnGroupID(ScriptInstance *instance);
+
+	/**
+	 * Get the controller attached to the instance.
+	 */
+	class ScriptController *GetController() { return controller; }
+
+	/**
+	 * Return the "this script died" value
+	 */
+	inline bool IsDead() const { return this->is_dead; }
+
+	/**
+	 * Call the script Save function and save all data in the savegame.
+	 */
+	void Save();
+
+	/**
+	 * Don't save any data in the savegame.
+	 */
+	static void SaveEmpty();
+
+	/**
+	 * Load data from a savegame and store it on the stack.
+	 * @param version The version of the script when saving, or -1 if this was
+	 *  not the original script saving the game.
+	 */
+	void Load(int version);
+
+	/**
+	 * Load and discard data from a savegame.
+	 */
+	static void LoadEmpty();
+
+	/**
+	 * Reduces the number of opcodes the script have left to zero. Unless
+	 * the script is in a state where it cannot suspend it will be suspended
+	 * for the reminder of the current tick. This function is safe to
+	 * call from within a function called by the script.
+	 */
+	void Suspend();
+
+	/**
+	 * Get the number of operations the script can execute before being suspended.
+	 * This function is safe to call from within a function called by the script.
+	 * @return The number of operations to execute.
+	 */
+	SQInteger GetOpsTillSuspend();
+
+	/**
+	 * DoCommand callback function for all commands executed by scripts.
+	 * @param result The result of the command.
+	 * @param tile The tile on which the command was executed.
+	 * @param p1 p1 as given to DoCommandPInternal.
+	 * @param p2 p2 as given to DoCommandPInternal.
+	 */
+	void DoCommandCallback(const CommandCost &result, TileIndex tile, uint32 p1, uint32 p2);
+
+	/**
+	 * Insert an event for this script.
+	 * @param event The event to insert.
+	 */
+	void InsertEvent(class ScriptEvent *event);
+
+protected:
+	class Squirrel *engine;               ///< A wrapper around the squirrel vm.
+
+	/**
+	 * Register all API functions to the VM.
+	 */
+	virtual void RegisterAPI();
+
+	/**
+	 * Tell the script it died.
+	 */
+	virtual void Died();
+
+private:
+	class ScriptController *controller;   ///< The script main class.
+	class ScriptStorage *storage;         ///< Some global information for each running script.
+	SQObject *instance;                   ///< Squirrel-pointer to the script main class.
+
+	bool is_started;                      ///< Is the scripts constructor executed?
+	bool is_dead;                         ///< True if the script has been stopped.
+	bool is_save_data_on_stack;           ///< Is the save data still on the squirrel stack?
+	int suspend;                          ///< The amount of ticks to suspend this script before it's allowed to continue.
+	Script_SuspendCallbackProc *callback; ///< Callback that should be called in the next tick the script runs.
+
+	/**
+	 * Call the script Load function if it exists and data was loaded
+	 *  from a savegame.
+	 */
+	bool CallLoad();
+
+	/**
+	 * Save one object (int / string / array / table) to the savegame.
+	 * @param vm The virtual machine to get all the data from.
+	 * @param index The index on the squirrel stack of the element to save.
+	 * @param max_depth The maximum depth recursive arrays / tables will be stored
+	 *   with before an error is returned.
+	 * @param test If true, don't really store the data but only check if it is
+	 *   valid.
+	 * @return True if the saving was successful.
+	 */
+	static bool SaveObject(HSQUIRRELVM vm, SQInteger index, int max_depth, bool test);
+
+	/**
+	 * Load all objects from a savegame.
+	 * @return True if the loading was successful.
+	 */
+	static bool LoadObjects(HSQUIRRELVM vm);
+};
+
+#endif /* SCRIPT_INSTANCE_HPP */
--- a/src/script/script_suspend.hpp
+++ b/src/script/script_suspend.hpp
@@ -15,7 +15,7 @@
 /**
  * The callback function when a script suspends.
  */
-typedef void (Script_SuspendCallbackProc)(class AIInstance *instance);
+typedef void (Script_SuspendCallbackProc)(class ScriptInstance *instance);
 
 /**
  * A throw-class that is given when the script wants to suspend.
--- a/src/script/squirrel.hpp
+++ b/src/script/squirrel.hpp
@@ -68,7 +68,7 @@
 
 public:
 	friend class AIScanner;
-	friend class AIInstance;
+	friend class ScriptInstance;
 	friend class ScriptController;
 	friend void squirrel_register_std(Squirrel *engine);