src/ai/ai_squirrel.cpp
author truebrain
Thu, 12 Jun 2008 19:47:02 +0000
branchnoai
changeset 10942 cd3f2d07199f
parent 10900 90b1d2fbb899
child 10958 65088d587094
permissions -rw-r--r--
(svn r13496) [NoAI] -Fix: if a library depends on an other library, the import became globally known, which defeats the idea of imports. They are now restricted to their scope, and 'import' returns the class of import (if any)
/* $Id$ */

/** @file squirrel.cpp allows loading squirrel scripts to control an AI */

#include "../stdafx.h"
#include "../debug.h"
#include "../openttd.h"
#include "../string_func.h"
#include "../fileio.h"
#include "../fios.h"
#include "../network/network.h"
#include "../core/random_func.hpp"
#include <sys/types.h>
#include <sys/stat.h>

#include <squirrel.h>
#include "../squirrel.hpp"
#include "ai_info.hpp"
#include "ai_squirrel.hpp"

void AISquirrel::ScanDir(const char *dirname, bool library_dir, char *library_depth)
{
	extern bool FiosIsValidFile(const char *path, const struct dirent *ent, struct stat *sb);
	extern bool FiosIsHiddenFile(const struct dirent *ent);

	struct stat sb;
	struct dirent *dirent;
	DIR *dir;
	char d_name[MAX_PATH];
	char script_name[MAX_PATH];
	dir = ttd_opendir(dirname);
	/* Dir not found, so do nothing */
	if (dir == NULL) return;

	/* Walk all dirs trying to find a dir in which 'main.nut' exists */
	while ((dirent = readdir(dir)) != NULL) {
		ttd_strlcpy(d_name, FS2OTTD(dirent->d_name), sizeof(d_name));

		/* Valid file, not '.' or '..', not hidden */
		if (!FiosIsValidFile(dirname, dirent, &sb)) continue;
		if (strcmp(d_name, ".") == 0 || strcmp(d_name, "..") == 0) continue;
		if (FiosIsHiddenFile(dirent) && strncasecmp(d_name, PERSONAL_DIR, strlen(d_name)) != 0) continue;

		if (sb.st_mode & S_IFDIR) {
			/* Create the full-length script-name */
			ttd_strlcpy(script_name, dirname, sizeof(script_name));
			ttd_strlcat(script_name, d_name, sizeof(script_name));
			ttd_strlcat(script_name, PATHSEP, sizeof(script_name));

			if (library_dir && library_depth == NULL) {
				ScanDir(script_name, library_dir, d_name);
				continue;
			}
		}
		if (sb.st_mode & S_IFREG && !library_dir) {
			char *ext = strrchr(d_name, '.');
			if (ext == NULL || strcasecmp(ext, ".tar") != 0) continue;

			/* Create the full path to the tarfile */
			char tarname[MAX_PATH];
			ttd_strlcpy(tarname, dirname, sizeof(tarname));
			ttd_strlcat(tarname, d_name, sizeof(tarname));

			/* Now the script-name starts with the first dir in the tar */
			assert(FioTarFirstDir(tarname) != NULL);
			ttd_strlcpy(script_name, FioTarFirstDir(tarname), sizeof(script_name));

			/* The name of the AI is the name of the tar minus the .tar */
			*ext = '\0';
		}

		if (!library_dir) {
			/* We look for the file 'info.nut' inside the AI dir.. if it doesn't exists, it isn't an AI */
			ttd_strlcat(script_name, "info.nut", sizeof(script_name));
			if (FioCheckFileExists(script_name, AI_DIR)) {
				char load_script[MAX_PATH];
				ttd_strlcpy(load_script, script_name, sizeof(load_script));

				/* Remove the 'info.nut' part and replace it with 'main.nut' */
				script_name[strlen(script_name) - 8] = '\0';
				ttd_strlcat(script_name, "main.nut", sizeof(script_name));

				DEBUG(ai, 6, "[squirrel] Loading script '%s' for AI handling", load_script);
				this->current_script = script_name;
				this->current_dir = d_name;
				this->engine->LoadScript(load_script);
			}
		} else {
			/* We look for the file 'library.nut' inside the library dir.. */
			ttd_strlcat(script_name, "library.nut", sizeof(script_name));
			if (FioCheckFileExists(script_name, AI_DIR)) {
				char load_script[MAX_PATH];
				char dir_name[MAX_PATH];
				ttd_strlcpy(load_script, script_name, sizeof(load_script));
				ttd_strlcpy(dir_name, library_depth, sizeof(dir_name));
				ttd_strlcat(dir_name, ".", sizeof(dir_name));
				ttd_strlcat(dir_name, d_name, sizeof(dir_name));

				/* Remove the 'library.nut' part and replace it with 'main.nut' */
				script_name[strlen(script_name) - 11] = '\0';
				ttd_strlcat(script_name, "main.nut", sizeof(script_name));

				DEBUG(ai, 6, "[squirrel] Loading script '%s' for Squirrel library", load_script);
				this->current_script = script_name;
				this->current_dir = dir_name;
				this->engine->LoadScript(load_script);
			}
		}
	}
	closedir(dir);
}

