792 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			792 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* $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 ai_instance.cpp Implementation of AIInstance. */
 | 
						|
 | 
						|
#include "../stdafx.h"
 | 
						|
#include "../debug.h"
 | 
						|
#include "../saveload/saveload.h"
 | 
						|
#include "../gui.h"
 | 
						|
 | 
						|
#include "../script/squirrel_class.hpp"
 | 
						|
 | 
						|
#include "ai_config.hpp"
 | 
						|
#include "ai_storage.hpp"
 | 
						|
#include "ai_instance.hpp"
 | 
						|
#include "ai_gui.hpp"
 | 
						|
 | 
						|
/* Convert all AI related classes to Squirrel data.
 | 
						|
 * Note: this line a marker in squirrel_export.sh. Do not change! */
 | 
						|
#include "api/ai_accounting.hpp.sq"
 | 
						|
#include "api/ai_airport.hpp.sq"
 | 
						|
#include "api/ai_base.hpp.sq"
 | 
						|
#include "api/ai_basestation.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_signlist.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"
 | 
						|
#include "api/ai_waypoint.hpp.sq"
 | 
						|
#include "api/ai_waypointlist.hpp.sq"
 | 
						|
 | 
						|
#include "../company_base.h"
 | 
						|
#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;
 | 
						|
 | 
						|
AIStorage::~AIStorage()
 | 
						|
{
 | 
						|
	/* Free our pointers */
 | 
						|
	if (event_data != NULL) AIEventController::FreeEventPointer();
 | 
						|
	if (log_data != NULL) AILog::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 */
 | 
						|
	AIController::Print(error_msg, SQ2OTTD(message));
 | 
						|
}
 | 
						|
 | 
						|
AIInstance::AIInstance(AIInfo *info) :
 | 
						|
	controller(NULL),
 | 
						|
	storage(NULL),
 | 
						|
	engine(NULL),
 | 
						|
	instance(NULL),
 | 
						|
	is_started(false),
 | 
						|
	is_dead(false),
 | 
						|
	is_save_data_on_stack(false),
 | 
						|
	suspend(0),
 | 
						|
	callback(NULL)
 | 
						|
{
 | 
						|
	/* Set the instance already, so we can use AIObject::Set commands */
 | 
						|
	Company::Get(_current_company)->ai_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);
 | 
						|
 | 
						|
	/* Register the API functions and classes */
 | 
						|
	this->RegisterAPI();
 | 
						|
 | 
						|
	if (!this->LoadCompatibilityScripts(info->GetAPIVersion())) {
 | 
						|
		this->Died();
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	try {
 | 
						|
		AIObject::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()) AILog::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;
 | 
						|
		}
 | 
						|
		AIObject::SetAllowDoCommand(true);
 | 
						|
	} catch (AI_FatalError e) {
 | 
						|
		this->is_dead = true;
 | 
						|
		this->engine->ThrowError(e.GetErrorMessage());
 | 
						|
		this->engine->ResumeError();
 | 
						|
		this->Died();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
AIInstance::~AIInstance()
 | 
						|
{
 | 
						|
	if (instance != NULL) this->engine->ReleaseObject(this->instance);
 | 
						|
	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);
 | 
						|
	SQAIList_Register(this->engine);
 | 
						|
	SQAIAccounting_Register(this->engine);
 | 
						|
	SQAIAirport_Register(this->engine);
 | 
						|
	SQAIBase_Register(this->engine);
 | 
						|
	SQAIBaseStation_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);
 | 
						|
	SQAIEventCompanyAskMerger_Register(this->engine);
 | 
						|
	SQAIEventCompanyBankrupt_Register(this->engine);
 | 
						|
	SQAIEventCompanyInTrouble_Register(this->engine);
 | 
						|
	SQAIEventCompanyMerger_Register(this->engine);
 | 
						|
	SQAIEventCompanyNew_Register(this->engine);
 | 
						|
	SQAIEventController_Register(this->engine);
 | 
						|
	SQAIEventDisasterZeppelinerCleared_Register(this->engine);
 | 
						|
	SQAIEventDisasterZeppelinerCrashed_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);
 | 
						|
	SQAIEventTownFounded_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);
 | 
						|
	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);
 | 
						|
	SQAISignList_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_DefaultGroup_Register(this->engine);
 | 
						|
	SQAIVehicleList_Depot_Register(this->engine);
 | 
						|
	SQAIVehicleList_Group_Register(this->engine);
 | 
						|
	SQAIVehicleList_SharedOrders_Register(this->engine);
 | 
						|
	SQAIVehicleList_Station_Register(this->engine);
 | 
						|
	SQAIWaypoint_Register(this->engine);
 | 
						|
	SQAIWaypointList_Register(this->engine);
 | 
						|
	SQAIWaypointList_Vehicle_Register(this->engine);
 | 
						|
 | 
						|
	this->engine->SetGlobalPointer(this->engine);
 | 
						|
}
 | 
						|
 | 
						|
