summaryrefslogtreecommitdiff
path: root/src/ai/ai_instance.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ai/ai_instance.cpp')
-rw-r--r--src/ai/ai_instance.cpp628
1 files changed, 628 insertions, 0 deletions
diff --git a/src/ai/ai_instance.cpp b/src/ai/ai_instance.cpp
new file mode 100644
index 000000000..a0612498d
--- /dev/null
+++ b/src/ai/ai_instance.cpp
@@ -0,0 +1,628 @@
+/* $Id$ */
+
+/** @file ai_instance.cpp Implementation of AIInstance. */
+
+#include "../stdafx.h"
+#include "../openttd.h"
+#include "../debug.h"
+#include "../company_func.h"
+#include "../core/alloc_func.hpp"
+#include "../string_func.h"
+#include "../settings_type.h"
+#include "../company_base.h"
+#include "../saveload/saveload.h"
+#include "table/strings.h"
+
+#include <squirrel.h>
+#include "../script/squirrel.hpp"
+#include "../script/squirrel_helper.hpp"
+#include "../script/squirrel_class.hpp"
+#include "../script/squirrel_std.hpp"
+#include "ai.hpp"
+#include "api/ai_controller.hpp"
+#include "ai_info.hpp"
+#include "ai_storage.hpp"
+#include "ai_instance.hpp"
+
+/* Convert all AI related classes to Squirrel data.
+ * Note: this line a marker in squirrel_export.sh. Do not change! */
+#include "api/ai_abstractlist.hpp.sq"
+#include "api/ai_accounting.hpp.sq"
+#include "api/ai_airport.hpp.sq"
+#include "api/ai_base.hpp.sq"
+#include "api/ai_bridge.hpp.sq"
+#include "api/ai_bridgelist.hpp.sq"
+#include "api/ai_cargo.hpp.sq"
+#include "api/ai_cargolist.hpp.sq"
+#include "api/ai_company.hpp.sq"
+#include "api/ai_controller.hpp.sq"
+#include "api/ai_date.hpp.sq"
+#include "api/ai_depotlist.hpp.sq"
+#include "api/ai_engine.hpp.sq"
+#include "api/ai_enginelist.hpp.sq"
+#include "api/ai_error.hpp.sq"
+#include "api/ai_event.hpp.sq"
+#include "api/ai_event_types.hpp.sq"
+#include "api/ai_execmode.hpp.sq"
+#include "api/ai_gamesettings.hpp.sq"
+#include "api/ai_group.hpp.sq"
+#include "api/ai_grouplist.hpp.sq"
+#include "api/ai_industry.hpp.sq"
+#include "api/ai_industrylist.hpp.sq"
+#include "api/ai_industrytype.hpp.sq"
+#include "api/ai_industrytypelist.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"
+#include "api/ai_rail.hpp.sq"
+#include "api/ai_railtypelist.hpp.sq"
+#include "api/ai_road.hpp.sq"
+#include "api/ai_sign.hpp.sq"
+#include "api/ai_station.hpp.sq"
+#include "api/ai_stationlist.hpp.sq"
+#include "api/ai_subsidy.hpp.sq"
+#include "api/ai_subsidylist.hpp.sq"
+#include "api/ai_testmode.hpp.sq"
+#include "api/ai_tile.hpp.sq"
+#include "api/ai_tilelist.hpp.sq"
+#include "api/ai_town.hpp.sq"
+#include "api/ai_townlist.hpp.sq"
+#include "api/ai_tunnel.hpp.sq"
+#include "api/ai_vehicle.hpp.sq"
+#include "api/ai_vehiclelist.hpp.sq"
+
+/* static */ AIInstance *AIInstance::current_instance = NULL;
+
+AIStorage::~AIStorage()
+{
+ /* Free our pointers */
+ if (event_data != NULL) AIEventController::FreeEventPointer();
+ if (log_data != NULL) AILog::FreeLogPointer();
+}
+
+static void PrintFunc(bool error_msg, const SQChar *message)
+{
+ /* Convert to OpenTTD internal capable string */
+ AIController::Print(error_msg, FS2OTTD(message));
+}
+
+AIInstance::AIInstance(AIInfo *info) :
+ controller(NULL),
+ storage(NULL),
+ engine(NULL),
+ instance(NULL),
+ is_started(false),
+ is_dead(false),
+ suspend(0),
+ callback(NULL)
+{
+ /* Set the instance already, so we can use AIObject::Set commands */
+ GetCompany(_current_company)->ai_instance = this;
+ AIInstance::current_instance = this;
+
+ this->controller = new AIController();
+ this->storage = new AIStorage();
+ this->engine = new Squirrel();
+ this->engine->SetPrintFunction(&PrintFunc);
+
+ /* The import method is available at a very early stage */
+ this->engine->AddMethod("import", &AILibrary::Import, 4, "?ssi");
+
+ /* Register the AIController */
+ SQAIController_Register(this->engine);
+
+ /* Load and execute the script for this AI */
+ const char *script_name = info->GetScriptName();
+ if (strcmp(script_name, "%_dummy") == 0) {
+ extern void AI_CreateAIDummy(HSQUIRRELVM vm);
+ AI_CreateAIDummy(this->engine->GetVM());
+ } else if (!this->engine->LoadScript(script_name)) {
+ 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;
+ }
+
+ /* Register the API functions and classes */
+ this->RegisterAPI();
+
+ /* Run the constructor if it exists. Don't allow any DoCommands in it. */
+ if (this->engine->MethodExists(*this->instance, "constructor")) {
+ AIObject::SetAllowDoCommand(false);
+ this->engine->CallMethod(*this->instance, "constructor");
+ AIObject::SetAllowDoCommand(true);
+ }
+}
+
+AIInstance::~AIInstance()
+{
+ if (engine != NULL) delete this->engine;
+ delete this->storage;
+ delete this->controller;
+ free(this->instance);
+}
+
+void AIInstance::RegisterAPI()
+{
+/* Register all classes */
+ squirrel_register_std(this->engine);
+ SQAIAbstractList_Register(this->engine);
+ SQAIAccounting_Register(this->engine);
+ SQAIAirport_Register(this->engine);
+ SQAIBase_Register(this->engine);
+ SQAIBridge_Register(this->engine);
+ SQAIBridgeList_Register(this->engine);
+ SQAIBridgeList_Length_Register(this->engine);
+ SQAICargo_Register(this->engine);
+ SQAICargoList_Register(this->engine);
+ SQAICargoList_IndustryAccepting_Register(this->engine);
+ SQAICargoList_IndustryProducing_Register(this->engine);
+ SQAICompany_Register(this->engine);
+ SQAIDate_Register(this->engine);
+ SQAIDepotList_Register(this->engine);
+ SQAIEngine_Register(this->engine);
+ SQAIEngineList_Register(this->engine);
+ SQAIError_Register(this->engine);
+ SQAIEvent_Register(this->engine);
+ SQAIEventCompanyBankrupt_Register(this->engine);
+ SQAIEventCompanyInTrouble_Register(this->engine);
+ SQAIEventCompanyMerger_Register(this->engine);
+ SQAIEventCompanyNew_Register(this->engine);
+ SQAIEventController_Register(this->engine);
+ SQAIEventEngineAvailable_Register(this->engine);
+ SQAIEventEnginePreview_Register(this->engine);
+ SQAIEventIndustryClose_Register(this->engine);
+ SQAIEventIndustryOpen_Register(this->engine);
+ SQAIEventStationFirstVehicle_Register(this->engine);
+ SQAIEventSubsidyAwarded_Register(this->engine);
+ SQAIEventSubsidyExpired_Register(this->engine);
+ SQAIEventSubsidyOffer_Register(this->engine);
+ SQAIEventSubsidyOfferExpired_Register(this->engine);
+ SQAIEventTest_Register(this->engine);
+ SQAIEventVehicleCrashed_Register(this->engine);
+ SQAIEventVehicleLost_Register(this->engine);
+ SQAIEventVehicleUnprofitable_Register(this->engine);
+ SQAIEventVehicleWaitingInDepot_Register(this->engine);
+ SQAIExecMode_Register(this->engine);
+ SQAIGameSettings_Register(this->engine);
+ SQAIGroup_Register(this->engine);
+ SQAIGroupList_Register(this->engine);
+ SQAIIndustry_Register(this->engine);
+ SQAIIndustryList_Register(this->engine);
+ SQAIIndustryList_CargoAccepting_Register(this->engine);
+ SQAIIndustryList_CargoProducing_Register(this->engine);
+ SQAIIndustryType_Register(this->engine);
+ SQAIIndustryTypeList_Register(this->engine);
+ SQAIList_Register(this->engine);
+ SQAILog_Register(this->engine);
+ SQAIMap_Register(this->engine);
+ SQAIMarine_Register(this->engine);
+ SQAIOrder_Register(this->engine);
+ SQAIRail_Register(this->engine);
+ SQAIRailTypeList_Register(this->engine);
+ SQAIRoad_Register(this->engine);
+ SQAISign_Register(this->engine);
+ SQAIStation_Register(this->engine);
+ SQAIStationList_Register(this->engine);
+ SQAIStationList_Vehicle_Register(this->engine);
+ SQAISubsidy_Register(this->engine);
+ SQAISubsidyList_Register(this->engine);
+ SQAITestMode_Register(this->engine);
+ SQAITile_Register(this->engine);
+ SQAITileList_Register(this->engine);
+ SQAITileList_IndustryAccepting_Register(this->engine);
+ SQAITileList_IndustryProducing_Register(this->engine);
+ SQAITileList_StationType_Register(this->engine);
+ SQAITown_Register(this->engine);
+ SQAITownList_Register(this->engine);
+ SQAITunnel_Register(this->engine);
+ SQAIVehicle_Register(this->engine);
+ SQAIVehicleList_Register(this->engine);
+ SQAIVehicleList_Station_Register(this->engine);
+
+ this->engine->SetGlobalPointer(this->engine);
+}
+
+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;
+
+ delete this->engine;
+ this->engine = NULL;
+}
+
+void AIInstance::GameLoop()
+{
+ if (this->is_dead) 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) {
+ try {
+ this->callback(this);
+ } catch (AI_VMSuspend e) {
+ this->suspend = e.GetSuspendTime();
+ this->callback = e.GetSuspendCallback();
+
+ return;
+ }
+ }
+
+ this->suspend = 0;
+ this->callback = NULL;
+
+ if (!this->is_started) {
+ /* Start the AI by calling Start() */
+ try {
+ if (!this->engine->CallMethod(*this->instance, "Start", _settings_game.ai.ai_max_opcode_till_suspend)) this->Died();
+ } catch (AI_VMSuspend e) {
+ this->suspend = e.GetSuspendTime();
+ this->callback = e.GetSuspendCallback();
+ }
+
+ this->is_started = true;
+ return;
+ }
+
+ /* Continue the VM */
+ try {
+ if (!this->engine->Resume(_settings_game.ai.ai_max_opcode_till_suspend)) this->Died();
+ } catch (AI_VMSuspend e) {
+ this->suspend = e.GetSuspendTime();
+ this->callback = e.GetSuspendCallback();
+ }
+}
+
+/* static */ void AIInstance::DoCommandReturn(AIInstance *instance)
+{
+ instance->engine->InsertResult(AIObject::GetLastCommandRes());
+}
+
+/* static */ void AIInstance::DoCommandReturnVehicleID(AIInstance *instance)
+{
+ instance->engine->InsertResult(AIObject::GetNewVehicleID());
+}
+
+/* static */ void AIInstance::DoCommandReturnSignID(AIInstance *instance)
+{
+ instance->engine->InsertResult(AIObject::GetNewSignID());
+}
+
+/* static */ void AIInstance::DoCommandReturnGroupID(AIInstance *instance)
+{
+ instance->engine->InsertResult(AIObject::GetNewGroupID());
+}
+
+/* static */ AIStorage *AIInstance::GetStorage()
+{
+ assert(IsValidCompanyID(_current_company) && !IsHumanCompany(_current_company));
+ return GetCompany(_current_company)->ai_instance->storage;
+}
+
+/*
+ * 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 then 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;
+
+static const SaveLoad _ai_byte[] = {
+ SLEG_VAR(_ai_sl_byte, SLE_UINT8),
+ SLE_END()
+};
+
+enum {
+ 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) {
+ AILog::Error("Savedata can only be nested to 5 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 FS2OTTD, the
+ * internal buffer overflows. */
+ const char *buf = FS2OTTD(res);
+ size_t len = strlen(buf) + 1;
+ if (len >= 255) {
+ AILog::Error("Maximum string length is 254 chars. No data saved.");
+ return false;
+ }
+ if (!test) {
+ _ai_sl_byte = (byte)len;
+ SlObject(NULL, _ai_byte);
+ SlArray((void*)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:
+ AILog::Error("You tried to save unsupported type. No data saved.");
+ return false;
+ }
+}
+
+/* static */ void AIInstance::SaveEmpty()
+{
+ _ai_sl_byte = 0;
+ SlObject(NULL, _ai_byte);
+}
+
+void AIInstance::Save()
+{
+ /* Don't save data if the AI didn't start yet. */
+ if (this->engine == NULL) {
+ SaveEmpty();
+ return;
+ }
+
+ /* We don't want to be interrupted during the save function. */
+ AIObject::SetAllowDoCommand(false);
+
+ HSQOBJECT savedata;
+ if (this->engine->MethodExists(*this->instance, "Save")) {
+ this->engine->CallMethod(*this->instance, "Save", &savedata);
+ if (!sq_istable(savedata)) {
+ AILog::Error("Save function should return a table.");
+ _ai_sl_byte = 0;
+ SlObject(NULL, _ai_byte);
+ AIObject::SetAllowDoCommand(true);
+ return;
+ }
+ HSQUIRRELVM vm = this->engine->GetVM();
+ 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);
+ } else {
+ _ai_sl_byte = 0;
+ SlObject(NULL, _ai_byte);
+ }
+ sq_pop(vm, 1);
+ } else {
+ AILog::Warning("Save function is not implemented");
+ _ai_sl_byte = 0;
+ SlObject(NULL, _ai_byte);
+ }
+
+ AIObject::SetAllowDoCommand(true);
+}
+
+/* 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, OTTD2FS(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);
+}
+
+bool AIInstance::Load()
+{
+ HSQUIRRELVM vm = (this->engine == NULL) ? NULL : this->engine->GetVM();
+
+ SlObject(NULL, _ai_byte);
+ /* Check if there was anything saved at all. */
+ if (_ai_sl_byte == 0) return true;
+ AIObject::SetAllowDoCommand(false);
+
+ if (vm != NULL) {
+ /* Go to the instance-root */
+ sq_pushobject(vm, *this->instance);
+ /* Find the function-name inside the script */
+ sq_pushstring(vm, OTTD2FS("Load"), -1);
+ if (SQ_FAILED(sq_get(vm, -2))) sq_pushnull(vm);
+ sq_pushobject(vm, *this->instance);
+ }
+
+ LoadObjects(vm);
+
+ if (this->engine != NULL) {
+ if (this->engine->MethodExists(*this->instance, "Load")) {
+ sq_call(vm, 2, SQFalse, SQFalse);
+ } else {
+ AILog::Warning("Loading failed: there was data for the AI to load, but the AI does not have a Load() function.");
+ }
+ }
+
+ /* Pop 1) the object instance, 2) the function name, 3) the instance again, 4) the table. */
+ if (vm != NULL) sq_pop(vm, 4);
+
+ AIObject::SetAllowDoCommand(true);
+ return true;
+}