void AISquirrel::ScanAIDir()
{
	char buf[MAX_PATH];
	Searchpath sp;

	FOR_ALL_SEARCHPATHS(sp) {
		FioAppendDirectory(buf, MAX_PATH, sp, AI_DIR);
		if (FileExists(buf)) this->ScanDir(buf, false);
		ttd_strlcat(buf, "library" PATHSEP, MAX_PATH);
		if (FileExists(buf)) this->ScanDir(buf, true);
	}
}

void AISquirrel::RescanAIDir()
{
	extern void ScanForTarFiles();
	ScanForTarFiles();
	this->ScanAIDir();
}

AISquirrel::AISquirrel()
{
	this->library_instance_count = 0;
	this->engine = new Squirrel();

	/* Create the AIInfo class, and add the RegisterAI function */
	this->engine->AddClassBegin("AIInfo");
	this->engine->AddClassEnd();
	this->engine->AddMethod("RegisterAI", &AIInfo::Constructor, 2, "tx");
	/* Create the AILibrary class, and add the RegisterLibrary function */
	this->engine->AddClassBegin("AILibrary");
	this->engine->AddClassEnd();
	this->engine->AddMethod("RegisterLibrary", &AILibrary::Constructor, 2, "tx");

	/* Mark this class as global pointer */
	this->engine->SetGlobalPointer(this);

	/* Scan the AI dir for scripts */
	this->ScanAIDir();
}

AISquirrel::~AISquirrel()
{
	AIInfoList::iterator it = this->info_list.begin();
	for (; it != this->info_list.end(); it++) {
		AIInfo *i = (*it).second;
		delete i;
	}

	delete this->engine;
}

bool AISquirrel::ImportLibrary(const char *library, const char *class_name, int version, HSQUIRRELVM vm)
{
	AILibraryList::iterator iter = this->library_list.find(library);
	/* Check if the library exists */
	if (iter == this->library_list.end()) {
		char error[1024];
		snprintf(error, sizeof(error), "couldn't find library '%s'", library);
		sq_throwerror(vm, OTTD2FS(error));
		return false;
	}

	/* Check if the version matches */
	if ((*iter).second->GetVersion() != version) {
		char error[1024];
		snprintf(error, sizeof(error), "this AI is expected library '%s' to be version %d, but it is version %d", library, version, (*iter).second->GetVersion());
		sq_throwerror(vm, OTTD2FS(error));
		return false;
	}

	/* Create a fake internal name */
	char fake_class[1024];
	snprintf(fake_class, sizeof(fake_class), "_internalNA%d", ++this->library_instance_count);

	/* Get the current table/class we belong to */
	HSQOBJECT parent;
	sq_getstackobj(vm, 1, &parent);

	/* Load the library in a 'fake' namespace, so we can link it to the name the user requested */
	sq_pushroottable(vm);
	sq_pushstring(vm, OTTD2FS(fake_class), -1);
	sq_newclass(vm, SQFalse);
	/* Load the library */
	if (!Squirrel::LoadScript(vm, (*iter).second->GetScriptName(), false)) {
		char error[1024];
		snprintf(error, sizeof(error), "there was a compile error when importing '%s'", library);
		sq_throwerror(vm, OTTD2FS(error));
		return false;
	}
	/* Create the fake class */
	sq_newslot(vm, -3, SQFalse);
	sq_pop(vm, 1);

	/* Find the real class inside the fake class (like 'sets.Vector') */
	sq_pushroottable(vm);
	sq_pushstring(vm, OTTD2FS(fake_class), -1);
	if (SQ_FAILED(sq_get(vm, -2))) {
		sq_throwerror(vm, _SC("internal error assigning library class"));
		return false;
	}
	sq_pushstring(vm, OTTD2FS((*iter).second->GetInstanceName()), -1);
	if (SQ_FAILED(sq_get(vm, -2))) {
		char error[1024];
		snprintf(error, sizeof(error), "unable to find class '%s' in the library '%s'", (*iter).second->GetInstanceName(), library);
		sq_throwerror(vm, OTTD2FS(error));
		return false;
	}
	HSQOBJECT obj;
	sq_getstackobj(vm, -1, &obj);
	sq_pop(vm, 3);

	if (StrEmpty(class_name)) {
		sq_pushobject(vm, obj);
		return true;
	}

	/* Now link the name the user wanted to our 'fake' class */
	sq_pushobject(vm, parent);
	sq_pushstring(vm, OTTD2FS(class_name), -1);
	sq_pushobject(vm, obj);
	sq_newclass(vm, SQTrue);
	sq_newslot(vm, -3, SQFalse);
	sq_pop(vm, 1);

	sq_pushobject(vm, obj);
	return true;
}