bool AIInstance::LoadCompatibilityScripts(const char *api_version)
 | 
						|
{
 | 
						|
	char script_name[32];
 | 
						|
	seprintf(script_name, lastof(script_name), "compat_%s.nut", api_version);
 | 
						|
	char buf[MAX_PATH];
 | 
						|
	Searchpath sp;
 | 
						|
	FOR_ALL_SEARCHPATHS(sp) {
 | 
						|
		FioAppendDirectory(buf, MAX_PATH, sp, AI_DIR);
 | 
						|
		ttd_strlcat(buf, script_name, MAX_PATH);
 | 
						|
		if (!FileExists(buf)) continue;
 | 
						|
 | 
						|
		if (this->engine->LoadScript(buf)) return true;
 | 
						|
 | 
						|
		AILog::Error("Failed to load API compatibility script");
 | 
						|
		DEBUG(ai, 0, "Error compiling / running API compatibility script: %s", buf);
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	AILog::Warning("API compatibility script not found");
 | 
						|
	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;
 | 
						|
 | 
						|
	ShowAIDebugWindow(_current_company);
 | 
						|
 | 
						|
	const AIInfo *info = AIConfig::GetConfig(_current_company)->GetInfo();
 | 
						|
	if (info != NULL) {
 | 
						|
		ShowErrorMessage(STR_ERROR_AI_PLEASE_REPORT_CRASH, INVALID_STRING_ID, WL_WARNING);
 | 
						|
 | 
						|
		if (info->GetURL() != NULL) {
 | 
						|
			AILog::Info("Please report the error to the following URL:");
 | 
						|
			AILog::Info(info->GetURL());
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void AIInstance::GameLoop()
 | 
						|
{
 | 
						|
	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 (AI_VMSuspend e) {
 | 
						|
			this->suspend  = e.GetSuspendTime();
 | 
						|
			this->callback = e.GetSuspendCallback();
 | 
						|
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	this->suspend  = 0;
 | 
						|
	this->callback = NULL;
 | 
						|
 | 
						|
	if (!this->is_started) {
 | 
						|
		try {
 | 
						|
			AIObject::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()) AILog::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()) AILog::Error("This AI took too long in the Load function. AI is not started.");
 | 
						|
				this->Died();
 | 
						|
				return;
 | 
						|
			}
 | 
						|
			AIObject::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 (AI_VMSuspend e) {
 | 
						|
			this->suspend  = e.GetSuspendTime();
 | 
						|
			this->callback = e.GetSuspendCallback();
 | 
						|
		} catch (AI_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 (AI_VMSuspend e) {
 | 
						|
		this->suspend  = e.GetSuspendTime();
 | 
						|
		this->callback = e.GetSuspendCallback();
 | 
						|
	} catch (AI_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(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(Company::IsValidAiID(_current_company));
 | 
						|
	return Company::Get(_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 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) {
 | 
						|
		AILog::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) {
 | 
						|
				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 an 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 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 = AIObject::GetAllowDoCommand();
 | 
						|
		AIObject::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 (AI_FatalError e) {
 | 
						|
			/* If we don't mark the AI as dead here cleaning up the squirrel
 | 
						|
			 * stack could throw AI_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;
 | 
						|
		}
 | 
						|
		AIObject::SetAllowDoCommand(backup_allow);
 | 
						|
 | 
						|
		if (!sq_istable(savedata)) {
 | 
						|
			AILog::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 {
 | 
						|
		AILog::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)
 | 
						|
{
 | 
						|
	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")) {
 | 
						|
		AILog::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;
 | 
						|
}
 |