void AISquirrel::RegisterLibrary(AILibrary *library)
{
	const char *ai_name = library->GetDirName();

	/* Check if we register twice; than the first always wins */
	if (this->library_list.find(ai_name) != this->library_list.end()) {
		/* In case they are not the same dir, give a warning */
		if (strcasecmp(library->GetScriptName(), this->library_list[ai_name]->GetScriptName()) != 0) {
			DEBUG(ai, 0, "Registering two Libraries with the same name");
			DEBUG(ai, 0, "  1: %s", this->library_list[ai_name]->GetScriptName());
			DEBUG(ai, 0, "  2: %s", library->GetScriptName());
			DEBUG(ai, 0, "The first is taking precedence");
		}
		/* Delete the new AILibrary, as we will be using the old one */
		delete library;
		return;
	}
	this->library_list[ai_name] = library;
}

void AISquirrel::RegisterAI(AIInfo *info)
{
	const char *ai_name = info->GetDirName();

	/* Check if we register twice; than the first always wins */
	if (this->info_list.find(ai_name) != this->info_list.end()) {
		/* In case they are not the same dir, give a warning */
		if (strcasecmp(info->GetScriptName(), this->info_list[ai_name]->GetScriptName()) != 0) {
			DEBUG(ai, 0, "Registering two AIs with the same name");
			DEBUG(ai, 0, "  1: %s", this->info_list[ai_name]->GetScriptName());
			DEBUG(ai, 0, "  2: %s", info->GetScriptName());
			DEBUG(ai, 0, "The first is taking precedence");
		}
		/* Delete the new AIInfo, as we will be using the old one */
		delete info;
		return;
	}
	this->info_list[ai_name] = info;
}

void AISquirrel::UnregisterAI(AIInfo *info)
{
	this->info_list.erase(info->GetDirName());
}

AIInfo *AISquirrel::SelectRandomAI()
{
	if (this->info_list.size() == 0) return NULL;

	/* Find a random AI */
	uint pos;
	if (_networking) pos = InteractiveRandomRange(this->info_list.size());
	else             pos =            RandomRange(this->info_list.size());

	/* Find the Nth item from the array */
	AIInfoList::iterator it = this->info_list.begin();
	for (; pos > 0; pos--) it++;
	AIInfoList::iterator first_it = it;
	AIInfo *i = (*it).second;

	if (!i->AllowStartup()) {
		/* We can't start this AI, try to find the next best */
		do {
			it++;
			if (it == this->info_list.end()) it = this->info_list.begin();
			/* Back at the beginning? We can't start an AI. */
			if (first_it == it) return NULL;

			i = (*it).second;
		} while (!i->AllowStartup());
	}
	return i;
}

AIInfo *AISquirrel::SelectAI(const char *name)
{
	if (this->info_list.size() == 0) return NULL;

	AIInfoList::iterator it = this->info_list.begin();
	for (; it != this->info_list.end(); it++) {
		AIInfo *i = (*it).second;

		if (strcasecmp(name, (*it).first) == 0 && i->AllowStartup()) {
			return i;
		}
	}

	return NULL;
}

char *AISquirrel::GetAIConsoleList(char *p, const char *last)
{
	p += snprintf(p, last - p, "List of AIs:\n");
	AIInfoList::iterator it = this->info_list.begin();
	for (; it != this->info_list.end(); it++) {
		AIInfo *i = (*it).second;
		if (!i->AllowStartup()) continue;
		p += snprintf(p, last - p, "%10s: %s\n", (*it).first, i->GetDescription());
	}
	p += snprintf(p, last - p, "\n");

	return p;
}